A lot of changes:

Right sidebar animation
Fix animations speed with translate3d
Folders tabs scroll
Fix ripple animation
Right sidebar translateZ blink fix
Misc
This commit is contained in:
morethanwords 2020-09-23 23:29:53 +03:00
parent ae193a10db
commit f041d1bd69
122 changed files with 33868 additions and 17133 deletions

View File

@ -11,7 +11,8 @@
"build:dev": "webpack --config webpack.dev.js",
"test": "jest --config=jest.config.js",
"profile": "webpack --profile --json > stats.json --config webpack.prod.js",
"profile:dev": "webpack --profile --json > stats.json --config webpack.dev.js"
"profile:dev": "webpack --profile --json > stats.json --config webpack.dev.js",
"whybundled": "npm run profile; whybundled stats.json"
},
"author": "",
"license": "ISC",

View File

@ -0,0 +1 @@
<svg width="9" height="20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><filter x="-50%" y="-14.7%" width="200%" height="141.2%" filterUnits="objectBoundingBox" id="a"><feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0.0621962482 0 0 0 0 0.138574144 0 0 0 0 0.185037364 0 0 0 0.15 0" in="shadowBlurOuter1"/></filter><path d="M3 17h6V0c-.193 2.84-.876 5.767-2.05 8.782-.904 2.325-2.446 4.485-4.625 6.48A1 1 0 003 17z" id="b"/></defs><g fill="none" fill-rule="evenodd"><use fill="#000" filter="url(#a)" xlink:href="#b"/><use fill="#FFF" xlink:href="#b"/></g></svg>

After

Width:  |  Height:  |  Size: 730 B

View File

@ -0,0 +1 @@
<svg width='9' height='20' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'><defs><filter x='-50%' y='-14.7%' width='200%' height='141.2%' filterUnits='objectBoundingBox' id='a'><feOffset dy='1' in='SourceAlpha' result='shadowOffsetOuter1'/><feGaussianBlur stdDeviation='1' in='shadowOffsetOuter1' result='shadowBlurOuter1'/><feColorMatrix values='0 0 0 0 0.0621962482 0 0 0 0 0.138574144 0 0 0 0 0.185037364 0 0 0 0.15 0' in='shadowBlurOuter1'/></filter><path d='M6 17H0V0c.193 2.84.876 5.767 2.05 8.782.904 2.325 2.446 4.485 4.625 6.48A1 1 0 016 17z' id='b'/></defs><g fill='none' fill-rule='evenodd'><use fill='#000' filter='url(#a)' xlink:href='#b'/><use fill='#EEFFDE' xlink:href='#b'/></g></svg>

After

Width:  |  Height:  |  Size: 730 B

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

1
public/t/decoderWorker.min.js vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

1
public/t/encoderWorker.min.js vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

19
public/t/main.bundle.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/t/recorder.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
public/t/rlottie-wasm.js Normal file

File diff suppressed because one or more lines are too long

BIN
public/t/rlottie-wasm.wasm Normal file

Binary file not shown.

182
public/t/rlottie.worker.js Normal file
View File

@ -0,0 +1,182 @@
importScripts('rlottie-wasm.js');
function RLottieItem(reqId, jsString, width, height, fps) {
this.stringOnWasmHeap = null;
this.handle = null;
this.frameCount = 0;
this.reqId = reqId;
this.width = width;
this.height = height;
this.fps = Math.max(1, Math.min(60, fps || 60));
this.dead = false;
this.init(jsString, width, height);
reply('loaded', this.reqId, this.frameCount, this.fps);
}
RLottieItem.prototype.init = function(jsString) {
try {
this.handle = RLottieWorker.Api.init();
this.stringOnWasmHeap = allocate(intArrayFromString(jsString), 'i8', 0);
this.frameCount = RLottieWorker.Api.loadFromData(this.handle, this.stringOnWasmHeap);
RLottieWorker.Api.resize(this.handle, this.width, this.height);
} catch(e) {
console.error('init RLottieItem error:', e);
}
};
RLottieItem.prototype.render = function(frameNo, clamped) {
if(this.dead) return;
//return;
if(this.frameCount < frameNo || frameNo < 0) {
return;
}
try {
RLottieWorker.Api.render(this.handle, frameNo);
var bufferPointer = RLottieWorker.Api.buffer(this.handle);
var data = Module.HEAPU8.subarray(bufferPointer, bufferPointer + (this.width * this.height * 4));
if(!clamped) {
clamped = new Uint8ClampedArray(data);
} else {
clamped.set(data);
}
reply('frame', this.reqId, frameNo, clamped);
} catch(e) {
console.error('Render error:', e);
this.dead = true;
}
};
RLottieItem.prototype.destroy = function() {
this.dead = true;
RLottieWorker.Api.destroy(this.handle);
};
var RLottieWorker = (function() {
var worker = {};
worker.Api = {};
//worker.lottieHandle = null;
function initApi() {
worker.Api = {
init: Module.cwrap('lottie_init', '', []),
destroy: Module.cwrap('lottie_destroy', '', ['number']),
resize: Module.cwrap('lottie_resize', '', ['number', 'number', 'number']),
buffer: Module.cwrap('lottie_buffer', 'number', ['number']),
render: Module.cwrap('lottie_render', '', ['number', 'number']),
loadFromData: Module.cwrap('lottie_load_from_data', 'number', ['number', 'number']),
};
}
worker.init = function() {
initApi();
reply('ready');
};
return worker;
}());
Module.onRuntimeInitialized = function() {
RLottieWorker.init();
};
var items = {};
var queryableFunctions = {
loadFromData: function(reqId, jsString, width, height) {
try {
var json_parsed = jsString;//JSON.parse(jsString);
if(!json_parsed.tgs) {
throw new Error('Invalid file');
}
items[reqId] = new RLottieItem(reqId, JSON.stringify(jsString), width, height, json_parsed.fr);
} catch(e) {}
},
destroy: function(reqId) {
items[reqId].destroy();
delete items[reqId];
},
renderFrame: function(reqId, frameNo, clamped) {
//console.log('worker renderFrame', reqId, frameNo, clamped);
items[reqId].render(frameNo, clamped);
}
};
function defaultReply(message) {
// your default PUBLIC function executed only when main page calls the queryableWorker.postMessage() method directly
// do something
}
/**
* Returns true when run in WebKit derived browsers.
* This is used as a workaround for a memory leak in Safari caused by using Transferable objects to
* transfer data between WebWorkers and the main thread.
* https://github.com/mapbox/mapbox-gl-js/issues/8771
*
* This should be removed once the underlying Safari issue is fixed.
*
* @private
* @param scope {WindowOrWorkerGlobalScope} Since this function is used both on the main thread and WebWorker context,
* let the calling scope pass in the global scope object.
* @returns {boolean}
*/
var _isSafari = null;
function isSafari(scope) {
if(_isSafari == null) {
var userAgent = scope.navigator ? scope.navigator.userAgent : null;
_isSafari = !!scope.safari ||
!!(userAgent && (/\b(iPad|iPhone|iPod)\b/.test(userAgent) || (!!userAgent.match('Safari') && !userAgent.match('Chrome'))));
}
return _isSafari;
}
function reply() {
if(arguments.length < 1) {
throw new TypeError('reply - not enough arguments');
}
//if(arguments[0] == 'frame') return;
var args = Array.prototype.slice.call(arguments, 1);
if(isSafari(self)) {
postMessage({ 'queryMethodListener': arguments[0], 'queryMethodArguments': args });
} else {
var transfer = [];
for(var i = 0; i < args.length; i++) {
if(args[i] instanceof ArrayBuffer) {
transfer.push(args[i]);
}
if(args[i].buffer && args[i].buffer instanceof ArrayBuffer) {
transfer.push(args[i].buffer);
//args[i] = args[i].buffer;
}
}
postMessage({ 'queryMethodListener': arguments[0], 'queryMethodArguments': args }, transfer);
}
//postMessage({ 'queryMethodListener': arguments[0], 'queryMethodArguments': Array.prototype.slice.call(arguments, 1) });
//console.error(transfer, args);
}
onmessage = function(oEvent) {
if(oEvent.data instanceof Object && oEvent.data.hasOwnProperty('queryMethod') && oEvent.data.hasOwnProperty('queryMethodArguments')) {
queryableFunctions[oEvent.data.queryMethod].apply(self, oEvent.data.queryMethodArguments);
} else {
defaultReply(oEvent.data);
}
};

9
public/t/sw.js Normal file

File diff suppressed because one or more lines are too long

1
public/t/waveWorker.min.js vendored Normal file
View File

@ -0,0 +1 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.WaveWorker=t():e.WaveWorker=t()}("undefined"!=typeof self?self:this,(function(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var s=t[n]={i:n,l:!1,exports:{}};return e[n].call(s.exports,s,s.exports,r),s.l=!0,s.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var s in e)r.d(n,s,function(t){return e[t]}.bind(null,s));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";(function(t){var r;t.onmessage=function(e){switch(e.data.command){case"encode":r&&r.record(e.data.buffers);break;case"done":r&&(r.requestData(),r=null);break;case"close":t.close();break;case"init":r=new n(e.data),t.postMessage({message:"ready"})}};var n=function(e){if(!(e=Object.assign({wavBitDepth:16},e)).wavSampleRate)throw new Error("wavSampleRate value is required to record. NOTE: Audio is not resampled!");if(-1===[8,16,24,32].indexOf(e.wavBitDepth))throw new Error("Only 8, 16, 24 and 32 bits per sample are supported");this.bitDepth=e.wavBitDepth,this.sampleRate=e.wavSampleRate,this.recordedBuffers=[],this.bytesPerSample=this.bitDepth/8};n.prototype.record=function(e){this.numberOfChannels=this.numberOfChannels||e.length;for(var t=e[0].length,r=new Uint8Array(t*this.numberOfChannels*this.bytesPerSample),n=0;n<t;n++)for(var s=0;s<this.numberOfChannels;s++){var a=(n*this.numberOfChannels+s)*this.bytesPerSample,i=Math.max(-1,Math.min(1,e[s][n]));switch(this.bytesPerSample){case 4:i=2147483647.5*i-.5,r[a]=i,r[a+1]=i>>8,r[a+2]=i>>16,r[a+3]=i>>24;break;case 3:i=8388607.5*i-.5,r[a]=i,r[a+1]=i>>8,r[a+2]=i>>16;break;case 2:i=32767.5*i-.5,r[a]=i,r[a+1]=i>>8;break;case 1:r[a]=127.5*(i+1);break;default:throw new Error("Only 8, 16, 24 and 32 bits per sample are supported")}}this.recordedBuffers.push(r)},n.prototype.requestData=function(){var e=this.recordedBuffers[0].length,r=this.recordedBuffers.length*e,n=new Uint8Array(44+r),s=new DataView(n.buffer);s.setUint32(0,1380533830,!1),s.setUint32(4,36+r,!0),s.setUint32(8,1463899717,!1),s.setUint32(12,1718449184,!1),s.setUint32(16,16,!0),s.setUint16(20,1,!0),s.setUint16(22,this.numberOfChannels,!0),s.setUint32(24,this.sampleRate,!0),s.setUint32(28,this.sampleRate*this.bytesPerSample*this.numberOfChannels,!0),s.setUint16(32,this.bytesPerSample*this.numberOfChannels,!0),s.setUint16(34,this.bitDepth,!0),s.setUint32(36,1684108385,!1),s.setUint32(40,r,!0);for(var a=0;a<this.recordedBuffers.length;a++)n.set(this.recordedBuffers[a],a*e+44);t.postMessage({message:"page",page:n},[n.buffer]),t.postMessage({message:"done"})},e.exports=n}).call(this,r(1))},function(e,t){var r;r=function(){return this}();try{r=r||new Function("return this")()}catch(e){"object"==typeof window&&(r=window)}e.exports=r}])}));

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/t4/decoderWorker.min.js vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

1
public/t4/encoderWorker.min.js vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

19
public/t4/main.bundle.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/t4/recorder.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
public/t4/rlottie-wasm.wasm Normal file

Binary file not shown.

182
public/t4/rlottie.worker.js Normal file
View File

@ -0,0 +1,182 @@
importScripts('rlottie-wasm.js');
function RLottieItem(reqId, jsString, width, height, fps) {
this.stringOnWasmHeap = null;
this.handle = null;
this.frameCount = 0;
this.reqId = reqId;
this.width = width;
this.height = height;
this.fps = Math.max(1, Math.min(60, fps || 60));
this.dead = false;
this.init(jsString, width, height);
reply('loaded', this.reqId, this.frameCount, this.fps);
}
RLottieItem.prototype.init = function(jsString) {
try {
this.handle = RLottieWorker.Api.init();
this.stringOnWasmHeap = allocate(intArrayFromString(jsString), 'i8', 0);
this.frameCount = RLottieWorker.Api.loadFromData(this.handle, this.stringOnWasmHeap);
RLottieWorker.Api.resize(this.handle, this.width, this.height);
} catch(e) {
console.error('init RLottieItem error:', e);
}
};
RLottieItem.prototype.render = function(frameNo, clamped) {
if(this.dead) return;
//return;
if(this.frameCount < frameNo || frameNo < 0) {
return;
}
try {
RLottieWorker.Api.render(this.handle, frameNo);
var bufferPointer = RLottieWorker.Api.buffer(this.handle);
var data = Module.HEAPU8.subarray(bufferPointer, bufferPointer + (this.width * this.height * 4));
if(!clamped) {
clamped = new Uint8ClampedArray(data);
} else {
clamped.set(data);
}
reply('frame', this.reqId, frameNo, clamped);
} catch(e) {
console.error('Render error:', e);
this.dead = true;
}
};
RLottieItem.prototype.destroy = function() {
this.dead = true;
RLottieWorker.Api.destroy(this.handle);
};
var RLottieWorker = (function() {
var worker = {};
worker.Api = {};
//worker.lottieHandle = null;
function initApi() {
worker.Api = {
init: Module.cwrap('lottie_init', '', []),
destroy: Module.cwrap('lottie_destroy', '', ['number']),
resize: Module.cwrap('lottie_resize', '', ['number', 'number', 'number']),
buffer: Module.cwrap('lottie_buffer', 'number', ['number']),
render: Module.cwrap('lottie_render', '', ['number', 'number']),
loadFromData: Module.cwrap('lottie_load_from_data', 'number', ['number', 'number']),
};
}
worker.init = function() {
initApi();
reply('ready');
};
return worker;
}());
Module.onRuntimeInitialized = function() {
RLottieWorker.init();
};
var items = {};
var queryableFunctions = {
loadFromData: function(reqId, jsString, width, height) {
try {
var json_parsed = jsString;//JSON.parse(jsString);
if(!json_parsed.tgs) {
throw new Error('Invalid file');
}
items[reqId] = new RLottieItem(reqId, JSON.stringify(jsString), width, height, json_parsed.fr);
} catch(e) {}
},
destroy: function(reqId) {
items[reqId].destroy();
delete items[reqId];
},
renderFrame: function(reqId, frameNo, clamped) {
//console.log('worker renderFrame', reqId, frameNo, clamped);
items[reqId].render(frameNo, clamped);
}
};
function defaultReply(message) {
// your default PUBLIC function executed only when main page calls the queryableWorker.postMessage() method directly
// do something
}
/**
* Returns true when run in WebKit derived browsers.
* This is used as a workaround for a memory leak in Safari caused by using Transferable objects to
* transfer data between WebWorkers and the main thread.
* https://github.com/mapbox/mapbox-gl-js/issues/8771
*
* This should be removed once the underlying Safari issue is fixed.
*
* @private
* @param scope {WindowOrWorkerGlobalScope} Since this function is used both on the main thread and WebWorker context,
* let the calling scope pass in the global scope object.
* @returns {boolean}
*/
var _isSafari = null;
function isSafari(scope) {
if(_isSafari == null) {
var userAgent = scope.navigator ? scope.navigator.userAgent : null;
_isSafari = !!scope.safari ||
!!(userAgent && (/\b(iPad|iPhone|iPod)\b/.test(userAgent) || (!!userAgent.match('Safari') && !userAgent.match('Chrome'))));
}
return _isSafari;
}
function reply() {
if(arguments.length < 1) {
throw new TypeError('reply - not enough arguments');
}
//if(arguments[0] == 'frame') return;
var args = Array.prototype.slice.call(arguments, 1);
if(isSafari(self)) {
postMessage({ 'queryMethodListener': arguments[0], 'queryMethodArguments': args });
} else {
var transfer = [];
for(var i = 0; i < args.length; i++) {
if(args[i] instanceof ArrayBuffer) {
transfer.push(args[i]);
}
if(args[i].buffer && args[i].buffer instanceof ArrayBuffer) {
transfer.push(args[i].buffer);
//args[i] = args[i].buffer;
}
}
postMessage({ 'queryMethodListener': arguments[0], 'queryMethodArguments': args }, transfer);
}
//postMessage({ 'queryMethodListener': arguments[0], 'queryMethodArguments': Array.prototype.slice.call(arguments, 1) });
//console.error(transfer, args);
}
onmessage = function(oEvent) {
if(oEvent.data instanceof Object && oEvent.data.hasOwnProperty('queryMethod') && oEvent.data.hasOwnProperty('queryMethodArguments')) {
queryableFunctions[oEvent.data.queryMethod].apply(self, oEvent.data.queryMethodArguments);
} else {
defaultReply(oEvent.data);
}
};

9
public/t4/sw.js Normal file

File diff suppressed because one or more lines are too long

1
public/t4/waveWorker.min.js vendored Normal file
View File

@ -0,0 +1 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.WaveWorker=t():e.WaveWorker=t()}("undefined"!=typeof self?self:this,(function(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var s=t[n]={i:n,l:!1,exports:{}};return e[n].call(s.exports,s,s.exports,r),s.l=!0,s.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var s in e)r.d(n,s,function(t){return e[t]}.bind(null,s));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";(function(t){var r;t.onmessage=function(e){switch(e.data.command){case"encode":r&&r.record(e.data.buffers);break;case"done":r&&(r.requestData(),r=null);break;case"close":t.close();break;case"init":r=new n(e.data),t.postMessage({message:"ready"})}};var n=function(e){if(!(e=Object.assign({wavBitDepth:16},e)).wavSampleRate)throw new Error("wavSampleRate value is required to record. NOTE: Audio is not resampled!");if(-1===[8,16,24,32].indexOf(e.wavBitDepth))throw new Error("Only 8, 16, 24 and 32 bits per sample are supported");this.bitDepth=e.wavBitDepth,this.sampleRate=e.wavSampleRate,this.recordedBuffers=[],this.bytesPerSample=this.bitDepth/8};n.prototype.record=function(e){this.numberOfChannels=this.numberOfChannels||e.length;for(var t=e[0].length,r=new Uint8Array(t*this.numberOfChannels*this.bytesPerSample),n=0;n<t;n++)for(var s=0;s<this.numberOfChannels;s++){var a=(n*this.numberOfChannels+s)*this.bytesPerSample,i=Math.max(-1,Math.min(1,e[s][n]));switch(this.bytesPerSample){case 4:i=2147483647.5*i-.5,r[a]=i,r[a+1]=i>>8,r[a+2]=i>>16,r[a+3]=i>>24;break;case 3:i=8388607.5*i-.5,r[a]=i,r[a+1]=i>>8,r[a+2]=i>>16;break;case 2:i=32767.5*i-.5,r[a]=i,r[a+1]=i>>8;break;case 1:r[a]=127.5*(i+1);break;default:throw new Error("Only 8, 16, 24 and 32 bits per sample are supported")}}this.recordedBuffers.push(r)},n.prototype.requestData=function(){var e=this.recordedBuffers[0].length,r=this.recordedBuffers.length*e,n=new Uint8Array(44+r),s=new DataView(n.buffer);s.setUint32(0,1380533830,!1),s.setUint32(4,36+r,!0),s.setUint32(8,1463899717,!1),s.setUint32(12,1718449184,!1),s.setUint32(16,16,!0),s.setUint16(20,1,!0),s.setUint16(22,this.numberOfChannels,!0),s.setUint32(24,this.sampleRate,!0),s.setUint32(28,this.sampleRate*this.bytesPerSample*this.numberOfChannels,!0),s.setUint16(32,this.bytesPerSample*this.numberOfChannels,!0),s.setUint16(34,this.bitDepth,!0),s.setUint32(36,1684108385,!1),s.setUint32(40,r,!0);for(var a=0;a<this.recordedBuffers.length;a++)n.set(this.recordedBuffers[a],a*e+44);t.postMessage({message:"page",page:n},[n.buffer]),t.postMessage({message:"done"})},e.exports=n}).call(this,r(1))},function(e,t){var r;r=function(){return this}();try{r=r||new Function("return this")()}catch(e){"object"==typeof window&&(r=window)}e.exports=r}])}));

View File

@ -1,8 +1,8 @@
import { $rootScope } from "../lib/utils";
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import appDocsManager, {MyDocument} from "../lib/appManagers/appDocsManager";
import { isSafari } from "../lib/config";
import { CancellablePromise, deferredPromise } from "../lib/polyfill";
import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise";
import { isSafari } from "../helpers/userAgent";
// TODO: если удалить сообщение, и при этом аудио будет играть - оно не остановится, и можно будет по нему перейти вникуда

View File

@ -307,7 +307,7 @@ export class AppSelectPeers {
this.selectedContainer.insertBefore(div, this.input);
//this.selectedScrollable.scrollTop = this.selectedScrollable.scrollHeight;
this.selectedScrollable.scrollTo(this.selectedScrollable.scrollHeight, true, true);
this.selectedScrollable.scrollTo(this.selectedScrollable.scrollHeight, 'top', true, true);
this.onChange && this.onChange(this.selected.size);
return div;

View File

@ -5,8 +5,9 @@ import ProgressivePreloader from "./preloader";
import { MediaProgressLine } from "../lib/mediaPlayer";
import appMediaPlaybackController from "./appMediaPlaybackController";
import { DocumentAttribute } from "../layer";
import { mediaSizes, isSafari } from "../lib/config";
import { Download } from "../lib/appManagers/appDownloadManager";
import mediaSizes from "../helpers/mediaSizes";
import { isSafari } from "../helpers/userAgent";
// https://github.com/LonamiWebs/Telethon/blob/4393ec0b83d511b6a20d8a20334138730f084375/telethon/utils.py#L1285
export function decodeWaveform(waveform: Uint8Array | number[]) {
@ -121,7 +122,7 @@ function wrapVoiceMessage(doc: MyDocument, audioEl: AudioElement) {
let start = () => {
clearInterval(interval);
interval = setInterval(() => {
interval = window.setInterval(() => {
if(lastIndex > svg.childElementCount || isNaN(audio.duration) || audio.paused) {
clearInterval(interval);
return;

View File

@ -34,11 +34,16 @@ export class ChatAudio {
this.close.addEventListener('click', (e) => {
cancelEvent(e);
const scrollTop = appImManager.scrollable.scrollTop;
this.container.style.display = 'none';
this.container.parentElement.classList.remove('is-audio-shown');
appImManager.topbar.classList.remove('is-audio-shown');
if(this.toggle.classList.contains('flip-icon')) {
appMediaPlaybackController.toggle();
}
if(!appImManager.topbar.classList.contains('is-pinned-shown')) {
appImManager.scrollable.scrollTop = scrollTop - height;
}
});
this.toggle.addEventListener('click', (e) => {
@ -46,6 +51,8 @@ export class ChatAudio {
appMediaPlaybackController.toggle();
});
const height = 52;
$rootScope.$on('audio_play', (e) => {
const {doc, mid} = e.detail;
@ -68,8 +75,11 @@ export class ChatAudio {
if(this.container.style.display) {
const scrollTop = appImManager.scrollable.scrollTop;
this.container.style.display = '';
this.container.parentElement.classList.add('is-audio-shown');
appImManager.scrollable.scrollTop = scrollTop;
appImManager.topbar.classList.add('is-audio-shown');
if(!appImManager.topbar.classList.contains('is-pinned-shown')) {
appImManager.scrollable.scrollTop = scrollTop + height;
}
}
});

View File

@ -6,7 +6,7 @@ import { horizontalMenu } from "../horizontalMenu";
import animationIntersector from "../animationIntersector";
import appSidebarRight from "../../lib/appManagers/appSidebarRight";
import appImManager from "../../lib/appManagers/appImManager";
import Scrollable from "../scrollable_new";
import Scrollable, { ScrollableX } from "../scrollable_new";
import EmojiTab from "./tabs/emoji";
import StickersTab from "./tabs/stickers";
import StickyIntersector from "../stickyIntersector";
@ -240,7 +240,7 @@ export class EmoticonsDropdown {
//animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP);
};
public static menuOnClick = (menu: HTMLUListElement, scroll: Scrollable, menuScroll?: Scrollable) => {
public static menuOnClick = (menu: HTMLUListElement, scroll: Scrollable, menuScroll?: ScrollableX) => {
let prevId = 0;
let jumpedTo = -1;

View File

@ -81,7 +81,7 @@ export default class EmojiTab implements EmoticonsTab {
//console.timeEnd('emojiParse');
const menu = this.content.previousElementSibling.firstElementChild as HTMLUListElement;
const emojiScroll = this.scroll = new Scrollable(this.content, 'y', 'EMOJI', null);
const emojiScroll = this.scroll = new Scrollable(this.content, 'EMOJI', null);
//emojiScroll.setVirtualContainer(emojiScroll.container);

View File

@ -13,7 +13,7 @@ export default class GifsTab implements EmoticonsTab {
const gifsContainer = this.content.firstElementChild as HTMLDivElement;
gifsContainer.addEventListener('click', EmoticonsDropdown.onMediaClick);
const scroll = new Scrollable(this.content, 'y', 'GIFS', null);
const scroll = new Scrollable(this.content, 'GIFS', null);
const masonry = new GifsMasonry(gifsContainer, EMOTICONSSTICKERGROUP, scroll);
const preloader = putPreloader(this.content, true);

View File

@ -1,6 +1,6 @@
import emoticonsDropdown, { EmoticonsTab, EMOTICONSSTICKERGROUP, EmoticonsDropdown } from "..";
import { StickerSet } from "../../../layer";
import Scrollable from "../../scrollable_new";
import Scrollable, { ScrollableX } from "../../scrollable_new";
import { wrapSticker } from "../../wrappers";
import appStickersManager from "../../../lib/appManagers/appStickersManager";
import appDownloadManager from "../../../lib/appManagers/appDownloadManager";
@ -230,7 +230,7 @@ export default class StickersTab implements EmoticonsTab {
let menuWrapper = this.content.previousElementSibling as HTMLDivElement;
this.menu = menuWrapper.firstElementChild.firstElementChild as HTMLUListElement;
let menuScroll = new Scrollable(menuWrapper, 'x');
let menuScroll = new ScrollableX(menuWrapper);
let stickersDiv = document.createElement('div');
stickersDiv.classList.add('stickers-categories');
@ -274,7 +274,7 @@ export default class StickersTab implements EmoticonsTab {
stickersDiv.addEventListener('click', EmoticonsDropdown.onMediaClick);
this.scroll = new Scrollable(this.content, 'y', 'STICKERS', undefined, undefined, 2);
this.scroll = new Scrollable(this.content, 'STICKERS', undefined, undefined, 2);
this.scroll.setVirtualContainer(stickersDiv);
this.stickyIntersector = EmoticonsDropdown.menuOnClick(this.menu, this.scroll, menuScroll);

View File

@ -3,9 +3,9 @@ import appDocsManager, {MyDocument} from "../lib/appManagers/appDocsManager";
import { wrapVideo } from "./wrappers";
import { renderImageFromUrl } from "./misc";
import { LazyLoadQueueRepeat2 } from "./lazyLoadQueue";
import { CancellablePromise, deferredPromise } from "../lib/polyfill";
import animationIntersector from "./animationIntersector";
import Scrollable from "./scrollable_new";
import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise";
const width = 400;
const maxSingleWidth = width - 100;

View File

@ -10,14 +10,15 @@ function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, t
tabContent.style.transform = `translateX(-25%)`;
prevTabContent.style.transform = `translateX(20%)`;
} */
const width = prevTabContent.getBoundingClientRect().width;
if(toRight) {
prevTabContent.style.filter = `brightness(80%)`;
prevTabContent.style.transform = `translateX(-25%)`;
tabContent.style.transform = `translateX(100%)`;
prevTabContent.style.transform = `translate3d(${-width * .25}px, 0, 0)`;
tabContent.style.transform = `translate3d(${width}px, 0, 0)`;
} else {
tabContent.style.filter = `brightness(80%)`;
tabContent.style.transform = `translateX(-25%)`;
prevTabContent.style.transform = `translateX(100%)`;
tabContent.style.transform = `translate3d(${-width * .25}px, 0, 0)`;
prevTabContent.style.transform = `translate3d(${width}px, 0, 0)`;
}
tabContent.classList.add('active');
@ -28,12 +29,13 @@ function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, t
}
function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) {
const width = prevTabContent.getBoundingClientRect().width;
if(toRight) {
tabContent.style.transform = `translateX(100%)`;
prevTabContent.style.transform = `translateX(-100%)`;
tabContent.style.transform = `translate3d(${width}px, 0, 0)`;
prevTabContent.style.transform = `translate3d(${-width}px, 0, 0)`;
} else {
tabContent.style.transform = `translateX(-100%)`;
prevTabContent.style.transform = `translateX(100%)`;
tabContent.style.transform = `translate3d(${-width}px, 0, 0)`;
prevTabContent.style.transform = `translate3d(${width}px, 0, 0)`;
}
tabContent.classList.add('active');
@ -83,7 +85,7 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?
const _prevId = prevId;
if(hideTimeouts.hasOwnProperty(id)) clearTimeout(hideTimeouts[id]);
if(p/* && false */) {
hideTimeouts[_prevId] = setTimeout(() => {
hideTimeouts[_prevId] = window.setTimeout(() => {
p.style.transform = '';
p.style.filter = '';
p.classList.remove('active');

View File

@ -1,4 +1,6 @@
import Config, { touchSupport, isApple, mediaSizes } from "../lib/config";
import mediaSizes from "../helpers/mediaSizes";
import { isApple } from "../helpers/userAgent";
import Config, { touchSupport } from "../lib/config";
export const loadedURLs: {[url: string]: boolean} = {};
const set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string) => {

View File

@ -1,11 +1,12 @@
import appPollsManager, { PollResults, Poll } from "../lib/appManagers/appPollsManager";
import { RichTextProcessor } from "../lib/richtextprocessor";
import { findUpClassName, $rootScope, cancelEvent } from "../lib/utils";
import { mediaSizes, touchSupport } from "../lib/config";
import { touchSupport } from "../lib/config";
import appSidebarRight from "../lib/appManagers/appSidebarRight";
import appImManager from "../lib/appManagers/appImManager";
import serverTimeManager from "../lib/mtproto/serverTimeManager";
import { ripple } from "./ripple";
import mediaSizes from "../helpers/mediaSizes";
let lineTotalLength = 0;
const tailLength = 9;

View File

@ -46,7 +46,7 @@ export default class PopupCreatePoll extends PopupElement {
this.confirmBtn.addEventListener('click', this.onSubmitClick);
this.scrollable = new Scrollable(this.body, 'y', undefined);
this.scrollable = new Scrollable(this.body);
this.appendMoreField();
}
@ -131,6 +131,6 @@ export default class PopupCreatePoll extends PopupElement {
this.questions.append(questionField);
this.scrollable.scrollTo(this.scrollable.scrollHeight, true, true);
this.scrollable.scrollTo(this.scrollable.scrollHeight, 'top', true, true);
}
}

View File

@ -64,7 +64,7 @@ export default class PopupStickers extends PopupElement {
this.stickersFooter.innerText = 'Loading...';
this.body.append(div);
const scrollable = new Scrollable(this.body, 'y', undefined);
const scrollable = new Scrollable(this.body);
this.body.append(this.stickersFooter);
// const editButton = document.createElement('button');

View File

@ -1,5 +1,5 @@
import { isInDOM, cancelEvent } from "../lib/utils";
import { CancellablePromise } from "../lib/polyfill";
import { CancellablePromise } from "../helpers/cancellablePromise";
export default class ProgressivePreloader {
public preloader: HTMLDivElement;

View File

@ -1,4 +1,5 @@
import { touchSupport } from "../lib/config";
import { findUpClassName } from "../lib/utils";
let rippleClickID = 0;
export function ripple(elem: HTMLElement, callback: (id: number) => Promise<boolean | void> = () => Promise.resolve(), onEnd: (id: number) => void = null) {
@ -28,6 +29,7 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise<bool
//console.log('ripple drawRipple');
handler = () => {
//return;
let elapsedTime = Date.now() - startTime;
if(elapsedTime < duration) {
let delay = Math.max(duration - elapsedTime, duration / 2);
@ -66,8 +68,8 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise<bool
} */
window.requestAnimationFrame(() => {
span.classList.add('c-ripple__circle');
let rect = r.getBoundingClientRect();
span.classList.add('c-ripple__circle');
let clickX = clientX - rect.left;
let clickY = clientY - rect.top;
@ -110,7 +112,7 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise<bool
elem.addEventListener('touchstart', (e) => {
//console.log('ripple touchstart', e);
if(e.touches.length > 1 || ((e.target as HTMLElement).tagName == 'BUTTON' && e.target != elem)) {
if(e.touches.length > 1 || ((e.target as HTMLElement).tagName == 'BUTTON' && e.target != elem) || findUpClassName(e.target as HTMLElement, 'c-ripple') != r) {
return;
}
@ -130,7 +132,9 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise<bool
}, {passive: true});
} else {
elem.addEventListener('mousedown', (e) => {
if(elem.dataset.ripple == '0') {
//console.log('ripple mousedown', e, e.target, findUpClassName(e.target as HTMLElement, 'c-ripple') == r);
if(elem.dataset.ripple == '0' || findUpClassName(e.target as HTMLElement, 'c-ripple') != r) {
return false;
} else if(touchStartFired) {
touchStartFired = false;

View File

@ -1,10 +1,10 @@
import { logger, LogLevels } from "../lib/logger";
import smoothscroll from '../vendor/smoothscroll';
import { touchSupport, isSafari, mediaSizes } from "../lib/config";
import { CancellablePromise, deferredPromise } from "../lib/polyfill";
import smoothscroll, { SCROLL_TIME, SmoothScrollToOptions } from '../vendor/smoothscroll';
import { touchSupport } from "../lib/config";
//import { CancellablePromise, deferredPromise } from "../lib/polyfill";
//import { isInDOM } from "../lib/utils";
(window as any).__forceSmoothScrollPolyfill__ = true;
smoothscroll.polyfill();
smoothscroll();
/*
var el = $0;
var height = 0;
@ -48,10 +48,88 @@ const scrollsIntersector = new IntersectionObserver(entries => {
}
}); */
export default class Scrollable {
//public container: HTMLDivElement;
public overflowContainer: HTMLElement;
export class ScrollableBase {
protected log: ReturnType<typeof logger>;
protected onScroll: () => void;
public getScrollValue: () => number;
public scrollLocked = 0;
constructor(public el: HTMLElement, logPrefix = '', public appendTo = el, public container: HTMLElement = document.createElement('div')) {
this.container.classList.add('scrollable');
if(!appendTo) {
this.appendTo = this.container;
}
this.log = logger('SCROLL' + (logPrefix ? '-' + logPrefix : ''), LogLevels.error);
if(el) {
Array.from(el.children).forEach(c => this.container.append(c));
el.append(this.container);
}
//this.onScroll();
}
protected setListeners() {
window.addEventListener('resize', this.onScroll);
this.container.addEventListener('scroll', this.onScroll, {passive: true, capture: true});
}
public prepend(element: HTMLElement) {
this.appendTo.prepend(element);
}
public append(element: HTMLElement) {
this.appendTo.append(element);
}
public contains(element: Element) {
return !!element.parentElement;
}
public removeElement(element: Element) {
element.remove();
}
public scrollTo(value: number, side: 'top' | 'left', smooth = true, important = false, scrollTime = SCROLL_TIME) {
if(this.scrollLocked && !important) return;
const scrollValue = this.getScrollValue();
if(scrollValue == Math.floor(value)) {
return;
}
if(this.scrollLocked) clearTimeout(this.scrollLocked);
/* else {
this.scrollLockedPromise = deferredPromise<void>();
} */
this.scrollLocked = window.setTimeout(() => {
this.scrollLocked = 0;
//this.scrollLockedPromise.resolve();
//this.onScroll();
this.container.dispatchEvent(new CustomEvent('scroll'));
}, scrollTime);
const options: SmoothScrollToOptions = {
behavior: smooth ? 'smooth' : 'auto',
scrollTime
};
options[side] = value;
this.container.scrollTo(options as any);
}
get length() {
return this.appendTo.childElementCount;
}
}
export default class Scrollable extends ScrollableBase {
public splitUp: HTMLElement;
public onScrolledTop: () => void = null;
@ -63,8 +141,6 @@ export default class Scrollable {
private disableHoverTimeout: number = 0;
private log: ReturnType<typeof logger>;
/* private sentinelsObserver: IntersectionObserver;
private topSentinel: HTMLDivElement;
private bottomSentinel: HTMLDivElement; */
@ -80,8 +156,7 @@ export default class Scrollable {
/* private onScrolledTopFired = false;
private onScrolledBottomFired = false; */
public scrollLocked = 0;
public scrollLockedPromise: CancellablePromise<void> = Promise.resolve();
//public scrollLockedPromise: CancellablePromise<void> = Promise.resolve();
public isVisible = false;
private reorderTimeout: number;
@ -102,8 +177,8 @@ export default class Scrollable {
this.visible.delete(element);
}
constructor(public el: HTMLElement, axis: 'y' | 'x' = 'y', logPrefix = '', public appendTo = el, public onScrollOffset = 300, public splitCount = 15, public container: HTMLElement = document.createElement('div')) {
this.container.classList.add('scrollable');
constructor(el: HTMLElement, logPrefix = '', appendTo = el, public onScrollOffset = 300, public splitCount = 15, container: HTMLElement = document.createElement('div')) {
super(el, logPrefix, appendTo, container);
this.visible = new Set();
this.observer = new IntersectionObserver(entries => {
@ -173,63 +248,11 @@ export default class Scrollable {
}
});
if(!appendTo) {
this.appendTo = this.container;
}
this.log = logger('SCROLL' + (logPrefix ? '-' + logPrefix : ''), LogLevels.error);
this.container.classList.add('scrollable-y');
if(axis == 'x') {
this.container.classList.add('scrollable-x');
if(!touchSupport) {
const scrollHorizontally = (e: any) => {
e = window.event || e;
if(e.which == 1) {
// maybe horizontal scroll is natively supports, works on macbook
return;
}
const delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
this.container.scrollLeft -= (delta * 20);
e.preventDefault();
};
if(this.container.addEventListener) {
// IE9, Chrome, Safari, Opera
this.container.addEventListener("mousewheel", scrollHorizontally, false);
// Firefox
this.container.addEventListener("DOMMouseScroll", scrollHorizontally, false);
} else {
// IE 6/7/8
// @ts-ignore
this.container.attachEvent("onmousewheel", scrollHorizontally);
}
}
} else if(axis == 'y') {
this.container.classList.add('scrollable-y');
} else {
throw new Error('no side for scroll');
}
window.addEventListener('resize', () => {
this.overflowContainer = mediaSizes.isMobile && false ? document.documentElement : this.container;
this.onScroll();
});
this.container.addEventListener('scroll', this.onScroll, {passive: true, capture: true});
//document.documentElement.addEventListener('scroll', binded, {passive: true, capture: true});
//window.addEventListener('scroll', binded, {passive: true, capture: true});
if(el) {
Array.from(el.children).forEach(c => this.container.append(c));
el.append(this.container);
}
//this.onScroll();
this.overflowContainer = mediaSizes.isMobile && false ? document.documentElement : this.container;
/* scrollables.set(this.container, this);
scrollsIntersector.observe(this.container); */
this.setListeners();
}
// public attachSentinels(container = this.container, offset = this.onScrollOffset) {
@ -329,12 +352,12 @@ export default class Scrollable {
this.onScrollMeasure = window.requestAnimationFrame(() => {
//if(!this.isVisible) return;
this.checkForTriggers(this.overflowContainer);
this.checkForTriggers();
this.onScrollMeasure = 0;
if(!this.splitUp) return;
const scrollTop = this.overflowContainer.scrollTop;
const scrollTop = this.scrollTop;
if(this.lastScrollTop != scrollTop) {
this.lastScrollDirection = this.lastScrollTop < scrollTop ? 1 : -1;
this.lastScrollTop = scrollTop;
@ -344,9 +367,10 @@ export default class Scrollable {
});
};
public checkForTriggers(container: HTMLElement) {
public checkForTriggers() {
if(this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) return;
const container = this.container;
const scrollHeight = container.scrollHeight;
if(!scrollHeight) { // незачем вызывать триггеры если блок пустой или не виден
return;
@ -439,11 +463,11 @@ export default class Scrollable {
if(element.parentElement && !this.scrollLocked) {
const isFirstUnread = element.classList.contains('is-first-unread');
let offsetTop = element.getBoundingClientRect().top - this.container.getBoundingClientRect().top;
offsetTop = this.container.scrollTop + offsetTop;
let offset = element.getBoundingClientRect().top - this.container.getBoundingClientRect().top;
offset = this.scrollTop + offset;
if(!smooth && isFirstUnread) {
this.scrollTo(offsetTop, false);
this.scrollTo(offset, 'top', false);
return;
}
@ -451,39 +475,20 @@ export default class Scrollable {
const height = element.scrollHeight;
const d = (clientHeight - height) / 2;
offsetTop -= d;
offset -= d;
this.scrollTo(offsetTop, smooth);
this.scrollTo(offset, 'top', smooth);
}
}
public scrollTo(top: number, smooth = true, important = false) {
if(this.scrollLocked && !important) return;
const scrollTop = this.scrollTop;
if(scrollTop == Math.floor(top)) {
return;
}
if(this.scrollLocked) clearTimeout(this.scrollLocked);
else {
this.scrollLockedPromise = deferredPromise<void>();
}
this.scrollLocked = window.setTimeout(() => {
this.scrollLocked = 0;
this.scrollLockedPromise.resolve();
//this.onScroll();
this.container.dispatchEvent(new CustomEvent('scroll'));
}, 468);
this.container.scrollTo({behavior: smooth ? 'smooth' : 'auto', top});
}
public removeElement(element: Element) {
element.remove();
}
public getScrollValue = () => {
return this.scrollTop;
};
set scrollTop(y: number) {
this.container.scrollTop = y;
}
@ -496,8 +501,57 @@ export default class Scrollable {
get scrollHeight() {
return this.container.scrollHeight;
}
get length() {
return this.appendTo.childElementCount;
}
}
export class ScrollableX extends ScrollableBase {
constructor(public el: HTMLElement, logPrefix = '', public appendTo = el, public onScrollOffset = 300, public splitCount = 15, public container: HTMLElement = document.createElement('div')) {
super(el, logPrefix, appendTo, container);
this.container.classList.add('scrollable-x');
if(!touchSupport) {
const scrollHorizontally = (e: any) => {
e = window.event || e;
if(e.which == 1) {
// maybe horizontal scroll is natively supports, works on macbook
return;
}
const delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
this.container.scrollLeft -= (delta * 20);
e.preventDefault();
};
if(this.container.addEventListener) {
// IE9, Chrome, Safari, Opera
this.container.addEventListener("mousewheel", scrollHorizontally, false);
// Firefox
this.container.addEventListener("DOMMouseScroll", scrollHorizontally, false);
} else {
// IE 6/7/8
// @ts-ignore
this.container.attachEvent("onmousewheel", scrollHorizontally);
}
}
this.setListeners();
}
public scrollIntoView(element: HTMLElement, smooth = true, scrollTime?: number) {
if(element.parentElement && !this.scrollLocked) {
let offset = element.getBoundingClientRect().left - this.container.getBoundingClientRect().left;
offset = this.getScrollValue() + offset;
const clientWidth = this.container.clientWidth;
const width = element.scrollWidth;
const d = (clientWidth - width) / 2;
offset -= d;
this.scrollTo(offset, 'left', smooth, undefined, scrollTime);
}
}
public getScrollValue = () => {
return this.container.scrollLeft;
};
}

View File

@ -134,7 +134,7 @@ export default class AppEditProfileTab implements SliderTab {
});
});
let scrollable = new Scrollable(this.scrollWrapper as HTMLElement, 'y');
let scrollable = new Scrollable(this.scrollWrapper as HTMLElement);
}
public fillElements() {

View File

@ -31,7 +31,7 @@ export default class AppGifsTab implements SliderTab {
private searchPromise: ReturnType<AppInlineBotsManager['getInlineResults']>;
constructor() {
this.scrollable = new Scrollable(this.contentDiv, 'y', ANIMATIONGROUP, undefined, undefined, 2);
this.scrollable = new Scrollable(this.contentDiv, ANIMATIONGROUP, undefined, undefined, 2);
this.scrollable.setVirtualContainer(this.gifsDiv);
this.masonry = new GifsMasonry(this.gifsDiv, ANIMATIONGROUP, this.scrollable);

View File

@ -17,7 +17,7 @@ export default class AppPollResultsTab implements SliderTab {
private mid: number;
constructor() {
this.scrollable = new Scrollable(this.contentDiv, 'y', 'POLL-RESULTS', undefined, undefined, 2);
this.scrollable = new Scrollable(this.contentDiv, 'POLL-RESULTS', undefined, undefined, 2);
}
public cleanup() {

View File

@ -23,7 +23,7 @@ export default class AppStickersTab implements SliderTab {
private lazyLoadQueue: LazyLoadQueue;
constructor() {
this.scrollable = new Scrollable(this.contentDiv, 'y', 'STICKERS-SEARCH', undefined, undefined, 2);
this.scrollable = new Scrollable(this.contentDiv, 'STICKERS-SEARCH', undefined, undefined, 2);
this.scrollable.setVirtualContainer(this.setsDiv);
this.lazyLoadQueue = new LazyLoadQueue();

View File

@ -10,7 +10,6 @@ import { renderImageFromUrl } from './misc';
import appMessagesManager from '../lib/appManagers/appMessagesManager';
import { Layouter, RectPart } from './groupedLayout';
import PollElement from './poll';
import { mediaSizes, isSafari } from '../lib/config';
import animationIntersector from './animationIntersector';
import AudioElement from './audio';
import { DownloadBlob } from '../lib/appManagers/appDownloadManager';
@ -18,7 +17,9 @@ import webpWorkerController from '../lib/webp/webpWorkerController';
import { readBlobAsText } from '../helpers/blob';
import appMediaPlaybackController from './appMediaPlaybackController';
import { PhotoSize } from '../layer';
import { deferredPromise } from '../lib/polyfill';
import { deferredPromise } from '../helpers/cancellablePromise';
import mediaSizes from '../helpers/mediaSizes';
import { isSafari } from '../helpers/userAgent';
export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue, noInfo, group}: {
doc: MyDocument,
@ -704,6 +705,14 @@ export function wrapReply(title: string, subtitle: string, message?: any, isPinn
const replySubtitle = document.createElement('div');
replySubtitle.classList.add(prefix + '-subtitle');
if(title.length > 150) {
title = title.substr(0, 140) + '...';
}
if(subtitle.length > 150) {
subtitle = subtitle.substr(0, 140) + '...';
}
replyTitle.innerHTML = title ? RichTextProcessor.wrapEmojiText(title) : '';

View File

@ -0,0 +1,63 @@
export interface CancellablePromise<T> extends Promise<T> {
resolve?: (...args: any[]) => void,
reject?: (...args: any[]) => void,
cancel?: () => void,
notify?: (...args: any[]) => void,
notifyAll?: (...args: any[]) => void,
lastNotify?: any,
listeners?: Array<(...args: any[]) => void>,
addNotifyListener?: (callback: (...args: any[]) => void) => void,
isFulfilled?: boolean,
isRejected?: boolean
}
export function deferredPromise<T>() {
let deferredHelper: any = {
isFulfilled: false,
isRejected: false,
notify: () => {},
notifyAll: (...args: any[]) => {
deferredHelper.lastNotify = args;
deferredHelper.listeners.forEach((callback: any) => callback(...args));
},
lastNotify: undefined,
listeners: [],
addNotifyListener: (callback: (...args: any[]) => void) => {
if(deferredHelper.lastNotify) {
callback(...deferredHelper.lastNotify);
}
deferredHelper.listeners.push(callback);
}
};
let deferred: CancellablePromise<T> = new Promise<T>((resolve, reject) => {
deferredHelper.resolve = (value: T) => {
if(deferred.isFulfilled) return;
deferred.isFulfilled = true;
resolve(value);
};
deferredHelper.reject = (...args: any[]) => {
if(deferred.isRejected) return;
deferred.isRejected = true;
reject(...args);
};
});
deferred.finally(() => {
deferred.notify = null;
deferred.listeners.length = 0;
deferred.lastNotify = null;
});
Object.assign(deferred, deferredHelper);
return deferred;
}

View File

@ -0,0 +1,48 @@
import type { ArgumentTypes } from "../types";
export default class EventListenerBase<Listeners extends {[name: string]: Function}> {
protected listeners: Partial<{
[k in keyof Listeners]: Array<{callback: Listeners[k], once?: true}>
}> = {};
protected listenerResults: Partial<{
[k in keyof Listeners]: ArgumentTypes<Listeners[k]>
}> = {};
constructor(private reuseResults?: true) {
}
public addListener(name: keyof Listeners, callback: Listeners[typeof name], once?: true) {
(this.listeners[name] ?? (this.listeners[name] = [])).push({callback, once});
if(this.listenerResults.hasOwnProperty(name)) {
callback(this.listenerResults[name]);
if(once) {
this.removeListener(name, callback);
}
}
}
public removeListener(name: keyof Listeners, callback: Listeners[typeof name]) {
if(this.listeners[name]) {
this.listeners[name].findAndSplice(l => l.callback == callback);
}
}
protected setListenerResult(name: keyof Listeners, ...args: ArgumentTypes<Listeners[typeof name]>) {
if(this.reuseResults) {
this.listenerResults[name] = args;
}
if(this.listeners[name]) {
this.listeners[name].forEach(listener => {
listener.callback(...args);
if(listener.once) {
this.removeListener(name, listener.callback);
}
});
}
}
}

105
src/helpers/mediaSizes.ts Normal file
View File

@ -0,0 +1,105 @@
import EventListenerBase from "./eventListenerBase";
type Size = {width: number, height: number};
type Sizes = {
regular: Size,
webpage: Size,
album: Size
};
export enum ScreenSize {
mobile,
medium,
large
}
const MOBILE_SIZE = 896;
const MEDIUM_SIZE = 1275;
const LARGE_SIZE = 1680;
class MediaSizes extends EventListenerBase<{
changeScreen: (from: ScreenSize, to: ScreenSize) => void
}> {
private screenSizes: {key: ScreenSize, value: number}[] = [
{key: ScreenSize.mobile, value: MOBILE_SIZE - 1},
{key: ScreenSize.medium, value: MEDIUM_SIZE},
{key: ScreenSize.large, value: LARGE_SIZE}
];
private sizes: {[k in 'desktop' | 'handhelds']: Sizes} = {
handhelds: {
regular: {
width: 293,
height: 293
},
webpage: {
width: 293,
height: 213
},
album: {
width: 293,
height: 0
}
},
desktop: {
regular: {
width: 480,
height: 480
},
webpage: {
width: 480,
height: 400
},
album: {
width: 451,
height: 0
}
}
};
public isMobile = false;
public active: Sizes;
public activeScreen: ScreenSize;
constructor() {
super();
window.addEventListener('resize', this.handleResize);
this.handleResize();
}
private handleResize = () => {
const innerWidth = window.innerWidth;
//this.isMobile = innerWidth <= 720;
let activeScreen = this.screenSizes[0].key;
for(let i = this.screenSizes.length - 1; i >= 0; --i) {
if(this.screenSizes[i].value < innerWidth) {
activeScreen = (this.screenSizes[i + 1] || this.screenSizes[i]).key;
break;
}
}
if(this.activeScreen != activeScreen) {
//console.log('changeScreen', this.activeScreen, activeScreen);
this.setListenerResult('changeScreen', this.activeScreen, activeScreen);
}
this.activeScreen = activeScreen;
this.isMobile = this.activeScreen == ScreenSize.mobile;
this.active = this.isMobile ? this.sizes.handhelds : this.sizes.desktop;
/* if(this.isMobile) {
for(let i in this.active) {
// @ts-ignore
let size = this.active[i];
size.width = innerWidth
}
} */
};
}
const mediaSizes = new MediaSizes();
export default mediaSizes;

View File

@ -22,7 +22,7 @@
{{/ each }}
</head>
<body>
<body class="animation-level-2">
<!--[if IE]>
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="https://browsehappy.com/">upgrade your browser</a> to improve your experience and security.</p>
<![endif]-->
@ -212,7 +212,7 @@
<div class="sidebar-content">
<div id="chats-container">
<div class="folders-tabs-scrollable hide">
<nav class="menu-horizontal no-stripe" id="folders-tabs">
<nav class="menu-horizontal" id="folders-tabs">
<ul>
<li class="rp"><span>All<span class="unread-count"></span><i></i></span></li>
</ul>
@ -237,7 +237,7 @@
</div>
<div class="sidebar-slider-item">
<div class="sidebar-header">
<button class="btn-icon rp tgico-back sidebar-close-button"></button>
<button class="btn-icon tgico-back sidebar-close-button"></button>
<div class="sidebar-header__title">Archived Chats</div>
</div>
<div class="sidebar-content">
@ -248,7 +248,7 @@
</div>
<div class="sidebar-slider-item" id="contacts-container">
<div class="sidebar-header">
<button class="btn-icon rp tgico-back sidebar-close-button"></button>
<button class="btn-icon tgico-back sidebar-close-button"></button>
</div>
<div class="sidebar-content">
<div>
@ -258,7 +258,7 @@
</div>
<div class="sidebar-slider-item new-channel-container">
<div class="sidebar-header">
<button class="btn-icon rp tgico-back sidebar-close-button"></button>
<button class="btn-icon tgico-back sidebar-close-button"></button>
<div class="sidebar-header__title">New Channel</div>
</div>
<div class="sidebar-content">
@ -282,7 +282,7 @@
</div>
<div class="sidebar-slider-item addmembers-container">
<div class="sidebar-header">
<button class="btn-icon rp tgico-back sidebar-close-button"></button>
<button class="btn-icon tgico-back sidebar-close-button"></button>
<div class="sidebar-header__title">Add Members</div>
</div>
<div class="sidebar-content">
@ -291,7 +291,7 @@
</div>
<div class="sidebar-slider-item new-group-container">
<div class="sidebar-header">
<button class="btn-icon rp tgico-back sidebar-close-button"></button>
<button class="btn-icon tgico-back sidebar-close-button"></button>
<div class="sidebar-header__title">New Group</div>
</div>
<div class="sidebar-content">
@ -310,7 +310,7 @@
</div>
<div class="sidebar-slider-item settings-container">
<div class="sidebar-header">
<button class="btn-icon rp tgico-back sidebar-close-button"></button>
<button class="btn-icon tgico-back sidebar-close-button"></button>
<div class="sidebar-header__title">Settings</div>
<div class="btn-icon tgico-more rp btn-menu-toggle">
<div class="btn-menu bottom-left">
@ -336,7 +336,7 @@
</div>
<div class="sidebar-slider-item edit-profile-container">
<div class="sidebar-header">
<button class="btn-icon rp tgico-back sidebar-close-button"></button>
<button class="btn-icon tgico-back sidebar-close-button"></button>
<div class="sidebar-header__title">Edit Profile</div>
</div>
<div class="sidebar-content">
@ -377,7 +377,7 @@
</div>
<div class="sidebar-slider-item chat-folders-container">
<div class="sidebar-header">
<button class="btn-icon rp tgico-back sidebar-close-button"></button>
<button class="btn-icon tgico-back sidebar-close-button"></button>
<div class="sidebar-header__title">Chat Folders</div>
</div>
<div class="sidebar-content">
@ -401,7 +401,7 @@
</div>
<div class="sidebar-slider-item edit-folder-container">
<div class="sidebar-header">
<button class="btn-icon rp tgico-back sidebar-close-button"></button>
<button class="btn-icon tgico-back sidebar-close-button"></button>
<div class="sidebar-header__title"></div>
<div class="btn-icon tgico-check1 btn-confirm rp hide"></div>
<div class="btn-icon btn-menu-toggle rp tgico-more hide">
@ -445,7 +445,7 @@
</div>
<div class="sidebar-slider-item included-chats-container">
<div class="sidebar-header">
<button class="btn-icon rp tgico-back sidebar-close-button"></button>
<button class="btn-icon tgico-back sidebar-close-button"></button>
<div class="sidebar-header__title"></div>
<div class="btn-icon tgico-check1 btn-confirm rp" style="display: none;"></div>
</div>
@ -460,9 +460,10 @@
</div>
</div>
<div class="chat-container main-column" id="column-center">
<canvas id="chat-background-canvas"></canvas>
{{!-- <canvas id="chat-background-canvas"></canvas> --}}
<div class="chat-background"></div>
<div id="topbar" style="display: none;" class="sidebar-header">
<button class="btn-icon rp tgico-back sidebar-close-button"></button>
<button class="btn-icon tgico-back sidebar-close-button"></button>
<div class="chat-info">
<div class="person">
<avatar-element id="im-avatar" dialog="1"></avatar-element>
@ -476,21 +477,23 @@
</div>
</div>
</div>
<button class="btn-primary rp chat-join hide">SUBSCRIBE</button>
<div class="btn-icon rp chat-mute-button hide"></div>
<div class="btn-icon rp tgico-search chat-search-button"></div>
<div class="btn-icon btn-menu-toggle rp tgico-more chat-more-button">
<div class="btn-menu bottom-left">
<div class="btn-menu-item menu-search tgico-search rp">Search</div>
<div class="btn-menu-item menu-mute rp">Mute</div>
<div class="btn-menu-item menu-delete tgico-delete danger rp btn-disabled">Delete and Leave</div>
<div class="chat-utils">
<button class="btn-primary rp chat-join hide">SUBSCRIBE</button>
<div class="btn-icon rp chat-mute-button hide"></div>
<div class="btn-icon rp tgico-search chat-search-button"></div>
<div class="btn-icon btn-menu-toggle rp tgico-more chat-more-button">
<div class="btn-menu bottom-left">
<div class="btn-menu-item menu-search tgico-search rp">Search</div>
<div class="btn-menu-item menu-mute rp">Mute</div>
<div class="btn-menu-item menu-delete tgico-delete danger rp btn-disabled">Delete and Leave</div>
</div>
</div>
</div>
</div>
<div id="bubbles" class="scrolled-down">
<div id="bubbles-inner"></div>
<div id="bubbles-go-down" class="tgico-down z-depth-1 rp" style="display: none;"></div>
</div>
<div id="bubbles-go-down" class="tgico-down btn-corner z-depth-1 rp hide"></div>
<div class="btn-menu" id="bubble-contextmenu">
<div class="btn-menu-item menu-reply tgico-reply rp">Reply</div>
<div class="btn-menu-item menu-edit tgico-edit rp">Edit</div>
@ -587,7 +590,7 @@
<div class="sidebar-content sidebar-slider tabs-container">
<div class="sidebar-slider-item profile-container">
<div class="sidebar-header">
<button class="btn-icon rp tgico sidebar-close-button"></button>
<button class="btn-icon tgico sidebar-close-button"></button>
<div class="sidebar-header__title">Info</div>
<!-- <button class="btn-icon rp tgico-edit sidebar-edit-button"></button> -->
@ -648,33 +651,33 @@
</div>
<div class="sidebar-slider-item sidebar-search chats-container" id="search-private-container">
<div class="sidebar-header">
<button class="btn-icon rp tgico-close sidebar-close-button"></button>
<button class="btn-icon tgico-close sidebar-close-button"></button>
</div>
<div class="chats-container"></div>
</div>
<div class="sidebar-slider-item sidebar-search" id="forward-container">
<div class="sidebar-header">
<button class="btn-icon rp tgico-close sidebar-close-button"></button>
<button class="btn-icon tgico-close sidebar-close-button"></button>
<div class="sidebar-header__title">Forward</div>
</div>
<button class="btn-primary btn-circle btn-icon rp btn-corner tgico-send"></button>
</div>
<div class="sidebar-slider-item sidebar-search chats-container" id="stickers-container">
<div class="sidebar-header">
<button class="btn-icon rp tgico-close sidebar-close-button"></button>
<button class="btn-icon tgico-close sidebar-close-button"></button>
</div>
<div class="sidebar-content"><div class="sticker-sets"></div></div>
</div>
<div class="sidebar-slider-item chats-container" id="poll-results-container">
<div class="sidebar-header">
<button class="btn-icon rp tgico-close sidebar-close-button"></button>
<button class="btn-icon tgico-close sidebar-close-button"></button>
<div class="sidebar-header__title">Results</div>
</div>
<div class="sidebar-content"><div class="poll-results"></div></div>
</div>
<div class="sidebar-slider-item sidebar-search chats-container" id="search-gifs-container">
<div class="sidebar-header">
<button class="btn-icon rp tgico-close sidebar-close-button"></button>
<button class="btn-icon tgico-close sidebar-close-button"></button>
</div>
<div class="sidebar-content"><div class="gifs-masonry"></div></div>
</div>

View File

@ -6,16 +6,17 @@ import appUsersManager, { User } from "./appUsersManager";
import { RichTextProcessor } from "../richtextprocessor";
import { putPreloader, positionMenu, openBtnMenu, parseMenuButtonsTo, attachContextMenuListener } from "../../components/misc";
//import Scrollable from "../../components/scrollable";
import Scrollable from "../../components/scrollable_new";
import Scrollable, { ScrollableX } from "../../components/scrollable_new";
import { logger, LogLevels } from "../logger";
import appChatsManager from "./appChatsManager";
import AvatarElement from "../../components/avatar";
import { PopupButton, PopupPeer } from "../../components/popup";
import { SliderTab } from "../../components/slider";
import appStateManager from "./appStateManager";
import { touchSupport, isSafari } from "../config";
import { touchSupport } from "../config";
import { horizontalMenu } from "../../components/horizontalMenu";
import { ripple } from "../../components/ripple";
import { isSafari } from "../../helpers/userAgent";
type DialogDom = {
avatarEl: AvatarElement,
@ -275,7 +276,7 @@ export class AppArchivedTab implements SliderTab {
public wasFilterID: number;
init() {
this.scroll = new Scrollable(this.container, 'y', 'CLA', this.chatList, 500);
this.scroll = new Scrollable(this.container, 'CLA', this.chatList, 500);
this.scroll.setVirtualContainer(this.chatList);
this.scroll.onScrolledBottom = appDialogsManager.onChatsScroll;
///this.scroll.attachSentinels();
@ -379,7 +380,7 @@ export class AppDialogsManager {
this.folders.menuScrollContainer = this.folders.menu.parentElement;
this.scroll = this._scroll = new Scrollable(this.chatsContainer, 'y', 'CL', this.chatList, 500);
this.scroll = this._scroll = new Scrollable(this.chatsContainer, 'CL', this.chatList, 500);
this.scroll.onScrolledBottom = this.onChatsScroll;
this.scroll.setVirtualContainer(this.chatList);
//this.scroll.attachSentinels();
@ -588,13 +589,15 @@ export class AppDialogsManager {
}
}); */
new Scrollable(this.folders.menuScrollContainer, 'x');
const foldersScrollable = new ScrollableX(this.folders.menuScrollContainer);
this.chatsContainer.prepend(this.folders.menuScrollContainer);
const selectTab = horizontalMenu(this.folders.menu, this.folders.container, (id, tabContent) => {
/* if(id != 0) {
id += 1;
} */
foldersScrollable.scrollIntoView(this.folders.menu.firstElementChild.children[id] as HTMLElement, true, 250);
id = +tabContent.dataset.filterID || 0;
if(this.filterID == id) return;

View File

@ -1,6 +1,6 @@
import { $rootScope } from "../utils";
import apiManager from "../mtproto/mtprotoworker";
import { deferredPromise, CancellablePromise } from "../polyfill";
import { deferredPromise, CancellablePromise } from "../../helpers/cancellablePromise";
import type { DownloadOptions } from "../mtproto/apiFileManager";
import { getFileNameByLocation } from "../bin_utils";
import { InputFile } from "../../layer";

View File

@ -28,7 +28,7 @@ import appStickersManager from './appStickersManager';
import AvatarElement from '../../components/avatar';
import appInlineBotsManager from './AppInlineBotsManager';
import StickyIntersector from '../../components/stickyIntersector';
import { mediaSizes, touchSupport, isAndroid, isApple } from '../config';
import { touchSupport } from '../config';
import animationIntersector from '../../components/animationIntersector';
import PopupStickers from '../../components/popupStickers';
import PopupDatePicker from '../../components/popupDatepicker';
@ -40,6 +40,8 @@ import { InputNotifyPeer, InputPeerNotifySettings } from '../../layer';
import { ChatAudio } from '../../components/chat/audio';
import { ChatContextMenu } from '../../components/chat/contextMenu';
import { ChatSearch } from '../../components/chat/search';
import mediaSizes from '../../helpers/mediaSizes';
import { isAndroid, isApple } from '../../helpers/userAgent';
//console.log('appImManager included33!');
@ -148,7 +150,7 @@ export class AppImManager {
parseMenuButtonsTo(this.menuButtons, this.columnEl.querySelector('.chat-more-button').firstElementChild.children);
this.chatAudio = new ChatAudio();
this.topbar.insertBefore(this.chatAudio.container, this.chatInfo.nextElementSibling);
this.chatInfo.nextElementSibling.prepend(this.chatAudio.container);
apiManager.getUserID().then((id) => {
this.myID = $rootScope.myID = id;
@ -738,7 +740,7 @@ export class AppImManager {
}, {once: true});
newPinned.append(close);
this.topbar.insertBefore(newPinned, this.btnJoin);
this.btnJoin.parentElement.insertBefore(newPinned, this.btnJoin);
this.topbar.classList.add('is-pinned-shown');
if(this.pinnedMessageContainer) {
@ -833,7 +835,7 @@ export class AppImManager {
}
public setScroll() {
this.scrollable = new Scrollable(this.bubblesContainer, 'y', 'IM', this.chatInner, 300);
this.scrollable = new Scrollable(this.bubblesContainer, 'IM', this.chatInner, 300);
/* const getScrollOffset = () => {
//return Math.round(Math.max(300, appPhotosManager.windowH / 1.5));
@ -847,8 +849,6 @@ export class AppImManager {
this.scrollable = new Scrollable(this.bubblesContainer, 'y', 'IM', this.chatInner, getScrollOffset()); */
this.scroll = this.scrollable.container;
this.bubblesContainer.append(this.goDownBtn);
this.scrollable.onScrolledTop = () => this.loadMoreHistory(true);
this.scrollable.onScrolledBottom = () => this.loadMoreHistory(false);
//this.scrollable.attachSentinels(undefined, 300);
@ -1004,7 +1004,8 @@ export class AppImManager {
////console.time('appImManager: pre render start');
if(peerID == 0) {
appSidebarRight.toggleSidebar(false);
this.topbar.style.display = this.chatInput.style.display = this.goDownBtn.style.display = 'none';
this.topbar.style.display = this.chatInput.style.display = 'none';
this.goDownBtn.classList.add('hide');
this.cleanup(true);
this.peerID = $rootScope.selectedPeerID = 0;
$rootScope.$broadcast('peer_changed', this.peerID);
@ -1253,7 +1254,7 @@ export class AppImManager {
else title = appPeersManager.getPeerTitle(this.peerID);
this.titleEl.innerHTML = appSidebarRight.profileElements.name.innerHTML = title;
this.goDownBtn.style.display = '';
this.goDownBtn.classList.remove('hide');
this.setPeerStatus(true);
});
@ -1320,10 +1321,10 @@ export class AppImManager {
//if(scrolledDown) this.scrollable.scrollTop = this.scrollable.scrollHeight;
if(this.messagesQueuePromise && scrolledDown) {
this.scrollable.scrollTo(this.scrollable.scrollHeight - 1, false, true);
this.scrollable.scrollTo(this.scrollable.scrollHeight - 1, 'top', false, true);
this.messagesQueuePromise.then(() => {
this.log('messagesQueuePromise after:', this.chatInner.childElementCount, this.scrollable.scrollHeight);
this.scrollable.scrollTo(this.scrollable.scrollHeight, true, true);
this.scrollable.scrollTo(this.scrollable.scrollHeight, 'top', true, true);
setTimeout(() => {
this.log('messagesQueuePromise afterafter:', this.chatInner.childElementCount, this.scrollable.scrollHeight);
@ -1974,17 +1975,17 @@ export class AppImManager {
quoteTextDiv.append(nameEl);
}
if(webpage.title) {
if(webpage.rTitle) {
let titleDiv = document.createElement('div');
titleDiv.classList.add('title');
titleDiv.innerHTML = RichTextProcessor.wrapRichText(webpage.title);
titleDiv.innerHTML = webpage.rTitle;
quoteTextDiv.append(titleDiv);
}
if(webpage.description) {
if(webpage.rDescription) {
let textDiv = document.createElement('div');
textDiv.classList.add('text');
textDiv.innerHTML = RichTextProcessor.wrapRichText(webpage.description);
textDiv.innerHTML = webpage.rDescription;
quoteTextDiv.append(textDiv);
}

View File

@ -9,11 +9,13 @@ import appDocsManager, {MyDocument} from "./appDocsManager";
import VideoPlayer from "../mediaPlayer";
import { renderImageFromUrl, parseMenuButtonsTo } from "../../components/misc";
import AvatarElement from "../../components/avatar";
import LazyLoadQueue, { LazyLoadQueueBase } from "../../components/lazyLoadQueue";
import { LazyLoadQueueBase } from "../../components/lazyLoadQueue";
import appForward from "../../components/appForward";
import { isSafari, mediaSizes, touchSupport } from "../config";
import { deferredPromise } from "../polyfill";
import { touchSupport } from "../config";
import appMediaPlaybackController from "../../components/appMediaPlaybackController";
import { deferredPromise } from "../../helpers/cancellablePromise";
import mediaSizes from "../../helpers/mediaSizes";
import { isSafari } from "../../helpers/userAgent";
// TODO: масштабирование картинок (не SVG) при ресайзе, и правильный возврат на исходную позицию
// TODO: картинки "обрезаются" если возвращаются или появляются с места, где есть их перекрытие (топбар, поле ввода)
@ -355,7 +357,7 @@ export class AppMediaViewer {
top = rect.top;
}
transform += `translate(${left}px,${top}px) `;
transform += `translate3d(${left}px,${top}px,0) `;
/* if(wasActive) {
left = fromRight === 1 ? appPhotosManager.windowW / 2 : -(containerRect.width + appPhotosManager.windowW / 2);
@ -585,7 +587,7 @@ export class AppMediaViewer {
// чтобы проверить установленную позицию - раскомментировать
//throw '';
mover.style.transform = `translate(${containerRect.left}px,${containerRect.top}px) scale(1,1)`;
mover.style.transform = `translate3d(${containerRect.left}px,${containerRect.top}px,0) scale(1,1)`;
//mover.style.transform = `translate(-50%,-50%) scale(1,1)`;
if(aspecter) {
@ -691,7 +693,7 @@ export class AppMediaViewer {
if(mover.classList.contains('center')) {
//const rect = mover.getBoundingClientRect();
const rect = this.content.container.getBoundingClientRect();
mover.style.transform = `translate(${rect.left}px,${rect.top}px)`;
mover.style.transform = `translate3d(${rect.left}px,${rect.top}px,0)`;
mover.classList.remove('center');
void mover.offsetLeft; // reflow
mover.classList.remove('no-transition');
@ -712,7 +714,7 @@ export class AppMediaViewer {
const rect = mover.getBoundingClientRect();
const newTransform = mover.style.transform.replace(/translate\((.+?),/, (match, p1) => {
const newTransform = mover.style.transform.replace(/translate3d\((.+?),/, (match, p1) => {
const x = toLeft ? -rect.width : windowW;
//const x = toLeft ? -(rect.right + (rect.width / 2)) : windowW / 2;

View File

@ -17,7 +17,7 @@ import serverTimeManager from "../mtproto/serverTimeManager";
//import apiManager from '../mtproto/apiManager';
import apiManager from '../mtproto/mtprotoworker';
import appWebPagesManager from "./appWebPagesManager";
import { CancellablePromise, deferredPromise } from "../polyfill";
import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise";
import appPollsManager from "./appPollsManager";
import searchIndexManager from '../searchIndexManager';
import { Modify } from "../../types";
@ -2471,6 +2471,10 @@ export class AppMessagesManager {
let messageWrapped = '';
if(text) {
if(text.length > 40) {
text = text.substr(0, 35) + '...';
}
let entities = RichTextProcessor.parseEntities(text.replace(/\n/g, ' '), {noLinebreakers: true});
messageWrapped = RichTextProcessor.wrapRichText(text, {

View File

@ -1,10 +1,10 @@
import { calcImageInBox, isObject } from "../utils";
import { bytesFromHex, getFileNameByLocation } from "../bin_utils";
import appDownloadManager from "./appDownloadManager";
import { CancellablePromise } from "../polyfill";
import { isSafari } from "../../helpers/userAgent";
import { FileLocation, InputFileLocation, Photo, PhotoSize } from "../../layer";
import { MyDocument } from "./appDocsManager";
import { CancellablePromise } from "../../helpers/cancellablePromise";
export type MyPhoto = Photo.photo;

View File

@ -1,11 +1,11 @@
//import { logger } from "../polyfill";
import appDialogsManager, { AppArchivedTab, archivedTab } from "./appDialogsManager";
import { $rootScope, findUpTag, findUpClassName } from "../utils";
import { $rootScope, findUpTag, findUpClassName, formatNumber } from "../utils";
import appImManager from "./appImManager";
import AppSearch, { SearchGroup } from "../../components/appSearch";
import { parseMenuButtonsTo } from "../../components/misc";
import appUsersManager from "./appUsersManager";
import Scrollable from "../../components/scrollable_new";
import Scrollable, { ScrollableX } from "../../components/scrollable_new";
import appPeersManager from "../appManagers/appPeersManager";
import AvatarElement from "../../components/avatar";
import AppNewChannelTab from "../../components/sidebarLeft/newChannel";
@ -175,7 +175,7 @@ export class AppSidebarLeft extends SidebarSlider {
peopleContainer.classList.add('search-group-scrollable');
peopleContainer.append(this.searchGroups.people.list);
this.searchGroups.people.container.append(peopleContainer);
let peopleScrollable = new Scrollable(peopleContainer, 'x');
let peopleScrollable = new ScrollableX(peopleContainer);
parseMenuButtonsTo(this.buttons, this.menuEl.children);
parseMenuButtonsTo(this.newButtons, this.newBtnMenu.firstElementChild.children);
@ -251,7 +251,7 @@ export class AppSidebarLeft extends SidebarSlider {
});
$rootScope.$on('dialogs_archived_unread', (e) => {
this.archivedCount.innerText = '' + e.detail.count;
this.archivedCount.innerText = '' + formatNumber(e.detail.count, 1);
});
appUsersManager.getTopPeers().then(peers => {

View File

@ -16,16 +16,19 @@ import { wrapDocument, wrapAudio } from "../../components/wrappers";
import AppSearch, { SearchGroup } from "../../components/appSearch";
import AvatarElement from "../../components/avatar";
import appForward from "../../components/appForward";
import { mediaSizes } from "../config";
import SidebarSlider from "../../components/slider";
import SearchInput from "../../components/searchInput";
import { horizontalMenu } from "../../components/horizontalMenu";
import AppStickersTab from "../../components/sidebarRight/stickers";
import AppPollResultsTab from "../../components/sidebarRight/pollResults";
import AppGifsTab from "../../components/sidebarRight/gifs";
import mediaSizes, { ScreenSize } from "../../helpers/mediaSizes";
import { isSafari } from "../../helpers/userAgent";
const testScroll = false;
const COLUMN_ACTIVE_CLASSNAME = 'is-right-column-shown';
let setText = (text: string, el: HTMLDivElement) => {
window.requestAnimationFrame(() => {
if(el.childElementCount > 1) {
@ -183,7 +186,7 @@ export class AppSidebarRight extends SidebarSlider {
let container = this.profileContentEl.querySelector('.content-container .tabs-container') as HTMLDivElement;
this.profileTabs = this.profileContentEl.querySelector('.profile-tabs');
this.scroll = new Scrollable(this.profileContainer, 'y', 'SR', undefined, 400);
this.scroll = new Scrollable(this.profileContainer, 'SR', undefined, 400);
this.scroll.onScrolledBottom = () => {
if(this.sharedMediaSelected && this.sharedMediaSelected.childElementCount/* && false */) {
this.log('onScrolledBottom will load media');
@ -194,6 +197,10 @@ export class AppSidebarRight extends SidebarSlider {
horizontalMenu(this.profileTabs, container, (id, tabContent) => {
if(this.prevTabID == id) return;
if(this.prevTabID != -1) {
this.onTransitionStart();
}
this.sharedMediaType = this.sharedMediaTypes[id];
this.sharedMediaSelected = tabContent.firstElementChild as HTMLDivElement;
@ -213,6 +220,7 @@ export class AppSidebarRight extends SidebarSlider {
this.prevTabID = id;
}, () => {
this.scroll.onScroll();
this.onTransitionEnd();
});
let sidebarCloseBtn = this.sidebarEl.querySelector('.sidebar-close-button') as HTMLButtonElement;
@ -252,8 +260,31 @@ export class AppSidebarRight extends SidebarSlider {
//let checked = this.profileElements.notificationsCheckbox.checked;
appImManager.mutePeer(this.peerID);
});
mediaSizes.addListener('changeScreen', (from, to) => {
if(from !== undefined && to == ScreenSize.medium) {
this.toggleSidebar(false);
}
});
}
private onTransitionStart = () => {
// Jolly Cobra's // Workaround for scrollable content flickering during animation.
const container = this.scroll.container;
if(container.style.overflowY !== 'hidden') {
const scrollBarWidth = container.offsetWidth - container.clientWidth;
container.style.overflowY = 'hidden';
container.style.paddingRight = `${scrollBarWidth}px`;
}
};
private onTransitionEnd = () => {
// Jolly Cobra's // Workaround for scrollable content flickering during animation.
const container = this.scroll.container;
container.style.overflowY = '';
container.style.paddingRight = '0';
};
public beginSearch() {
this.toggleSidebar(true);
this.searchContainer.classList.add('active');
@ -263,7 +294,7 @@ export class AppSidebarRight extends SidebarSlider {
public toggleSidebar(enable?: boolean) {
/////this.log('sidebarEl', this.sidebarEl, enable, isElementInViewport(this.sidebarEl));
const active = this.sidebarEl.classList.contains('active');
const active = document.body.classList.contains(COLUMN_ACTIVE_CLASSNAME);
let willChange: boolean;
if(enable !== undefined) {
if(enable) {
@ -286,9 +317,15 @@ export class AppSidebarRight extends SidebarSlider {
}
const set = () => {
this.sidebarEl.classList.toggle('active', enable);
document.body.classList.toggle(COLUMN_ACTIVE_CLASSNAME, enable);
};
set();
return new Promise(resolve => {
setTimeout(resolve, 200);
});
//return Promise.resolve();
return new Promise((resolve, reject) => {
const hidden: {element: HTMLDivElement, height: number}[] = [];
const observer = new IntersectionObserver((entries) => {
@ -483,6 +520,12 @@ export class AppSidebarRight extends SidebarSlider {
const needBlurCallback = needBlur ? () => {
//void img.offsetLeft; // reflow
img.style.opacity = '';
if(thumb) {
window.setTimeout(() => {
thumb.remove();
}, 200);
}
} : undefined;
renderImageFromUrl(img, url, needBlurCallback);
}
@ -515,7 +558,7 @@ export class AppSidebarRight extends SidebarSlider {
});
const timeout = setTimeout(() => {
this.log('did not loaded', thumb, media, isDownloaded, sizes);
this.log('didn\'t load', thumb, media, isDownloaded, sizes);
reject();
}, 1e3);
});
@ -780,8 +823,10 @@ export class AppSidebarRight extends SidebarSlider {
const inputFilter = contentToSharedMap[key];
if(!this.historiesStorage[this.peerID] || !this.historiesStorage[this.peerID][inputFilter]) {
const parent = this.sharedMedia[key].parentElement;
if(!parent.querySelector('.preloader')) {
putPreloader(parent, true);
if(!testScroll) {
if(!parent.querySelector('.preloader')) {
putPreloader(parent, true);
}
}
const empty = parent.querySelector('.content-empty');
@ -792,16 +837,14 @@ export class AppSidebarRight extends SidebarSlider {
});
if(testScroll) {
for(let i = 0; i < 30; ++i) {
//div.insertAdjacentHTML('beforeend', `<div style="background-image: url(assets/img/camomile.jpg);"></div>`);
for(let i = 0; i < 1500; ++i) {
let div = document.createElement('div');
div.insertAdjacentHTML('beforeend', `<img class="media-image" src="assets/img/camomile.jpg">`);
div.classList.add('media-item');
div.dataset.id = '' + (i / 3 | 0);
div.innerText = '' + (i / 3 | 0);
//div.innerText = '' + (i / 3 | 0);
this.sharedMedia.contentMedia.append(div);
}
(this.profileTabs.children[1] as HTMLLIElement).click(); // set media
}
(this.profileTabs.firstElementChild.children[1] as HTMLLIElement).click(); // set media

View File

@ -7,7 +7,7 @@
// @ts-ignore
import {BigInteger, SecureRandom} from 'jsbn';
import { InputFileLocation, FileLocation } from '../layer';
import type { InputFileLocation, FileLocation } from '../layer';
/// #if !MTPROTO_WORKER
// @ts-ignore

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
import { convertToArrayBuffer, convertToByteArray } from "../bin_utils";
import { InputCheckPasswordSRP } from "../../layer";
import type { InputCheckPasswordSRP } from "../../layer";
export default abstract class CryptoWorkerMethods {
abstract performTaskWorker<T>(task: string, ...args: any[]): Promise<T>;

View File

@ -1,4 +1,3 @@
import {dT} from '../bin_utils';
import CryptoWorkerMethods from './crypto_methods';
type Task = {
@ -24,7 +23,7 @@ class CryptoWorker extends CryptoWorkerMethods {
constructor() {
super();
console.log(dT(), 'CW constructor');
console.log('CW constructor');
/// #if MTPROTO_WORKER
Promise.all([
@ -60,7 +59,7 @@ class CryptoWorker extends CryptoWorkerMethods {
tmpWorker.onmessage = (e: any) => {
if(!this.webWorker) {
this.webWorker = tmpWorker;
console.info(dT(), 'CW set webWorker');
console.info('CW set webWorker');
this.releasePending();
} else {
this.finalizeTask(e.data.taskID, e.data.result);
@ -76,17 +75,19 @@ class CryptoWorker extends CryptoWorkerMethods {
/// #endif
}
/// #if !MTPROTO_WORKER
private finalizeTask(taskID: number, result: any) {
let deferred = this.awaiting[taskID];
if(deferred !== undefined) {
this.debug && console.log(dT(), 'CW done', deferred.taskName, result);
this.debug && console.log('CW done', deferred.taskName, result);
deferred.resolve(result);
delete this.awaiting[taskID];
}
}
/// #endif
public performTaskWorker<T>(task: string, ...args: any[]) {
this.debug && console.log(dT(), 'CW start', task, args);
this.debug && console.log('CW start', task, args);
/// #if MTPROTO_WORKER
return Promise.resolve<T>(this.utils[task](...args));
@ -109,6 +110,7 @@ class CryptoWorker extends CryptoWorkerMethods {
/// #endif
}
/// #if !MTPROTO_WORKER
private releasePending() {
if(this.webWorker) {
this.pending.forEach(pending => {
@ -118,6 +120,7 @@ class CryptoWorker extends CryptoWorkerMethods {
this.pending.length = 0;
}
}
/// #endif
}
const cryptoWorker = new CryptoWorker();

View File

@ -1,368 +0,0 @@
import {blobConstruct, bytesToBase64, blobSafeMimeType, dataUrlToBlob} from './bin_utils';
import FileManager from './filemanager';
import { logger } from './logger';
class IdbFileStorage {
public dbName = 'cachedFiles';
public dbStoreName = 'files';
public dbVersion = 2;
public openDbPromise: Promise<IDBDatabase>;
public storageIsAvailable = true;
public name = 'IndexedDB';
private log: ReturnType<typeof logger> = logger('IDB');
constructor() {
this.openDatabase(true);
}
public isAvailable() {
return this.storageIsAvailable;
}
public openDatabase(createNew = false): Promise<IDBDatabase> {
if(this.openDbPromise && !createNew) {
return this.openDbPromise;
}
const createObjectStore = (db: IDBDatabase) => {
db.createObjectStore(this.dbStoreName);
};
try {
var request = indexedDB.open(this.dbName, this.dbVersion);
if(!request) {
throw new Error();
}
} catch(error) {
this.log.error('error opening db', error.message)
this.storageIsAvailable = false;
return Promise.reject(error);
}
let finished = false;
setTimeout(() => {
if(!finished) {
request.onerror({type: 'IDB_CREATE_TIMEOUT'} as Event);
}
}, 3000);
return this.openDbPromise = new Promise<IDBDatabase>((resolve, reject) => {
request.onsuccess = (event) => {
finished = true;
const db = request.result;
let calledNew = false;
this.log('Opened');
db.onerror = (error) => {
this.storageIsAvailable = false;
this.log.error('Error creating/accessing IndexedDB database', error);
reject(error);
};
db.onclose = (e) => {
this.log.error('closed:', e);
!calledNew && this.openDatabase();
};
db.onabort = (e) => {
this.log.error('abort:', e);
const transaction = e.target as IDBTransaction;
this.openDatabase(calledNew = true);
if(transaction.onerror) {
transaction.onerror(e);
}
db.close();
};
db.onversionchange = (e) => {
this.log.error('onversionchange, lol?');
};
resolve(db);
};
request.onerror = (event) => {
finished = true;
this.storageIsAvailable = false;
this.log.error('Error creating/accessing IndexedDB database', event);
reject(event);
};
request.onupgradeneeded = (event) => {
finished = true;
this.log.warn('performing idb upgrade from', event.oldVersion, 'to', event.newVersion);
// @ts-ignore
var db = event.target.result as IDBDatabase;
if(event.oldVersion == 1) {
db.deleteObjectStore(this.dbStoreName);
}
createObjectStore(db);
};
});
}
public deleteFile(fileName: string): Promise<void> {
//return Promise.resolve();
return this.openDatabase().then((db) => {
try {
this.log('Delete file: `' + fileName + '`');
var objectStore = db.transaction([this.dbStoreName], 'readwrite')
.objectStore(this.dbStoreName);
var request = objectStore.delete(fileName);
} catch(error) {
return Promise.reject(error);
}
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
this.log.error('deleteFile request not finished!', fileName, request);
resolve();
}, 3000);
request.onsuccess = (event) => {
this.log('deleted file', event);
resolve();
clearTimeout(timeout);
};
request.onerror = (error) => {
reject(error);
clearTimeout(timeout);
};
});
});
}
public saveFile(fileName: string, blob: Blob | Uint8Array): Promise<Blob> {
return Promise.resolve(blobConstruct([blob]));
return this.openDatabase().then((db) => {
if(!(blob instanceof Blob)) {
blob = blobConstruct([blob]) as Blob;
}
this.log('saveFile:', fileName, blob);
const handleError = (error: Error) => {
this.log.error('saveFile transaction error:', fileName, blob, db, error, error && error.name);
if((!error || error.name === 'InvalidStateError')/* && false */) {
setTimeout(() => {
this.saveFile(fileName, blob);
}, 2e3);
} else {
//console.error('IndexedDB saveFile transaction error:', error, error && error.name);
}
};
try {
const transaction = db.transaction([this.dbStoreName], 'readwrite');
transaction.onerror = (e) => {
handleError(transaction.error);
};
transaction.oncomplete = (e) => {
this.log('saveFile transaction complete:', fileName);
};
/* transaction.addEventListener('abort', (e) => {
//handleError();
this.log.error('IndexedDB: saveFile transaction abort!', transaction.error);
}); */
const objectStore = transaction.objectStore(this.dbStoreName);
var request = objectStore.put(blob, fileName);
} catch(error) {
handleError(error);
return blob;
/* this.storageIsAvailable = false;
throw error; */
}
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
this.log.error('saveFile request not finished', fileName, request);
}, 3000);
request.onsuccess = (event) => {
resolve(blob as Blob);
clearTimeout(timeout);
};
request.onerror = (error) => {
reject(error);
clearTimeout(timeout);
};
});
});
}
public saveFileBase64(db: IDBDatabase, fileName: string, blob: Blob | any): Promise<Blob> {
if(this.getBlobSize(blob) > 10 * 1024 * 1024) {
return Promise.reject();
}
if(!(blob instanceof Blob)) {
var safeMimeType = blobSafeMimeType(blob.type || 'image/jpeg');
var address = 'data:' + safeMimeType + ';base64,' + bytesToBase64(blob);
return this.storagePutB64String(db, fileName, address).then(() => {
return blob;
});
}
try {
var reader = new FileReader();
} catch (e) {
this.storageIsAvailable = false;
return Promise.reject();
}
let promise = new Promise<Blob>((resolve, reject) => {
reader.onloadend = () => {
this.storagePutB64String(db, fileName, reader.result as string).then(() => {
resolve(blob);
}, reject);
}
reader.onerror = reject;
});
try {
reader.readAsDataURL(blob);
} catch (e) {
this.storageIsAvailable = false;
return Promise.reject();
}
return promise;
}
public storagePutB64String(db: IDBDatabase, fileName: string, b64string: string) {
try {
var objectStore = db.transaction([this.dbStoreName], 'readwrite')
.objectStore(this.dbStoreName);
var request = objectStore.put(b64string, fileName);
} catch(error) {
this.storageIsAvailable = false;
return Promise.reject(error);
}
return new Promise((resolve, reject) => {
request.onsuccess = function(event) {
resolve();
};
request.onerror = reject;
});
}
public getBlobSize(blob: any) {
return blob.size || blob.byteLength || blob.length;
}
public getFile(fileName: string): Promise<Blob> {
//return Promise.reject();
return this.openDatabase().then((db) => {
this.log('getFile pre:', fileName);
try {
const transaction = db.transaction([this.dbStoreName], 'readonly');
transaction.onabort = (e) => {
this.log.error('getFile transaction onabort?', e);
};
const objectStore = transaction.objectStore(this.dbStoreName);
var request = objectStore.get(fileName);
//this.log.log('IDB getFile:', fileName, request);
} catch(err) {
this.log.error('getFile error:', err, fileName, request, request.error);
}
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
this.log.error('getFile request not finished!', fileName, request);
reject();
}, 3000);
request.onsuccess = function(event) {
const result = request.result;
if(result === undefined) {
reject();
} else if(typeof result === 'string' &&
result.substr(0, 5) === 'data:') {
resolve(dataUrlToBlob(result));
} else {
resolve(result);
}
clearTimeout(timeout);
}
request.onerror = () => {
clearTimeout(timeout);
reject();
};
});
});
}
public getAllKeys(): Promise<Array<string>> {
console.time('getAllEntries');
return this.openDatabase().then((db) => {
var objectStore = db.transaction([this.dbStoreName], 'readonly')
.objectStore(this.dbStoreName);
var request = objectStore.getAllKeys();
return new Promise((resolve, reject) => {
request.onsuccess = function(event) {
// @ts-ignore
var result = event.target.result;
resolve(result);
console.timeEnd('getAllEntries');
}
request.onerror = reject;
});
});
}
public isFileExists(fileName: string): Promise<boolean> {
console.time('isFileExists');
return this.openDatabase().then((db) => {
var objectStore = db.transaction([this.dbStoreName], 'readonly')
.objectStore(this.dbStoreName);
var request = objectStore.openCursor(fileName);
return new Promise((resolve, reject) => {
request.onsuccess = function(event) {
// @ts-ignore
var cursor = event.target.result;
resolve(!!cursor);
console.timeEnd('isFileExists');
}
request.onerror = reject;
});
});
}
public getFileWriter(fileName: string, mimeType: string) {
var fakeWriter = FileManager.getFakeFileWriter(mimeType, (blob) => {
return this.saveFile(fileName, blob);
});
return Promise.resolve(fakeWriter);
}
}
const idbFileStorage = new IdbFileStorage();
(window as any).IdbFileStorage = idbFileStorage;
export default idbFileStorage;

View File

@ -1,5 +0,0 @@
// @ts-ignore
//import LottiePlayer from "lottie-web/build/player/lottie_canvas.min.js";
import LottiePlayer from "lottie-web/build/player/lottie_light.min.js";
(window as any).lottie = LottiePlayer;

View File

@ -1,8 +1,10 @@
import { isApple, mediaSizes, isSafari } from "./config";
import { logger, LogLevels } from "./logger";
import animationIntersector from "../components/animationIntersector";
import apiManager from "./mtproto/mtprotoworker";
import { copy } from "./utils";
import EventListenerBase from "../helpers/eventListenerBase";
import mediaSizes from "../helpers/mediaSizes";
import { isApple, isSafari } from "../helpers/userAgent";
let convert = (value: number) => {
return Math.round(Math.min(Math.max(value, 0), 1) * 255);
@ -21,7 +23,12 @@ type RLottieOptions = {
needUpscale?: true
};
export class RLottiePlayer {
export class RLottiePlayer extends EventListenerBase<{
enterFrame: (frameNo: number) => void,
ready: () => void,
firstFrame: () => void,
cached: () => void
}> {
public static reqId = 0;
public reqId = 0;
@ -34,13 +41,6 @@ export class RLottiePlayer {
public width = 0;
public height = 0;
public listeners: Partial<{
[k in RLottiePlayerListeners]: Array<{callback: (res: any) => void, once?: true}>
}> = {};
public listenerResults: Partial<{
[k in RLottiePlayerListeners]: any
}> = {};
public el: HTMLElement;
public canvas: HTMLCanvasElement;
public context: CanvasRenderingContext2D;
@ -75,6 +75,8 @@ export class RLottiePlayer {
worker: QueryableWorker,
options: RLottieOptions
}) {
super(true);
this.reqId = ++RLottiePlayer['reqId'];
this.el = el;
this.worker = worker;
@ -138,37 +140,6 @@ export class RLottiePlayer {
this.frames = {};
}
public addListener(name: RLottiePlayerListeners, callback: (res?: any) => void, once?: true) {
(this.listeners[name] ?? (this.listeners[name] = [])).push({callback, once});
if(this.listenerResults.hasOwnProperty(name)) {
callback(this.listenerResults[name]);
if(once) {
this.removeListener(name, callback);
}
}
}
public removeListener(name: RLottiePlayerListeners, callback: (res?: any) => void) {
if(this.listeners[name]) {
this.listeners[name].findAndSplice(l => l.callback == callback);
}
}
public setListenerResult(name: RLottiePlayerListeners, value?: any) {
this.listenerResults[name] = value;
if(this.listeners[name]) {
this.listeners[name].forEach(listener => {
listener.callback(value);
if(listener.once) {
this.removeListener(name, listener.callback);
}
});
}
}
public sendQuery(methodName: string, ...args: any[]) {
//console.trace('RLottie sendQuery:', methodName);
this.worker.sendQuery(methodName, this.reqId, ...args);
@ -424,11 +395,12 @@ export class RLottiePlayer {
}
}
class QueryableWorker {
class QueryableWorker extends EventListenerBase<any> {
private worker: Worker;
private listeners: {[name: string]: (...args: any[]) => void} = {};
constructor(url: string, private defaultListener: (data: any) => void = () => {}, onError?: (error: any) => void) {
super();
this.worker = new Worker(url);
if(onError) {
this.worker.onerror = onError;
@ -444,7 +416,7 @@ class QueryableWorker {
return;
} */
this.listeners[event.data.queryMethodListener](...event.data.queryMethodArguments);
this.setListenerResult(event.data.queryMethodListener, ...event.data.queryMethodArguments);
} else {
this.defaultListener.call(this, event.data);
}
@ -459,14 +431,6 @@ class QueryableWorker {
this.worker.terminate();
}
public addListener(name: string, listener: (...args: any[]) => void) {
this.listeners[name] = listener;
}
public removeListener(name: string) {
delete this.listeners[name];
}
public sendQuery(queryMethod: string, ...args: any[]) {
var args = Array.prototype.slice.call(arguments, 1);
if(isSafari) {

View File

@ -3,12 +3,12 @@ import { nextRandomInt, getFileNameByLocation } from "../bin_utils";
import cacheStorage from "../cacheStorage";
import FileManager from "../filemanager";
import apiManager from "./apiManager";
import { deferredPromise, CancellablePromise } from "../polyfill";
import { logger, LogLevels } from "../logger";
import { isSafari } from "../../helpers/userAgent";
import cryptoWorker from "../crypto/cryptoworker";
import { notifySomeone, notifyAll } from "../../helpers/context";
import { InputFileLocation, FileLocation, InputFile, UploadFile } from "../../layer";
import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise";
type Delayed = {
offset: number,

View File

@ -2,9 +2,9 @@ import { isSafari } from '../../helpers/userAgent';
import { logger, LogLevels } from '../logger';
import type { DownloadOptions } from './apiFileManager';
import type { WorkerTaskTemplate } from '../../types';
import { deferredPromise, CancellablePromise } from '../polyfill';
import { notifySomeone } from '../../helpers/context';
import { InputFileLocation, FileLocation, UploadFile } from '../../layer';
import type { InputFileLocation, FileLocation, UploadFile } from '../../layer';
import { CancellablePromise, deferredPromise } from '../../helpers/cancellablePromise';
const log = logger('SW', LogLevels.error/* | LogLevels.debug | LogLevels.log */);
const ctx = self as any as ServiceWorkerGlobalScope;

View File

@ -1,15 +1,14 @@
// just to include
import {secureRandom, CancellablePromise} from '../polyfill';
import {secureRandom} from '../polyfill';
secureRandom;
import apiManager from "./apiManager";
import AppStorage from '../storage';
import cryptoWorker from "../crypto/cryptoworker";
import networkerFactory from "./networkerFactory";
import apiFileManager, { ApiFileManager } from './apiFileManager';
import apiFileManager from './apiFileManager';
import { logger, LogLevels } from '../logger';
import type { ServiceWorkerTask, ServiceWorkerTaskResponse } from './mtproto.service';
import { UploadFile } from '../../layer';
const log = logger('DW', LogLevels.error);

View File

@ -1,4 +1,4 @@
import { isSafari } from "./config";
import { isSafari } from "../helpers/userAgent";
import { logger, LogLevels } from "./logger";
type Result = {
@ -132,7 +132,7 @@ export class OpusDecodeController {
}, isSafari ? undefined : [task.pages.buffer]);
//}, 1e3);
task.timeout = setTimeout(() => {
task.timeout = window.setTimeout(() => {
this.log.error('decode timeout'/* , task */);
this.terminateWorkers(true);

View File

@ -4,70 +4,6 @@ import {SecureRandom} from 'jsbn';
export const secureRandom = new SecureRandom();
export interface CancellablePromise<T> extends Promise<T> {
resolve?: (...args: any[]) => void,
reject?: (...args: any[]) => void,
cancel?: () => void,
notify?: (...args: any[]) => void,
notifyAll?: (...args: any[]) => void,
lastNotify?: any,
listeners?: Array<(...args: any[]) => void>,
addNotifyListener?: (callback: (...args: any[]) => void) => void,
isFulfilled?: boolean,
isRejected?: boolean
}
export function deferredPromise<T>() {
let deferredHelper: any = {
isFulfilled: false,
isRejected: false,
notify: () => {},
notifyAll: (...args: any[]) => {
deferredHelper.lastNotify = args;
deferredHelper.listeners.forEach((callback: any) => callback(...args));
},
lastNotify: undefined,
listeners: [],
addNotifyListener: (callback: (...args: any[]) => void) => {
if(deferredHelper.lastNotify) {
callback(...deferredHelper.lastNotify);
}
deferredHelper.listeners.push(callback);
}
};
let deferred: CancellablePromise<T> = new Promise<T>((resolve, reject) => {
deferredHelper.resolve = (value: T) => {
if(deferred.isFulfilled) return;
deferred.isFulfilled = true;
resolve(value);
};
deferredHelper.reject = (...args: any[]) => {
if(deferred.isRejected) return;
deferred.isRejected = true;
reject(...args);
};
});
deferred.finally(() => {
deferred.notify = null;
deferred.listeners.length = 0;
deferred.lastNotify = null;
});
Object.assign(deferred, deferredHelper);
return deferred;
}
Object.defineProperty(Uint8Array.prototype, 'hex', {
get: function(): string {
return bytesToHex([...this]);

View File

@ -1,5 +1,5 @@
import WebpWorker from 'worker-loader!./webp.worker';
import { CancellablePromise, deferredPromise } from '../polyfill';
import { CancellablePromise, deferredPromise } from '../../helpers/cancellablePromise';
import apiManagerProxy from '../mtproto/mtprotoworker';
export type WebpConvertTask = {

View File

@ -8,7 +8,7 @@ import LottieLoader, { RLottiePlayer } from '../lib/lottieLoader';
import apiManager from '../lib/mtproto/mtprotoworker';
import Page from './page';
import { App } from '../lib/mtproto/mtproto_config';
import { mediaSizes } from '../lib/config';
import mediaSizes from '../helpers/mediaSizes';
let authCode: {
_: string, // 'auth.sentCode'

View File

@ -7,10 +7,10 @@ import LottieLoader, { RLottiePlayer } from '../lib/lottieLoader';
//import passwordManager from '../lib/mtproto/passwordManager';
import apiManager from '../lib/mtproto/mtprotoworker';
import Page from './page';
import { mediaSizes } from '../lib/config';
import passwordManager from '../lib/mtproto/passwordManager';
import { cancelEvent } from '../lib/utils';
import { AccountPassword } from '../layer';
import mediaSizes from '../helpers/mediaSizes';
let onFirstMount = (): Promise<any> => {
let needFrame = 0;

View File

@ -105,7 +105,7 @@ Utility Classes
}
.position-center {
position: absolute;
position: absolute !important;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);

View File

@ -0,0 +1,82 @@
.audio {
position: relative;
padding-left: 67px;
min-height: 58px;
max-width: 244px;
overflow: visible!important;
@include respond-to(handhelds) {
padding-left: 45px;
}
&-toggle, &-download {
border-radius: 50%;
background-color: $color-blue;
font-size: 2.3rem;
align-items: center;
@include respond-to(handhelds) {
font-size: 24px !important;
}
}
&-download {
z-index: 2;
}
&-waveform {
height: 23px;
//overflow: visible!important;
rect {
//overflow: visible!important;
fill: #CBCBCB;
&.active {
fill: $color-blue;
}
}
}
&-title {
font-size: 1rem;
color: #000;
user-select: none;
}
&-time, &-subtitle {
font-size: 14px;
color: $color-gray;
margin-top: 3px;
margin-left: -1px;
user-select: none;
@include respond-to(handhelds) {
margin-top: 1px;
font-size: 12px;
}
}
&-title, &:not(.audio-show-progress) &-subtitle {
white-space: nowrap;
overflow: hidden;
max-width: 100%;
text-overflow: ellipsis;
}
@include respond-to(handhelds) {
&-download {
/* background: transparent; */
margin-left: 2px;
margin-top: 1px;
}
&.is-voice {
.audio-download {
margin: 0;
}
}
}
}

View File

@ -0,0 +1,59 @@
avatar-element {
color: #fff;
width: 54px;
height: 54px;
line-height: 54px;
border-radius: 50%;
background-color: $color-blue;
text-align: center;
font-size: 1.25em;
/* overflow: hidden; */
position: relative;
user-select: none;
@include respond-to(handhelds) {
font-size: 14px;
}
/* kostil */
display: flex;
align-items: center;
justify-content: center;
img {
width: 100%;
height: 100%;
border-radius: inherit;
user-select: none;
&.fade-in {
animation: fadeIn .2s ease forwards;
}
}
&[class*=" tgico-"] {
line-height: 52px;
font-size: 28px;
}
path {
fill: white;
}
&.is-online:after {
position: absolute;
content: " ";
display: block;
border-radius: 50%;
border: 2px solid white;
background-color: #0ac630;
left: 74%;
top: 73%;
width: 14px;
height: 14px;
}
&.tgico-avatar_deletedaccount {
font-size: 3rem;
}
}

Some files were not shown because too many files have changed in this diff Show More