diff --git a/.DS_Store b/.DS_Store index e96bc568..4f258411 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/package-lock.json b/package-lock.json index eca1e2e1..3e931485 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4926,13 +4926,13 @@ } }, "globule": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", - "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.1.tgz", + "integrity": "sha512-OVyWOHgw29yosRHCHo7NncwR1hW5ew0W/UrvtwvjefVJeQ26q4/8r8FmPsSF1hJ93IgWkyv16pCTz6WblMzm/g==", "dev": true, "requires": { "glob": "~7.1.1", - "lodash": "~4.17.10", + "lodash": "~4.17.12", "minimatch": "~3.0.2" } }, @@ -5555,13 +5555,10 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", @@ -6247,9 +6244,9 @@ } }, "js-base64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", - "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz", + "integrity": "sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ==", "dev": true }, "js-sha3": { @@ -7088,9 +7085,9 @@ } }, "node-sass": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.0.tgz", - "integrity": "sha512-W1XBrvoJ1dy7VsvTAS5q1V45lREbTlZQqFbiHb3R3OTTCma0XBtuG6xZ6Z4506nR4lmHPTqVRwxT6KgtWC97CA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.1.tgz", + "integrity": "sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw==", "dev": true, "requires": { "async-foreach": "^0.1.3", @@ -7191,9 +7188,9 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "npm": { - "version": "6.13.4", - "resolved": "https://registry.npmjs.org/npm/-/npm-6.13.4.tgz", - "integrity": "sha512-vTcUL4SCg3AzwInWTbqg1OIaOXlzKSS8Mb8kc5avwrJpcvevDA5J9BhYSuei+fNs3pwOp4lzA5x2FVDXACvoXA==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.1.tgz", + "integrity": "sha512-2hi3UF7g5VL8VKm46Bx5GAW28DPb8BJZbj2uONMBMhY8XkJ56lSmHJNFcjTQr7KHZqWqiBT5BugaQEy+Y/4T2g==", "dev": true, "requires": { "JSONStream": "^1.3.5", @@ -7202,12 +7199,12 @@ "ansistyles": "~0.1.3", "aproba": "^2.0.0", "archy": "~1.0.0", - "bin-links": "^1.1.6", + "bin-links": "^1.1.7", "bluebird": "^3.5.5", "byte-size": "^5.0.1", "cacache": "^12.0.3", "call-limit": "^1.1.1", - "chownr": "^1.1.3", + "chownr": "^1.1.4", "ci-info": "^2.0.0", "cli-columns": "^3.1.2", "cli-table3": "^0.5.1", @@ -7227,7 +7224,7 @@ "glob": "^7.1.4", "graceful-fs": "^4.2.3", "has-unicode": "~2.0.1", - "hosted-git-info": "^2.8.5", + "hosted-git-info": "^2.8.7", "iferr": "^1.0.2", "imurmurhash": "*", "infer-owner": "^1.0.4", @@ -7245,7 +7242,7 @@ "libnpmorg": "^1.0.1", "libnpmsearch": "^2.0.2", "libnpmteam": "^1.0.2", - "libnpx": "^10.2.0", + "libnpx": "^10.2.2", "lock-verify": "^2.1.0", "lockfile": "^1.0.4", "lodash._baseindexof": "*", @@ -7264,7 +7261,7 @@ "mississippi": "^3.0.0", "mkdirp": "~0.5.1", "move-concurrently": "^1.0.1", - "node-gyp": "^5.0.5", + "node-gyp": "^5.0.7", "nopt": "~4.0.1", "normalize-package-data": "^2.5.0", "npm-audit-report": "^1.3.2", @@ -7272,16 +7269,16 @@ "npm-install-checks": "^3.0.2", "npm-lifecycle": "^3.1.4", "npm-package-arg": "^6.1.1", - "npm-packlist": "^1.4.7", + "npm-packlist": "^1.4.8", "npm-pick-manifest": "^3.0.2", "npm-profile": "^4.0.2", - "npm-registry-fetch": "^4.0.2", + "npm-registry-fetch": "^4.0.3", "npm-user-validate": "~1.0.0", "npmlog": "~4.1.2", "once": "~1.4.0", "opener": "^1.5.1", "osenv": "^0.1.5", - "pacote": "^9.5.11", + "pacote": "^9.5.12", "path-is-inside": "~1.0.2", "promise-inflight": "~1.0.1", "qrcode-terminal": "^0.12.0", @@ -7292,7 +7289,7 @@ "read-installed": "~4.0.3", "read-package-json": "^2.1.1", "read-package-tree": "^5.3.1", - "readable-stream": "^3.4.0", + "readable-stream": "^3.6.0", "readdir-scoped-modules": "^1.1.0", "request": "^2.88.0", "retry": "^0.12.0", @@ -7484,7 +7481,7 @@ } }, "bin-links": { - "version": "1.1.6", + "version": "1.1.7", "bundled": true, "dev": true, "requires": { @@ -7597,7 +7594,7 @@ } }, "chownr": { - "version": "1.1.3", + "version": "1.1.4", "bundled": true, "dev": true }, @@ -8037,7 +8034,7 @@ } }, "env-paths": { - "version": "1.0.0", + "version": "2.2.0", "bundled": true, "dev": true }, @@ -8381,7 +8378,7 @@ } }, "get-caller-file": { - "version": "1.0.2", + "version": "1.0.3", "bundled": true, "dev": true }, @@ -8490,7 +8487,7 @@ "dev": true }, "hosted-git-info": { - "version": "2.8.5", + "version": "2.8.7", "bundled": true, "dev": true }, @@ -8606,7 +8603,7 @@ } }, "invert-kv": { - "version": "1.0.0", + "version": "2.0.0", "bundled": true, "dev": true }, @@ -8795,11 +8792,11 @@ "dev": true }, "lcid": { - "version": "1.0.0", + "version": "2.0.0", "bundled": true, "dev": true, "requires": { - "invert-kv": "^1.0.0" + "invert-kv": "^2.0.0" } }, "libcipm": { @@ -8972,7 +8969,7 @@ } }, "libnpx": { - "version": "10.2.0", + "version": "10.2.2", "bundled": true, "dev": true, "requires": { @@ -9123,17 +9120,34 @@ "ssri": "^6.0.0" } }, + "map-age-cleaner": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, "meant": { "version": "1.0.1", "bundled": true, "dev": true }, "mem": { - "version": "1.1.0", + "version": "4.3.0", "bundled": true, "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "bundled": true, + "dev": true + } } }, "mime-db": { @@ -9149,11 +9163,6 @@ "mime-db": "~1.35.0" } }, - "mimic-fn": { - "version": "1.2.0", - "bundled": true, - "dev": true - }, "minimatch": { "version": "3.0.4", "bundled": true, @@ -9241,6 +9250,11 @@ "bundled": true, "dev": true }, + "nice-try": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, "node-fetch-npm": { "version": "2.0.2", "bundled": true, @@ -9252,36 +9266,21 @@ } }, "node-gyp": { - "version": "5.0.5", + "version": "5.0.7", "bundled": true, "dev": true, "requires": { - "env-paths": "^1.0.0", - "glob": "^7.0.3", - "graceful-fs": "^4.1.2", - "mkdirp": "^0.5.0", - "nopt": "2 || 3", - "npmlog": "0 || 1 || 2 || 3 || 4", - "request": "^2.87.0", - "rimraf": "2", - "semver": "~5.3.0", + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.2", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "npmlog": "^4.1.2", + "request": "^2.88.0", + "rimraf": "^2.6.3", + "semver": "^5.7.1", "tar": "^4.4.12", - "which": "1" - }, - "dependencies": { - "nopt": { - "version": "3.0.6", - "bundled": true, - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "semver": { - "version": "5.3.0", - "bundled": true, - "dev": true - } + "which": "^1.3.1" } }, "nopt": { @@ -9381,12 +9380,13 @@ } }, "npm-packlist": { - "version": "1.4.7", + "version": "1.4.8", "bundled": true, "dev": true, "requires": { "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" } }, "npm-pick-manifest": { @@ -9410,7 +9410,7 @@ } }, "npm-registry-fetch": { - "version": "4.0.2", + "version": "4.0.3", "bundled": true, "dev": true, "requires": { @@ -9502,13 +9502,41 @@ "dev": true }, "os-locale": { - "version": "2.1.0", + "version": "3.1.0", "bundled": true, "dev": true, "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "bundled": true, + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + } } }, "os-tmpdir": { @@ -9525,11 +9553,21 @@ "os-tmpdir": "^1.0.0" } }, + "p-defer": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, "p-finally": { "version": "1.0.0", "bundled": true, "dev": true }, + "p-is-promise": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, "p-limit": { "version": "1.2.0", "bundled": true, @@ -9563,7 +9601,7 @@ } }, "pacote": { - "version": "9.5.11", + "version": "9.5.12", "bundled": true, "dev": true, "requires": { @@ -9877,7 +9915,7 @@ } }, "readable-stream": { - "version": "3.4.0", + "version": "3.6.0", "bundled": true, "dev": true, "requires": { @@ -10261,11 +10299,18 @@ } }, "string_decoder": { - "version": "1.2.0", + "version": "1.3.0", "bundled": true, "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "bundled": true, + "dev": true + } } }, "stringify-package": { @@ -10646,7 +10691,7 @@ "dev": true }, "yargs": { - "version": "11.0.0", + "version": "11.1.1", "bundled": true, "dev": true, "requires": { @@ -10654,7 +10699,7 @@ "decamelize": "^1.1.1", "find-up": "^2.1.0", "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", + "os-locale": "^3.1.0", "require-directory": "^2.1.1", "require-main-filename": "^1.0.1", "set-blocking": "^2.0.0", diff --git a/package.json b/package.json index b726d817..2d840f7a 100644 --- a/package.json +++ b/package.json @@ -42,8 +42,8 @@ "jest": "^24.9.0", "leemon": "^6.2.0", "lottie-web": "^5.6.4", - "node-sass": "^4.13.0", - "npm": "^6.13.4", + "node-sass": "^4.13.1", + "npm": "^6.14.1", "offscreen-canvas": "^0.1.1", "on-build-webpack": "^0.1.0", "overlayscrollbars": "^1.10.0", diff --git a/src/components/misc.ts b/src/components/misc.ts index 11cce135..968be45a 100644 --- a/src/components/misc.ts +++ b/src/components/misc.ts @@ -52,11 +52,23 @@ export function ripple(elem: Element) { } } -export function putPreloader(elem: Element) { +export function putPreloader(elem: Element, returnDiv = false) { const html = ` `; + + if(returnDiv) { + let div = document.createElement('div'); + div.classList.add('preloader'); + div.innerHTML = html; + + if(elem) { + elem.appendChild(div); + } + + return div; + } elem.innerHTML += html; } @@ -76,18 +88,19 @@ export function horizontalMenu(tabs: HTMLUListElement, content: HTMLDivElement, ///////console.log('tabs click:', target); - if(!target || target.classList.contains('active')) return false; + if(!target) return false; + + let id = whichChild(target); + let tabContent = content.children[id] as HTMLDivElement; + if(onClick) onClick(id, tabContent); + if(target.classList.contains('active') || id == prevId) { + return false; + } let prev = tabs.querySelector('li.active') as HTMLLIElement; prev && prev.classList.remove('active'); target.classList.add('active'); - - let id = whichChild(target); - - if(id == prevId) return false; - - let tabContent = content.children[id] as HTMLDivElement; tabContent.classList.add('active'); /////console.log('mambo rap', prevId, id); @@ -139,7 +152,6 @@ export function horizontalMenu(tabs: HTMLUListElement, content: HTMLDivElement, if(onTransitionEnd) onTransitionEnd(); }, 200); - if(onClick) onClick(id, tabContent); prevTabContent = tabContent; }); } diff --git a/src/components/scrollable.ts b/src/components/scrollable.ts index 3b77063d..208d4044 100644 --- a/src/components/scrollable.ts +++ b/src/components/scrollable.ts @@ -32,12 +32,13 @@ export default class Scrollable { public type: string; public side: string; + public translate: string; public scrollType: string; public scrollSide: string; public clientAxis: string; - public scrollSize = -1; - public size = 0; + public scrollSize = -1; // it will be scrollHeight + public size = 0; // it will be outerHeight of container (not scrollHeight) public thumbSize = 0; public hiddenElements: { @@ -57,11 +58,12 @@ export default class Scrollable { public onAddedBottom: () => void = null; public onScrolledTop: () => void = null; public onScrolledBottom: () => void = null; + public onScrolledTopFired = false; + public onScrolledBottomFired = false; public topObserver: IntersectionObserver; public bottomObserver: IntersectionObserver; - public splitObserver: IntersectionObserver; public splitMeasureTop: Promise<{element: Element, height: number}[]> = null; public splitMeasureBottom: Scrollable['splitMeasureTop'] = null; public splitMeasureAdd: Promise = null; @@ -86,37 +88,17 @@ export default class Scrollable { private log: ReturnType; private debug = false; - constructor(public el: HTMLDivElement, x = false, y = true, public splitOffset = 300, logPrefix = '') { + constructor(public el: HTMLDivElement, x = false, y = true, public splitOffset = 300, logPrefix = '', public appendTo = el, public onScrollOffset = splitOffset) { this.container = document.createElement('div'); this.container.classList.add('scrollable'); this.log = logger('SCROLL' + (logPrefix ? '-' + logPrefix : '')); - let arr = []; - for(let i = 0.001; i < 1; i += 0.001) arr.push(i); - this.topObserver = new IntersectionObserver(entries => { - let entry = entries[entries.length - 1]; - - //console.log('top intersection:', entries, entry.isIntersecting, entry.intersectionRatio > 0); - if(entry.isIntersecting) { - if(this.onScrolledTop) this.onScrolledTop(); - } - // console.log('top intersection end'); - }, {root: this.el}); - - this.bottomObserver = new IntersectionObserver(entries => { - let entry = entries[entries.length - 1]; - - //console.log('bottom intersection:', entries, entry, entry.isIntersecting, entry.intersectionRatio > 0); - if(entry.isIntersecting) { - if(this.onScrolledBottom) this.onScrolledBottom(); - } - }, {root: this.el}); - if(x) { this.container.classList.add('scrollable-x'); this.type = 'width'; this.side = 'left'; + this.translate = 'translateX'; this.scrollType = 'scrollWidth'; this.scrollSide = 'scrollLeft'; this.clientAxis = 'clientX'; @@ -141,6 +123,7 @@ export default class Scrollable { this.container.classList.add('scrollable-y'); this.type = 'height'; this.side = 'top'; + this.translate = 'translateY'; this.scrollType = 'scrollHeight'; this.scrollSide = 'scrollTop'; this.clientAxis = 'clientY'; @@ -181,16 +164,14 @@ export default class Scrollable { window.addEventListener('resize', () => { //this.resize.bind(this); this.onScroll(); + this.resize(); }); this.paddingTopDiv = document.createElement('div'); this.paddingTopDiv.classList.add('scroll-padding'); this.paddingBottomDiv = document.createElement('div'); this.paddingBottomDiv.classList.add('scroll-padding'); - - this.topObserver.observe(this.paddingTopDiv); - this.bottomObserver.observe(this.paddingBottomDiv); - + this.container.addEventListener('scroll', this.onScroll.bind(this)); Array.from(el.children).forEach(c => this.container.append(c)); @@ -280,181 +261,9 @@ export default class Scrollable { }); } - public detachByPrevScroll(child: Element, prevScrollTop: number, needHeight = 0) { - if(this.splitMeasureBottom) fastdom.clear(this.splitMeasureBottom); - if(this.splitMutateBottom) fastdom.clear(this.splitMutateBottom); - - let attachToTop = this.paddings.up < prevScrollTop; - - this.splitMeasureBottom = fastdom.measure(() => { - let sliced: {element: Element, height: number}[] = []; - - do { - if(needHeight > 0) { - needHeight -= child.scrollHeight; - } else { - sliced.push({element: child, height: child.scrollHeight}); - } - } while(child = child.nextElementSibling); - return sliced; - }); - - return this.splitMeasureBottom.then(sliced => { - if(this.splitMutateBottom) fastdom.clear(this.splitMutateBottom); - - return this.splitMutateBottom = fastdom.mutate(() => { - sliced.forEachReverse((child) => { - let {element, height} = child; - if(!this.splitUp.contains(element)) return; - - this.paddings.down += height; - this.hiddenElements.down.unshift(child); - this.splitUp.removeChild(element); - //element.parentElement.removeChild(element); - }); - - this.log('sliced down', sliced); - this.paddingBottomDiv.style.height = this.paddings.down + 'px'; - }); - }); - } - - public splitObserve(entries: IntersectionObserverEntry[]) { - let sorted: { - intersecting: { - top?: IntersectionObserverEntry, - bottom?: IntersectionObserverEntry - }, - notIntersecting: { - top?: IntersectionObserverEntry, - bottom?: IntersectionObserverEntry - } - } = { - intersecting: {}, - notIntersecting: {} - }; - - for(let entry of entries) { // there may be duplicates (1st - not intersecting, 2nd - intersecting) - //console.log('onscroll entry 1', entry.target, entry.isIntersecting, entry); - if(!entry.target.parentElement || !entry.rootBounds) continue; - - if(!entry.isIntersecting) { - let isTop = entry.boundingClientRect.top <= 0; - let isBottom = entry.rootBounds.height <= entry.boundingClientRect.top; - //console.log('onscroll entry notIntersecting', isTop, isBottom, entry.target, entry); - - if(isTop) { - sorted.notIntersecting.top = entry; - } else if(isBottom && !sorted.notIntersecting.bottom) { - sorted.notIntersecting.bottom = entry; - } - - //console.log('splitObserver', entry, entry.target, isTop); - } else { - let isTop = entry.boundingClientRect.top <= entry.rootBounds.top; - let isBottom = entry.boundingClientRect.bottom >= entry.rootBounds.bottom; - - if(isTop) { - sorted.intersecting.top = entry; - } else if(isBottom && !sorted.intersecting.bottom) { - sorted.intersecting.bottom = entry; - } - - // if(isTop) { - // this.onTopIntersection(entry.boundingClientRect.height); - // } else if(isBottom) { - // this.onTopIntersection(entry.boundingClientRect.height); - // } - } - } - - console.log('splitObserve', entries, sorted); - - let needHeight = this.splitOffset; - let isOutOfView: boolean; - let entry: IntersectionObserverEntry; - if(entry = sorted.notIntersecting.top) { // scrolled bottom - let child = entry.target; - - let diff = entry.boundingClientRect.bottom + needHeight; - if(diff < 0) { // maybe need <=, means out of view - if(!(child = child.nextElementSibling)) { - this.detachTop(this.splitUp.lastElementChild, 0); - } else { - if(this.splitMeasureRemoveBad) fastdom.clear(this.splitMeasureRemoveBad); - this.splitMeasureRemoveBad = fastdom.measure(() => { - do { - diff += child.scrollHeight; - } while(diff < 0 && (child = child.nextElementSibling)); - - return child || this.splitUp.lastElementChild; - }); - - this.splitMeasureRemoveBad.then(child => { - this.detachTop(child, 0); - }); - } - } else { - this.detachTop(child, needHeight); - } - } - - if(entry = sorted.notIntersecting.bottom) { // scrolled top - isOutOfView = (entry.boundingClientRect.top - needHeight) >= entry.rootBounds.height; - this.detachBottom(entry.target, isOutOfView ? 0 : needHeight); - } - - if(entry = sorted.intersecting.top) { // scrolling top - let needHeight = this.splitOffset; - - let child = entry.target; - if(this.splitMeasureAdd) fastdom.clear(this.splitMeasureAdd); - this.splitMeasureAdd = fastdom.measure(() => { - while(child = child.previousElementSibling) { - needHeight -= child.scrollHeight; - } - - return needHeight; - }); - - this.splitMeasureAdd.then(needHeight => { - this.onTopIntersection(needHeight); - }); - } - - if(entry = sorted.intersecting.bottom) { // scrolling bottom - let needHeight = this.splitOffset; - - let child = entry.target; - if(this.splitMeasureAdd) fastdom.clear(this.splitMeasureAdd); - this.splitMeasureAdd = fastdom.measure(() => { - while(child = child.nextElementSibling) { - needHeight -= child.scrollHeight; - } - - return needHeight; - }); - - this.splitMeasureAdd.then(needHeight => { - this.onBottomIntersection(needHeight); - }); - } - } - - public async resize() { + public resize() { //console.time('scroll resize'); - - await fastdom.measure(() => { - // @ts-ignore - this.scrollSize = this.container[this.scrollType]; - - let rect = this.container.getBoundingClientRect(); - - // @ts-ignore - this.size = rect[this.type]; - }); - - await fastdom.mutate(() => { + fastdom.mutate(() => { if(!this.size || this.size == this.scrollSize) { this.thumbSize = 0; @@ -480,7 +289,7 @@ export default class Scrollable { //console.log('onresize', thumb.style[type], thumbHeight, height); } - public async setVirtualContainer(el?: HTMLElement) { + public setVirtualContainer(el?: HTMLElement) { this.splitUp = el; this.hiddenElements.up.length = this.hiddenElements.down.length = 0; @@ -494,12 +303,6 @@ export default class Scrollable { }); } - /* if(this.splitObserver) { - this.splitObserver.disconnect(); - } - - this.splitObserver = new IntersectionObserver((entries) => this.splitObserve(entries), {root: this.el}); */ - this.log('setVirtualContainer:', el, this); this.getScrollTopOffset(); @@ -509,6 +312,9 @@ export default class Scrollable { el.parentElement.insertBefore(this.paddingTopDiv, el); el.parentNode.insertBefore(this.paddingBottomDiv, el.nextSibling); }); + } else { + this.paddingTopDiv.remove(); + this.paddingBottomDiv.remove(); } } @@ -529,33 +335,66 @@ export default class Scrollable { public onScroll() { if(this.onScrollMeasure) fastdom.clear(this.onScrollMeasure); this.onScrollMeasure = fastdom.measure(() => { + // @ts-ignore quick brown fix + this.size = this.parentElement[this.scrollType]; + // @ts-ignore - if(this.container[this.scrollType] != this.scrollSize || this.thumbSize == 0) { + let scrollSize = this.container[this.scrollType]; + if(scrollSize != this.scrollSize || this.thumbSize == 0) { this.resize(); } - + this.scrollSize = scrollSize; + // @ts-ignore - let value = this.container[this.scrollSide] / (this.scrollSize - this.size) * 100; - let maxValue = 100 - (this.thumbSize / this.size * 100); + let scrollPos = this.container[this.scrollSide]; + + // let value = scrollPos / (this.scrollSize - this.size) * 100; + // let maxValue = 100 - (this.thumbSize / this.size * 100); + let value = scrollPos / (this.scrollSize - this.size) * this.size; + let maxValue = this.size - this.thumbSize; + //this.log(scrollPos, this.scrollSize, this.size, value, scrollPos / (this.scrollSize - this.size) * this.size); let ret = {value, maxValue}; + + let scrollTop = scrollPos - this.scrollTopOffset; + let maxScrollTop = this.scrollSize - this.scrollTopOffset - this.size; + + if(this.onScrolledBottom) { + if(!this.hiddenElements.down.length && (maxScrollTop - scrollTop) <= this.onScrollOffset) { + if(!this.onScrolledBottomFired) { + this.onScrolledBottomFired = true; + this.onScrolledBottom(); + } + } else { + this.onScrolledBottomFired = false; + } + } + + if(this.onScrolledTop) { + //this.log('onScrolledTop:', scrollTop, this.onScrollOffset); + if(!this.hiddenElements.up.length && scrollTop <= this.onScrollOffset) { + if(!this.onScrolledTopFired) { + this.onScrolledTopFired = true; + this.onScrolledTop(); + } + } else { + this.onScrolledTopFired = false; + } + } if(!this.splitUp) { return ret; } let perf = performance.now(); - let scrollTop = this.scrollTop - this.scrollTopOffset; - let outerHeight = this.parentElement.scrollHeight; - let maxScrollTop = this.scrollHeight - this.scrollTopOffset - outerHeight; if(scrollTop < 0) scrollTop = 0; else if(scrollTop > maxScrollTop) scrollTop = maxScrollTop; let toBottom = scrollTop > this.lastScrollTop; let visibleFrom = /* scrollTop < this.paddings.up ? scrollTop : */scrollTop - this.paddings.up; - let visibleUntil = visibleFrom + outerHeight; + let visibleUntil = visibleFrom + this.size; let sum = 0; let firstVisibleElement: Element; @@ -619,10 +458,6 @@ export default class Scrollable { this.onBottomIntersection(needHeight); return needHeight; }); - - /* this.splitMeasureAdd.then(needHeight => { - this.onBottomIntersection(needHeight); - }); */ } else if(length) { // scrolled manually or safari if(this.debug) { this.log.warn('will detach all of top', length, this.splitUp.childElementCount, maxScrollTop, this.paddings, this.lastScrollTop); @@ -652,10 +487,6 @@ export default class Scrollable { this.onTopIntersection(needHeight); return needHeight; }); - - /* this.splitMeasureAdd.then(needHeight => { - this.onTopIntersection(needHeight); - }); */ } else if(length) { // scrolled manually or safari if(this.debug) { this.log.warn('will detach all of bottom', length, this.splitUp.childElementCount, maxScrollTop, this.paddings, this.lastScrollTop); @@ -681,7 +512,8 @@ export default class Scrollable { this.onScrollMeasure.then(({value, maxValue}) => { fastdom.mutate(() => { // @ts-ignore - this.thumb.style[this.side] = (value >= maxValue ? maxValue : value) + '%'; + //this.thumb.style[this.side] = (value >= maxValue ? maxValue : value) + '%'; + this.thumb.style.transform = this.translate + '(' + (value >= maxValue ? maxValue : value) + 'px)'; }); }); @@ -691,7 +523,7 @@ export default class Scrollable { public onManualScrollTop(scrollTop: number, needHeight: number, maxScrollTop: number) { //if(this.splitMutateRemoveBad) fastdom.clear(this.splitMutateRemoveBad); this.splitMutateRemoveBad = fastdom.mutate(() => { - let h = maxScrollTop - (scrollTop + outerHeight); + let h = maxScrollTop - (scrollTop + this.size); while(this.paddings.down < h && this.paddings.up) { let child = this.hiddenElements.up.pop(); @@ -705,11 +537,8 @@ export default class Scrollable { } this.paddingBottomDiv.style.height = this.paddings.down + 'px'; - this.onTopIntersection((outerHeight * 2) + (needHeight * 2)); + this.onTopIntersection((this.size * 2) + (needHeight * 2)); }); - - /* this.splitMutateRemoveBad.then(() => { - }); */ } public onManualScrollBottom(scrollTop: number, needHeight: number) { @@ -729,11 +558,8 @@ export default class Scrollable { } this.paddingTopDiv.style.height = this.paddings.up + 'px'; - this.onBottomIntersection(outerHeight + (needHeight * 2)); + this.onBottomIntersection(this.size + (needHeight * 2)); }); - - /* this.splitMutateRemoveBad.then(() => { - }); */ } public onTopIntersection(needHeight: number) { @@ -813,7 +639,7 @@ export default class Scrollable { }); if(this.hiddenElements.up.length) { - fastdom.mutate(() => { + /* fastdom.mutate(() => { this.splitUp.append(...smth); }).then(() => { return fastdom.measure(() => { @@ -839,13 +665,26 @@ export default class Scrollable { this.onScroll(); }); + }); */ + this.splitUp.prepend(...smth); + smth.forEachReverse(node => { + let height = node.scrollHeight; + this.log('will append element to up hidden', node, height); + this.paddings.up += height; + this.hiddenElements.up.unshift({ + element: node, + height: height + }); + node.parentElement.removeChild(node); }); + this.paddingTopDiv.style.height = this.paddings.up + 'px'; + this.onScroll(); } else { this.splitUp.prepend(...smth); this.onScroll(); } } else { - this.container.prepend(...smth); + this.appendTo.prepend(...smth); this.onScroll(); } @@ -891,7 +730,7 @@ export default class Scrollable { this.onScroll(); } } else { - this.container.append(...smth); + this.appendTo.append(...smth); this.onScroll(); } @@ -914,17 +753,14 @@ export default class Scrollable { } } - let index = this.hiddenElements.up.findIndex(c => c.element == element); - let child: {element: Element, height: number}; + let child = this.hiddenElements.up.findAndSplice(c => c.element == element); let foundUp = false; - if(index !== -1) { - child = this.hiddenElements.up.splice(index, 1)[0]; + if(child) { this.paddings.up -= child.height; foundUp = true; } else { - index = this.hiddenElements.down.findIndex(c => c.element == element); - if(index !== -1) { - child = this.hiddenElements.down.splice(index, 1)[0]; + child = this.hiddenElements.down.findAndSplice(c => c.element == element); + if(child) { this.paddings.down -= child.height; } } @@ -943,7 +779,7 @@ export default class Scrollable { } public insertBefore(newChild: Element, refChild: Element, height?: number) { - this.log('insertBefore', newChild, refChild); + //this.log('insertBefore', newChild, refChild); return; if(this.splitUp) { @@ -1022,6 +858,29 @@ export default class Scrollable { this.onScroll(); return ret; } + + public scrollIntoView(element: Element) { + if(element.parentElement) { + element.scrollIntoView(); + } else if(this.splitUp) { + let index = this.hiddenElements.up.findIndex(e => e.element == element); + let y = 0; + if(index !== -1) { + for(let i = 0; i < index; ++i) { + y += this.hiddenElements.up[i].height; + } + + this.scrollTop = y; + } else if((index = this.hiddenElements.down.findIndex(e => e.element == element)) !== -1) { + y += this.paddings.up + this.size; + for(let i = 0; i < index; ++i) { + y += this.hiddenElements.down[i].height; + } + + this.scrollTop = y; + } + } + } set scrollTop(y: number) { fastdom.mutate(() => { diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index 79e461a6..fb31d380 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -259,15 +259,21 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement { let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.classList.add('audio-waveform'); - svg.setAttributeNS(null, 'width', '250'); + svg.setAttributeNS(null, 'width', '190'); svg.setAttributeNS(null, 'height', '23'); - svg.setAttributeNS(null, 'viewBox', '0 0 250 23'); + svg.setAttributeNS(null, 'viewBox', '0 0 190 23'); div.insertBefore(svg, div.lastElementChild); let wave = doc.attributes[0].waveform as Uint8Array; let index = 0; + let skipped = 0; for(let uint8 of wave) { + if (index > 0 && index % 4 == 0) { + ++index; + ++skipped; + continue; + } let percents = uint8 / 255; let height = 23 * percents; @@ -276,12 +282,14 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement { } svg.insertAdjacentHTML('beforeend', ` - + `); ++index; } + let progress = div.querySelector('.audio-waveform') as HTMLDivElement; + let onClick = () => { if(!promise) { if(downloadDiv.classList.contains('downloading')) { @@ -310,6 +318,7 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement { let toggle = div.querySelector('.audio-toggle') as HTMLDivElement; let interval = 0; + let lastIndex = 0; toggle.addEventListener('click', () => { if(audio.paused) { @@ -327,7 +336,6 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement { (Array.from(svg.children) as HTMLElement[]).forEach(node => node.classList.remove('active')); - let lastIndex = 0; interval = setInterval(() => { if(lastIndex > svg.childElementCount || isNaN(audio.duration)) { clearInterval(interval); @@ -337,10 +345,11 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement { // @ts-ignore timeDiv.innerText = String(audio.currentTime | 0).toHHMMSS(true); - lastIndex = Math.round(audio.currentTime / audio.duration * 62); + lastIndex = Math.round(audio.currentTime / audio.duration * 47); //svg.children[lastIndex].setAttributeNS(null, 'fill', '#000'); - svg.children[lastIndex].classList.add('active'); + //svg.children[lastIndex].classList.add('active'); #Иногда пропускает полоски.. + (Array.from(svg.children) as HTMLElement[]).slice(0,lastIndex+1).forEach(node => node.classList.add('active')); //++lastIndex; //console.log('lastIndex:', lastIndex, audio.currentTime); //}, duration * 1000 / svg.childElementCount | 0/* 63 * duration / 10 */); @@ -358,11 +367,51 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement { toggle.classList.add('tgico-largeplay'); toggle.classList.remove('tgico-largepause'); clearInterval(interval); + (Array.from(svg.children) as HTMLElement[]).forEach(node => node.classList.remove('active')); // @ts-ignore timeDiv.innerText = String(audio.currentTime | 0).toHHMMSS(true); }); + let mousedown = false, mousemove = false; + progress.addEventListener('mouseleave', (e) => { + if(mousedown) { + audio.play(); + mousedown = false; + } + mousemove = false; + }) + progress.addEventListener('mousemove', (e) => { + mousemove = true; + if(mousedown) scrub(e, audio, progress); + }); + progress.addEventListener('mousedown', (e) => { + e.preventDefault(); + if(!audio.paused) { + audio.pause(); + scrub(e, audio, progress); + mousedown = true; + } + }); + progress.addEventListener('mouseup', (e) => { + if (mousemove && mousedown) { + audio.play(); + mousedown = false; + } + }); + progress.addEventListener('click', (e) => { + if(!audio.paused) scrub(e, audio, progress); + }); + + function scrub(e: MouseEvent, audio: HTMLAudioElement, progress: HTMLDivElement) { + let scrubTime = e.offsetX / 190 /* width */ * audio.duration; + (Array.from(svg.children) as HTMLElement[]).forEach(node => node.classList.remove('active')); + lastIndex = Math.round(scrubTime / audio.duration * 47); + + (Array.from(svg.children) as HTMLElement[]).slice(0,lastIndex+1).forEach(node => node.classList.add('active')); + audio.currentTime = scrubTime; + } + audio.append(source); audio.style.display = 'none'; div.append(audio); diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 024561b4..407b076f 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -27,35 +27,167 @@ console.log('appImManager included!'); let testScroll = false; class ScrollPosition { - public previousScrollHeightMinusTop = 0; - public readyFor = 'up'; - public container: HTMLElement; - - constructor(public node: HTMLElement) { + previousScrollHeightMinusTop = 0; + readyFor = 'up'; + container: HTMLElement; + rAF: number; + debug = true; + + constructor(node: HTMLElement) { this.container = node.parentElement; } + + restore() { + let setScrollTop = this.container.scrollHeight - this.previousScrollHeightMinusTop; + if(this.debug) appImManager.log('scrollPosition restore', this.readyFor, this.container.scrollHeight, + setScrollTop, this.container, this.container.parentElement.classList.contains('scrolled-down')); + + if(this.readyFor === 'up'/* || this.container.parentElement.classList.contains('scrolled-down') */) { + if(this.debug) appImManager.log('scrollPosition restore 2', this.readyFor, this.container.scrollHeight, + setScrollTop, this.container); + + if(this.rAF) window.cancelAnimationFrame(this.rAF); + this.rAF = window.requestAnimationFrame(() => { + this.container.scrollTop = this.container.scrollHeight - this.previousScrollHeightMinusTop; + this.rAF = 0; + }); + } else if(this.container.parentElement.classList.contains('scrolled-down')) { + if(this.debug) appImManager.log('scrollPosition restore 2', this.readyFor, this.container.scrollHeight, + setScrollTop, this.container); - public restore() { - //console.log('scrollPosition restore 2', this.node.scrollHeight, (this.node.scrollHeight - //- this.previousScrollHeightMinusTop) + 'px', this.container); - - if(this.readyFor === 'up' || appImManager.scroll.parentElement.classList.contains('scrolled-down')) { - this.container.scrollTop = this.node.scrollHeight - - this.previousScrollHeightMinusTop; + this.container.scrollTop = setScrollTop; } - + // 'down' doesn't need to be special cased unless the // content was flowing upwards, which would only happen // if the container is position: absolute, bottom: 0 for // a Facebook messages effect } + + prepareFor(direction = 'up') { + if(this.rAF) { + window.cancelAnimationFrame(this.rAF); + this.rAF = 0; + } - public prepareFor(direction: string) { - this.readyFor = direction || 'up'; - this.previousScrollHeightMinusTop = this.node.scrollHeight - - this.container.scrollTop; + this.readyFor = direction; - //console.log('scrollPosition prepareFor', direction, this.node.scrollHeight, this.previousScrollHeightMinusTop + 'px') + if(direction == 'down') { + let scrollTop = this.container.scrollTop; + this.previousScrollHeightMinusTop = scrollTop > 0 ? this.container.scrollHeight - scrollTop : 0; + } else { + this.previousScrollHeightMinusTop = this.container.scrollHeight - this.container.scrollTop; + } + //let scrollTop = this.container.scrollTop; + //this.previousScrollHeightMinusTop = scrollTop > 0 || this.readyFor == 'up' ? this.container.scrollHeight - this.container.scrollTop : 0; + + if(this.debug) appImManager.log.trace('scrollPosition prepareFor', direction, this.container.scrollHeight, + this.container.scrollTop, this.previousScrollHeightMinusTop); + } +} + +class BubbleGroups { + bubblesByGroups: Array<{timestamp: number, fromID: number, mid: number, group: HTMLDivElement[]}> = []; // map to group + groups: Array = []; + updateRAFs: Map = new Map(); + newGroupDiff = 120; + + removeBubble(bubble: HTMLDivElement, mid: number) { + let details = this.bubblesByGroups.findAndSplice(g => g.mid == mid); + if(details && details.group.length) { + details.group.findAndSplice(d => d == bubble); + if(!details.group.length) { + this.groups.findAndSplice(g => g == details.group); + } + } + } + + addBubble(bubble: HTMLDivElement, message: any, reverse: boolean) { + let timestamp = message.date; + let fromID = message.fromID; + let group: HTMLDivElement[]; + + // try to find added + //this.removeBubble(message.mid); + + if(this.bubblesByGroups.length) { + if(reverse) { + let g = this.bubblesByGroups[0]; + if(g.fromID == fromID && (g.timestamp - timestamp) < this.newGroupDiff) { + group = g.group; + group.unshift(bubble); + } else { + this.groups.unshift(group = [bubble]); + } + } else { + let g = this.bubblesByGroups[this.bubblesByGroups.length - 1]; + if(g.fromID == fromID && (timestamp - g.timestamp) < this.newGroupDiff) { + group = g.group; + group.push(bubble); + } else { + this.groups.push(group = [bubble]); + } + } + } else { + this.groups.push(group = [bubble]); + } + + console.log('addBubble', bubble, message.mid, fromID, reverse, group); + + this.bubblesByGroups[reverse ? 'unshift' : 'push']({timestamp, fromID, mid: message.mid, group}); + this.updateGroup(group); + } + + updateGroup(group: HTMLDivElement[]) { + if(this.updateRAFs.has(group)) { + window.cancelAnimationFrame(this.updateRAFs.get(group)); + this.updateRAFs.delete(group); + } + + this.updateRAFs.set(group, window.requestAnimationFrame(() => { + this.updateRAFs.delete(group); + + if(!group.length) { + return; + } + + let first = group[0]; + + console.log('updateGroup', group, first); + + if(group.length == 1) { + first.classList.add('is-group-first', 'is-group-last'); + return; + } else { + first.classList.remove('is-group-last'); + first.classList.add('is-group-first'); + } + + let length = group.length - 1; + for(let i = 1; i < length; ++i) { + let bubble = group[i]; + bubble.classList.remove('is-group-last', 'is-group-first'); + } + + let last = group[group.length - 1]; + last.classList.remove('is-group-first'); + last.classList.add('is-group-last'); + })); + } + + updateGroupByMessageID(mid: number) { + let details = this.bubblesByGroups.find(g => g.mid == mid); + if(details) { + this.updateGroup(details.group); + } + } + + cleanup() { + this.bubblesByGroups = []; + for(let value of this.updateRAFs.values()) { + window.cancelAnimationFrame(value); + } + this.updateRAFs.clear(); } } @@ -72,13 +204,13 @@ export class AppImManager { public goDownBtn = this.pageEl.querySelector('#bubbles-go-down') as HTMLButtonElement; private getHistoryPromise: Promise; private getHistoryTimeout = 0; - + private chatInputC: ChatInput = null; - + public myID = 0; public peerID = 0; public muted = false; - + public bubbles: {[mid: number]: HTMLDivElement} = {}; public dateMessages: {[timestamp: number]: { div: HTMLDivElement, firstTimestamp: number }} = {}; public unreaded: number[] = []; @@ -87,13 +219,13 @@ export class AppImManager { public offline = false; public updateStatusInterval = 0; - + public pinnedMsgID = 0; private pinnedMessageContainer = this.pageEl.querySelector('.pinned-message') as HTMLDivElement; private pinnedMessageContent = this.pinnedMessageContainer.querySelector('.pinned-message-subtitle') as HTMLDivElement; private firstTopMsgID = 0; - + public loadMediaQueue: Array<() => Promise> = []; private loadMediaQueuePromise: Promise = null; private loadingMedia = 0; @@ -101,116 +233,121 @@ export class AppImManager { public scroll: HTMLDivElement = null; public scrollable: Scrollable = null; public scrollPosition: ScrollPosition = null; - + public log: ReturnType; - + private preloader: ProgressivePreloader = null; - + private typingTimeouts: {[peerID: number]: number} = {}; private typingUsers: {[userID: number]: number} = {} // to peerID - + private topbar: HTMLDivElement = null; private chatInput: HTMLDivElement = null; private scrolledAll: boolean; private scrolledAllDown: boolean; - + public contextMenu = document.getElementById('bubble-contextmenu') as HTMLDivElement; private contextMenuPin = this.contextMenu.querySelector('.menu-pin') as HTMLDivElement; private contextMenuEdit = this.contextMenu.querySelector('.menu-edit') as HTMLDivElement; private contextMenuMsgID: number; - + private popupDeleteMessage: { popupEl?: HTMLDivElement, deleteBothBtn?: HTMLButtonElement, deleteMeBtn?: HTMLButtonElement, cancelBtn?: HTMLButtonElement } = {}; - + private setPeerPromise: Promise = null; - + + public bubbleGroups = new BubbleGroups(); + constructor() { this.log = logger('IM'); - + this.chatInputC = new ChatInput(); - + this.preloader = new ProgressivePreloader(null, false); - + this.popupDeleteMessage.popupEl = this.pageEl.querySelector('.popup-delete-message') as HTMLDivElement; this.popupDeleteMessage.deleteBothBtn = this.popupDeleteMessage.popupEl.querySelector('.popup-delete-both') as HTMLButtonElement; this.popupDeleteMessage.deleteMeBtn = this.popupDeleteMessage.popupEl.querySelector('.popup-delete-me') as HTMLButtonElement; this.popupDeleteMessage.cancelBtn = this.popupDeleteMessage.popupEl.querySelector('.popup-close') as HTMLButtonElement; - + apiManager.getUserID().then((id) => { this.myID = id; }); - + this.topbar = document.getElementById('topbar') as HTMLDivElement; this.chatInput = document.getElementById('chat-input') as HTMLDivElement; - + $rootScope.$on('user_auth', (e: CustomEvent) => { let userAuth = e.detail; this.myID = userAuth ? userAuth.id : 0; }); - + + // will call when message is sent (only 1) $rootScope.$on('history_append', (e: CustomEvent) => { let details = e.detail; - + this.renderMessagesByIDs([details.messageID]); }); - + + // will call when sent for update pos $rootScope.$on('history_update', (e: CustomEvent) => { let details = e.detail; - + if(details.mid && details.peerID == this.peerID) { let mid = details.mid; - + let bubble = this.bubbles[mid]; if(!bubble) return; - + let message = appMessagesManager.getMessage(mid); //this.log('history_update', this.bubbles[mid], mid, message); - this.renderMessage(message, false, false, bubble); + this.renderMessage(message, false, false, bubble); + this.deleteEmptySideDivs(); } }); - + $rootScope.$on('history_multiappend', (e: CustomEvent) => { let msgIDsByPeer = e.detail; if(!(this.peerID in msgIDsByPeer)) return; - + let msgIDs = msgIDsByPeer[this.peerID]; - + this.renderMessagesByIDs(msgIDs); - + //appDialogsManager.sortDom(); }); - + $rootScope.$on('history_delete', (e: CustomEvent) => { let detail: { peerID: string, msgs: {[x: number]: boolean} } = e.detail; - + this.deleteMessagesByIDs(Object.keys(detail.msgs).map(s => +s)); - + setTimeout(() => { this.deleteEmptySideDivs(); }, 0); }); - + // Calls when message successfully sent and we have an ID $rootScope.$on('message_sent', (e: CustomEvent) => { let {tempID, mid} = e.detail; - + ////this.log('message_sent', e.detail); - + let bubble = this.bubbles[tempID]; if(bubble) { this.bubbles[mid] = bubble; - + /////this.log('message_sent', bubble); - + let media = bubble.querySelector('img, video'); if(media) { media.setAttribute('message-id', mid); @@ -219,11 +356,13 @@ export class AppImManager { bubble.classList.remove('is-sending'); bubble.classList.add('is-sent'); + this.bubbleGroups.removeBubble(bubble, tempID); + delete this.bubbles[tempID]; } else { this.log.warn('message_sent there is no bubble', e.detail); } - + let length = this.unreadOut.length; for(let i = 0; i < length; i++) { if(this.unreadOut[i] == tempID) { @@ -231,19 +370,19 @@ export class AppImManager { } } }); - + $rootScope.$on('message_edit', (e: CustomEvent) => { let {peerID, mid, id, justMedia} = e.detail; - + if(peerID != this.peerID) return; - + let bubble = this.bubbles[mid]; if(!bubble) return; - + let message = appMessagesManager.getMessage(mid); this.renderMessage(message, false, false, bubble, false); }); - + $rootScope.$on('messages_downloaded', (e: CustomEvent) => { let mids: number[] = e.detail; @@ -255,65 +394,65 @@ export class AppImManager { this.pinnedMessageContainer.style.display = ''; this.pinnedMessageContent.innerHTML = RichTextProcessor.wrapEmojiText(message.message); } - + this.needUpdate.forEachReverse((obj, idx) => { if(obj.replyMid == mid) { let {mid, replyMid} = this.needUpdate.splice(idx, 1)[0]; - + //this.log('messages_downloaded', mid, replyMid, i, this.needUpdate, this.needUpdate.length, mids, this.bubbles[mid]); let bubble = this.bubbles[mid]; if(!bubble) return; - + let message = appMessagesManager.getMessage(mid); - + let repliedMessage = appMessagesManager.getMessage(replyMid); if(repliedMessage.deleted) { // чтобы не пыталось бесконечно загрузить удалённое сообщение delete message.reply_to_mid; // WARNING! } - + this.renderMessage(message, false, false, bubble, false); } }); }); }); - + $rootScope.$on('apiUpdate', (e: CustomEvent) => { let update = e.detail; - + this.handleUpdate(update); }); - + window.addEventListener('blur', () => { lottieLoader.checkAnimations(true); - + this.offline = true; this.updateStatus(); clearInterval(this.updateStatusInterval); window.addEventListener('focus', () => { lottieLoader.checkAnimations(false); - + this.offline = false; this.updateStatus(); this.updateStatusInterval = window.setInterval(() => this.updateStatus(), 50e3); }, {once: true}); }); - + (this.pageEl.querySelector('.person') as HTMLDivElement).addEventListener('click', (e) => { appSidebarRight.toggleSidebar(true); }); - + this.chatInner.addEventListener('click', (e) => { let target = e.target as HTMLElement; let bubble: HTMLDivElement = null; try { bubble = findUpClassName(e.target, 'bubble'); } catch(err) {} - + if(!bubble) return; - + if(['IMG', 'VIDEO', 'SVG', 'DIV'].indexOf(target.tagName) === -1) target = findUpTag(target, 'DIV'); - + if(target.tagName == 'DIV') { if(target.classList.contains('forward')) { let savedFrom = bubble.dataset.savedFrom; @@ -329,16 +468,16 @@ export class AppImManager { if(!isNaN(peerID)) { this.setPeer(peerID); } - + return; } - + let isReplyClick = false; - + try { isReplyClick = !!findUpClassName(e.target, 'reply'); } catch(err) {} - + if(isReplyClick && bubble.classList.contains('is-reply')/* || bubble.classList.contains('forwarded') */) { let originalMessageID = +bubble.getAttribute('data-original-mid'); this.setPeer(this.peerID, originalMessageID); @@ -347,103 +486,101 @@ export class AppImManager { } else if(target.tagName == 'IMG' && target.parentElement.classList.contains('user-avatar')) { let peerID = +target.parentElement.dataset.peerID; - + if(!isNaN(peerID)) { this.setPeer(peerID); } } else if((target.tagName == 'IMG' && !target.classList.contains('emoji')) || target.tagName == 'VIDEO') { let messageID = +target.getAttribute('message-id'); let message = appMessagesManager.getMessage(messageID); - + if(!message) { this.log.warn('no message by messageID:', messageID); return; } - + let ids = Object.keys(this.bubbles).map(k => +k).filter(id => { let message = appMessagesManager.getMessage(id); - - return message.media && (message.media.photo - || (message.media.document && (message.media.document.type == 'video' || message.media.document.type == 'gif')) - || (message.media.webpage && (message.media.webpage.document || message.media.webpage.photo))); + + return message.media && (message.media.photo || (message.media.document && (message.media.document.type == 'video' || message.media.document.type == 'gif')) || (message.media.webpage && (message.media.webpage.document || message.media.webpage.photo))); }).sort(); let idx = ids.findIndex(i => i == messageID); - + let prev = ids[idx + 1] || null; let next = ids[idx - 1] || null; - + let prevTarget = this.bubbles[prev] ? this.bubbles[prev].querySelector('img, video') as HTMLElement : null; let nextTarget = this.bubbles[next] ? this.bubbles[next].querySelector('img, video') as HTMLElement : null; - + /////this.log('ids', ids, idx, this.bubbles[prev], this.bubbles[next]); - + appMediaViewer.openMedia(message, target, nextTarget, prevTarget); - + //appMediaViewer.openMedia(message, target as HTMLImageElement); } - + //console.log('chatInner click', e); }); - + this.searchBtn.addEventListener('click', (e) => { if(this.peerID) { appSidebarLeft.beginSearch(this.peerID); } }); - + this.pinnedMessageContainer.addEventListener('click', (e) => { e.preventDefault(); e.cancelBubble = true; - + let mid = +this.pinnedMessageContainer.getAttribute('data-mid'); this.setPeer(this.peerID, mid); }); - + this.btnMenuMute.addEventListener('click', () => this.mutePeer()); this.btnMute.addEventListener('click', () => this.mutePeer()); - + let onKeyDown = (e: KeyboardEvent) => { let target = e.target as HTMLElement; - + //if(target.tagName == 'INPUT') return; - + //this.log('onkeydown', e); - + if(this.chatInputC.attachMediaPopUp.container.classList.contains('active')) { if(target.tagName != 'INPUT') { this.chatInputC.attachMediaPopUp.captionInput.focus(); } - + if(e.key == 'Enter') { this.chatInputC.attachMediaPopUp.sendBtn.click(); } else if(e.key == 'Escape') { this.chatInputC.attachMediaPopUp.container.classList.remove('active'); } - + return; } - + if(e.key == 'Meta' || e.key == 'Control') { return; } else if(e.key == 'c' && (e.ctrlKey || e.metaKey) && target.tagName != 'INPUT') { return; } - + if(e.target != this.chatInputC.messageInput && target.tagName != 'INPUT') { this.chatInputC.messageInput.focus(); placeCaretAtEnd(this.chatInputC.messageInput); } }; - + document.body.addEventListener('keydown', onKeyDown); - + this.chatInner.addEventListener('contextmenu', e => { let bubble: HTMLDivElement = null; - + try { bubble = findUpClassName(e.target, 'bubble'); } catch(e) {} - + if(bubble) { e.preventDefault(); e.cancelBubble = true; @@ -455,60 +592,59 @@ export class AppImManager { break; } } - + if(!msgID) return; - - if(this.myID == this.peerID || - (this.peerID < 0 && !appPeersManager.isChannel(this.peerID) && !appPeersManager.isMegagroup(this.peerID))) { + + if(this.myID == this.peerID || (this.peerID < 0 && !appPeersManager.isChannel(this.peerID) && !appPeersManager.isMegagroup(this.peerID))) { this.contextMenuPin.style.display = ''; } else this.contextMenuPin.style.display = 'none'; - + this.contextMenuMsgID = msgID; - + let side = bubble.parentElement.classList.contains('in') ? 'left' : 'right'; - + this.contextMenuEdit.style.display = side == 'right' ? '' : 'none'; - + this.contextMenu.classList.remove('bottom-left', 'bottom-right'); this.contextMenu.classList.add(side == 'left' ? 'bottom-right' : 'bottom-left'); - + let {clientX, clientY} = e; - + this.contextMenu.style.left = (side == 'right' ? clientX - this.contextMenu.scrollWidth : clientX) + 'px'; if((clientY + this.contextMenu.scrollHeight) > window.innerHeight) { this.contextMenu.style.top = (window.innerHeight - this.contextMenu.scrollHeight) + 'px'; } else { this.contextMenu.style.top = clientY + 'px'; } - + //this.contextMenu.classList.add('active'); openBtnMenu(this.contextMenu); - + /////this.log('contextmenu', e, bubble, msgID, side); } }); - + this.contextMenu.querySelector('.menu-copy').addEventListener('click', () => { let message = appMessagesManager.getMessage(this.contextMenuMsgID); - + let str = message ? message.message : ''; - + var textArea = document.createElement("textarea"); textArea.value = str; textArea.style.position = "fixed"; //avoid scrolling to bottom document.body.appendChild(textArea); textArea.focus(); textArea.select(); - + try { document.execCommand('copy'); } catch (err) { console.error('Oops, unable to copy', err); } - + document.body.removeChild(textArea); }); - + this.contextMenu.querySelector('.menu-delete').addEventListener('click', () => { if(this.peerID == this.myID) { this.popupDeleteMessage.deleteBothBtn.style.display = 'none'; @@ -516,7 +652,7 @@ export class AppImManager { } else { this.popupDeleteMessage.deleteBothBtn.style.display = ''; this.popupDeleteMessage.deleteMeBtn.innerText = 'DELETE JUST FOR ME'; - + if(this.peerID > 0) { let title = appPeersManager.getPeerTitle(this.peerID); this.popupDeleteMessage.deleteBothBtn.innerHTML = 'DELETE FOR ME AND ' + title; @@ -524,7 +660,7 @@ export class AppImManager { this.popupDeleteMessage.deleteBothBtn.innerText = 'DELETE FOR ALL'; } } - + this.popupDeleteMessage.popupEl.classList.add('active'); }); @@ -534,14 +670,14 @@ export class AppImManager { this.chatInputC.replyToMsgID = this.contextMenuMsgID; this.chatInputC.editMsgID = 0; }); - + this.contextMenuEdit.addEventListener('click', () => { let message = appMessagesManager.getMessage(this.contextMenuMsgID); this.chatInputC.setTopInfo('Editing', message.message, message.message, message.media); this.chatInputC.replyToMsgID = 0; this.chatInputC.editMsgID = this.contextMenuMsgID; }); - + this.contextMenuPin.addEventListener('click', () => { apiManager.invokeApi('messages.updatePinnedMessage', { flags: 0, @@ -552,12 +688,12 @@ export class AppImManager { apiUpdatesManager.processUpdateMessage(updates); }); }); - + this.popupDeleteMessage.deleteBothBtn.addEventListener('click', () => { this.deleteMessages(true); this.popupDeleteMessage.cancelBtn.click(); }); - + this.popupDeleteMessage.deleteMeBtn.addEventListener('click', () => { this.deleteMessages(false); this.popupDeleteMessage.cancelBtn.click(); @@ -565,32 +701,32 @@ export class AppImManager { this.goDownBtn.addEventListener('click', () => { let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0]; - + if(dialog) { this.setPeer(this.peerID, dialog.top_message); } else { this.scroll.scrollTop = this.scroll.scrollHeight; } }); - + this.updateStatusInterval = window.setInterval(() => this.updateStatus(), 50e3); this.updateStatus(); setInterval(() => this.setPeerStatus(), 60e3); - + this.setScroll(); } - + public deleteMessages(revoke = false) { let flags = revoke ? 1 : 0; let ids = [this.contextMenuMsgID]; - + apiManager.invokeApi('messages.deleteMessages', { flags: flags, revoke: revoke, id: ids }).then((affectedMessages: any) => { /////this.log('deleted messages:', affectedMessages); - + apiUpdatesManager.processUpdateMessage({ _: 'updateShort', update: { @@ -599,7 +735,7 @@ export class AppImManager { pts_count: affectedMessages.pts_count } }); - + apiUpdatesManager.processUpdateMessage({ _: 'updateShort', update: { @@ -609,14 +745,14 @@ export class AppImManager { }); }); } - + public deleteEmptySideDivs() { return; - + let nodes = Array.from(this.chatInner.childNodes) as HTMLDivElement[]; nodes.filter((node) => { let childElementCount = node.childElementCount; - + if(!childElementCount) { node.remove(); return false; @@ -627,7 +763,7 @@ export class AppImManager { return false; } } - + return true; }).forEach(node => { let nextNode = node.nextElementSibling; @@ -635,65 +771,33 @@ export class AppImManager { (Array.from(node.childNodes) as HTMLDivElement[]).reverse().forEach(div => { nextNode.prepend(div); }); - + node.remove(); } }); } - + public loadMediaQueuePush(cb: () => Promise) { this.loadMediaQueue.push(cb); this.loadMediaQueueProcess(); } - - public async loadMediaQueueProcessOld(): Promise { - if(this.loadMediaQueuePromise /* || 1 == 1 */) return this.loadMediaQueuePromise; - - let woo = this.loadMediaQueue.splice(-5, 5).reverse().map(f => f()); - - if(woo.length) { - ///this.log('Will load more media:', woo.length); - - woo.forEach(async(promise) => { - try { - await promise; - } catch(err) { - this.log.error('loadMediaQueue error:', err); - } - - this.loadingMedia--; - }); - - try { - this.loadMediaQueuePromise = Promise.all(woo); - await this.loadMediaQueuePromise; - } catch(err) { - this.log.error('loadMediaQueue error:', err); - } - } - - this.loadMediaQueuePromise = null; - - if(this.loadMediaQueue.length) return this.loadMediaQueueProcess(); - return this.loadMediaQueuePromise; - } - + public async loadMediaQueueProcess(): Promise { - if(this.loadingMedia >= 5) return; - + if(this.loadingMedia >= 5/* || 1 == 1 */) return; + let item = this.loadMediaQueue.pop(); if(item) { this.loadingMedia++; - + let peerID = this.peerID; - + let promise = item(); try { await promise; } catch(err) { this.log.error('loadMediaQueue error:', err); } - + if(peerID == this.peerID) { this.loadingMedia--; } @@ -701,155 +805,130 @@ export class AppImManager { if(this.loadMediaQueue.length) return this.loadMediaQueueProcess(); } - + public updateStatus() { if(!this.myID) return Promise.resolve(); - + appUsersManager.setUserStatus(this.myID, this.offline); return apiManager.invokeApi('account.updateStatus', {offline: this.offline}); } + public loadMoreHistory(top: boolean) { + // load more history + // возможно нужно добавить разные таймауты для верха и низа + if(!this.getHistoryPromise && !this.getHistoryTimeout && this.peerID && !testScroll) { + this.getHistoryTimeout = setTimeout(() => { // must be + let history = Object.keys(this.bubbles).map(id => +id).sort(); + + /* let history = appMessagesManager.historiesStorage[this.peerID].history; + let length = history.length; */ + + // filter negative ids + let lastBadIndex = -1; + for(let i = 0; i < history.length; ++i) { + if(history[i] <= 0) lastBadIndex = i; + else break; + } + if(lastBadIndex != -1) { + history = history.slice(lastBadIndex + 1); + } + + this.getHistoryTimeout = 0; + + if(!this.scrolledAll && top) { + this.log('Will load more (up) history by id:', history[0], 'maxID:', history[history.length - 1], history); + /* false && */!testScroll && this.getHistory(history[0], true).then(() => { // uncomment + this.onScroll(); + }).catch(err => { + this.log.warn('Could not load more history, err:', err); + }); + } + + if(this.scrolledAllDown) return; + + let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0]; + /* if(!dialog) { + this.log.warn('no dialog for load history'); + return; + } */ + + // if scroll down after search + if(!top && (!dialog || history.indexOf(dialog.top_message) === -1)) { + this.log('Will load more (down) history by maxID:', history[history.length - 1], history); + /* false && */!testScroll && this.getHistory(history[history.length - 1], false, true).then(() => { // uncomment + this.onScroll(); + }).catch(err => { + this.log.warn('Could not load more history, err:', err); + }); + } + }, 0); + } + } + public onScroll() { let readed: number[] = []; - + this.unreaded.forEachReverse((msgID, idx) => { let bubble = this.bubbles[msgID]; - + if(isElementInViewport(bubble)) { readed.push(msgID); this.unreaded.splice(idx, 1); } }); - + lottieLoader.checkAnimations(); - + if(readed.length) { let max = Math.max(...readed); let min = Math.min(...readed); - + if(this.peerID < 0) { max = appMessagesIDsManager.getMessageIDInfo(max)[0]; min = appMessagesIDsManager.getMessageIDInfo(min)[0]; } - + //appMessagesManager.readMessages(readed); appMessagesManager.readHistory(this.peerID, max, min).catch((err: any) => { this.log.error('readHistory err:', err); appMessagesManager.readHistory(this.peerID, max, min); }); } - + if(this.scroll.scrollHeight - (this.scroll.scrollTop + this.scroll.offsetHeight) == 0/* <= 5 */) { this.scroll.parentElement.classList.add('scrolled-down'); } else if(this.scroll.parentElement.classList.contains('scrolled-down')) { this.scroll.parentElement.classList.remove('scrolled-down'); } - - // load more history - if(!this.getHistoryPromise && !this.getHistoryTimeout && !testScroll) { - this.getHistoryTimeout = setTimeout(() => { // must be - let history = Object.keys(this.bubbles).map(id => +id).sort(); - - /* let history = appMessagesManager.historiesStorage[this.peerID].history; - let length = history.length; */ - - // filter negative ids - let lastBadIndex = -1; - for(let i = 0; i < history.length; ++i) { - if(history[i] <= 0) lastBadIndex = i; - else break; - } - if(lastBadIndex != -1) { - history = history.slice(lastBadIndex + 1); - } - - this.getHistoryTimeout = 0; - - let willLoad = false; - if(!this.scrolledAll) { - let length = history.length < 10 ? history.length : 10; - for(let i = 0; i < length; ++i) { - let msgID = history[i]; - - let bubble = this.bubbles[msgID]; - - if(!bubble) { - this.log.error('no bubble by msgID:', msgID); - continue; - } - - if(isElementInViewport(bubble)) { - willLoad = true; - - ////this.log('Will load more (up) history by id:', history[0], 'maxID:', history[history.length - 1], history, bubble); - /* false && */!testScroll && this.getHistory(history[0], true).then(() => { // uncomment - this.onScroll(); - }).catch(err => { - this.log.warn('Could not load more history, err:', err); - }); - - break; - } - } - } - - if(this.scrolledAllDown) return; - - let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0]; - /* if(!dialog) { - this.log.warn('no dialog for load history'); - return; - } */ - - // if scroll down after search - if(!willLoad && (!dialog || history.indexOf(dialog.top_message) === -1)) { - let lastMsgIDs = history.slice(-10); - for(let msgID of lastMsgIDs) { - let bubble = this.bubbles[msgID]; - - if(isElementInViewport(bubble)) { - willLoad = true; - - ////this.log('Will load more (down) history by maxID:', lastMsgIDs[lastMsgIDs.length - 1], lastMsgIDs, bubble); - /* false && */!testScroll && this.getHistory(lastMsgIDs[lastMsgIDs.length - 1], false, true).then(() => { // uncomment - this.onScroll(); - }).catch(err => { - this.log.warn('Could not load more history, err:', err); - }); - - break; - } - } - } - }, 0); - } } - + public setScroll() { - this.scrollable = new Scrollable(this.bubblesContainer, false, true, 750, 'IM'/* 1500 */); + this.scrollable = new Scrollable(this.bubblesContainer, false, true, 750, 'IM', this.chatInner/* 1500 */, 300); this.scroll = this.scrollable.container; - + this.scrollable.setVirtualContainer(this.chatInner); - + this.scrollable.onScrolledTop = () => this.loadMoreHistory(true); + this.scrollable.onScrolledBottom = () => this.loadMoreHistory(false); + this.scrollPosition = new ScrollPosition(this.chatInner); this.scroll.addEventListener('scroll', this.onScroll.bind(this)); this.scroll.parentElement.classList.add('scrolled-down'); } - + public setPeerStatus() { if(!this.myID) return; - + // set subtitle this.subtitleEl.innerText = appSidebarRight.profileElements.subtitle.innerText = ''; this.subtitleEl.classList.remove('online'); appSidebarRight.profileElements.subtitle.classList.remove('online'); - + if(this.peerID < 0) { // not human let chat = appPeersManager.getPeer(this.peerID); let isChannel = appPeersManager.isChannel(this.peerID) && !appPeersManager.isMegagroup(this.peerID); - + ///////this.log('setPeerStatus', chat); - + Promise.all([ appPeersManager.isMegagroup(this.peerID) ? apiManager.invokeApi('messages.getOnlines', { peer: appPeersManager.getInputPeerByID(this.peerID) @@ -858,65 +937,65 @@ export class AppImManager { appProfileManager.getChatFull(chat.id) ]).then(results => { let [chatOnlines, chatInfo] = results; - + let onlines = chatOnlines ? chatOnlines.onlines : 1; - + ///////////this.log('chatInfo res:', chatInfo); - + if(chatInfo.pinned_msg_id) { // request pinned message this.pinnedMsgID = chatInfo.pinned_msg_id; appMessagesManager.wrapSingleMessage(chatInfo.pinned_msg_id); } - + let participants_count = chatInfo.participants_count || chatInfo.participants.participants.length; let subtitle = numberWithCommas(participants_count) + ' ' + (isChannel ? 'subscribers' : 'members'); - + if(onlines > 1) { subtitle += ', ' + numberWithCommas(onlines) + ' online'; } - + this.subtitleEl.innerText = appSidebarRight.profileElements.subtitle.innerText = subtitle; }); } else if(!appUsersManager.isBot(this.peerID)) { // user let user = appUsersManager.getUser(this.peerID); - + //this.subtitleEl.classList.remove('online'); - + if(user && user.status && this.myID != this.peerID) { let subtitle = ''; switch(user.status._) { case 'userStatusRecently': - subtitle += 'last seen recently'; - break; + subtitle += 'last seen recently'; + break; case 'userStatusOffline': - subtitle = 'last seen '; - - let date = user.status.was_online; - let now = Date.now() / 1000; - - if((now - date) < 60) { - subtitle += ' just now'; - } else if((now - date) < 3600) { - subtitle += ((now - date) / 60 | 0) + ' minutes ago'; - } else if(now - date < 86400) { - subtitle += ((now - date) / 3600 | 0) + ' hours ago'; - } else { - let d = new Date(date * 1000); - subtitle += ('0' + d.getDate()).slice(-2) + '.' + ('0' + (d.getMonth() + 1)).slice(-2) + ' at ' + - ('0' + d.getHours()).slice(-2) + ':' + ('0' + d.getMinutes()).slice(-2); - } - - break; + subtitle = 'last seen '; + + let date = user.status.was_online; + let now = Date.now() / 1000; + + if((now - date) < 60) { + subtitle += ' just now'; + } else if((now - date) < 3600) { + subtitle += ((now - date) / 60 | 0) + ' minutes ago'; + } else if(now - date < 86400) { + subtitle += ((now - date) / 3600 | 0) + ' hours ago'; + } else { + let d = new Date(date * 1000); + subtitle += ('0' + d.getDate()).slice(-2) + '.' + ('0' + (d.getMonth() + 1)).slice(-2) + ' at ' + + ('0' + d.getHours()).slice(-2) + ':' + ('0' + d.getMinutes()).slice(-2); + } + + break; case 'userStatusOnline': - this.subtitleEl.classList.add('online'); - appSidebarRight.profileElements.subtitle.classList.add('online'); - subtitle = 'online'; - break; + this.subtitleEl.classList.add('online'); + appSidebarRight.profileElements.subtitle.classList.add('online'); + subtitle = 'online'; + break; } - + appSidebarRight.profileElements.subtitle.innerText = subtitle; - + if(this.typingUsers[this.peerID] == this.peerID) { this.subtitleEl.innerText = 'typing...'; this.subtitleEl.classList.add('online'); @@ -924,39 +1003,40 @@ export class AppImManager { } } } - + public cleanup() { this.peerID = $rootScope.selectedPeerID = 0; this.scrolledAll = false; this.scrolledAllDown = false; this.muted = false; - + for(let i in this.bubbles) { let bubble = this.bubbles[i]; bubble.remove(); } this.bubbles = {}; this.dateMessages = {}; + this.bubbleGroups.cleanup(); this.unreaded = []; this.unreadOut = []; this.loadMediaQueue = []; this.loadingMedia = 0; this.needUpdate.length = 0; - + lottieLoader.checkAnimations(false, 'chat', true); - + // clear input this.chatInputC.messageInput.innerHTML = ''; this.chatInputC.replyElements.cancelBtn.click(); - + // clear messages this.chatInner.innerHTML = ''; - + this.scrollable.setVirtualContainer(this.chatInner); - + //appSidebarRight.minMediaID = {}; } - + public setPeer(peerID: number, lastMsgID = 0, forwarding = false) { if(peerID == 0) { appSidebarRight.toggleSidebar(false); @@ -964,49 +1044,50 @@ export class AppImManager { this.cleanup(); return Promise.resolve(false); } - + let samePeer = this.peerID == peerID; - + if(this.setPeerPromise && samePeer) return this.setPeerPromise; - + if(lastMsgID) { appMessagesManager.readHistory(peerID, lastMsgID); // lol } - + if(samePeer) { if(!testScroll && !lastMsgID) { return Promise.resolve(true); } - + if(this.bubbles[lastMsgID]) { let dialog = appMessagesManager.getDialogByPeerID(peerID)[0]; - + if(dialog && lastMsgID == dialog.top_message) { this.scroll.scrollTop = this.scroll.scrollHeight; } else { - this.bubbles[lastMsgID].scrollIntoView(); + //this.bubbles[lastMsgID].scrollIntoView(); + this.scrollable.scrollIntoView(this.bubbles[lastMsgID]); } - + return Promise.resolve(true); } } - + // clear this.cleanup(); - + // set new this.peerID = $rootScope.selectedPeerID = peerID; - + // no dialog /* if(!appMessagesManager.getDialogByPeerID(this.peerID).length) { this.log.error('No dialog by peerID:', this.peerID); return Promise.reject(); } */ - + this.pinnedMessageContainer.style.display = 'none'; - + this.preloader.attach(this.chatInner); - + let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0] || null; //////this.log('setPeer peerID:', this.peerID, dialog, lastMsgID); appDialogsManager.loadDialogPhoto(this.avatarEl, this.peerID); @@ -1014,9 +1095,9 @@ export class AppImManager { if(!samePeer && appDialogsManager.lastActiveListElement) { appDialogsManager.lastActiveListElement.classList.remove('active'); } - + this.firstTopMsgID = dialog ? dialog.top_message : 0; - + /* let dom = appDialogsManager.getDialogDom(this.peerID); if(!dom) { this.log.warn('No rendered dialog by peerID:', this.peerID); @@ -1025,9 +1106,9 @@ export class AppImManager { } // warning need check dom.listEl.classList.add('active'); */ - + this.setPeerStatus(); - + let title = ''; if(this.peerID == this.myID) { title = 'Saved Messages'; @@ -1036,18 +1117,20 @@ export class AppImManager { } //this.titleEl.innerHTML = appSidebarRight.profileElements.name.innerHTML = dom.titleSpan.innerHTML; this.titleEl.innerHTML = appSidebarRight.profileElements.name.innerHTML = title; - + this.topbar.style.display = this.goDownBtn.style.display = ''; - //appSidebarRight.toggleSidebar(true); - + appSidebarRight.toggleSidebar(true); + this.chatInput.style.display = appPeersManager.isChannel(peerID) && !appPeersManager.isMegagroup(peerID) ? 'none' : ''; - + if(appPeersManager.isAnyGroup(peerID)) { this.chatInner.classList.add('is-chat'); } else { this.chatInner.classList.remove('is-chat'); } + //this.scroll.scrollTop = this.scroll.scrollHeight; + return this.setPeerPromise = Promise.all([ this.getHistory(forwarding ? lastMsgID + 1 : lastMsgID).then(() => { ////this.log('setPeer removing preloader'); @@ -1058,7 +1141,7 @@ export class AppImManager { //////this.log('setPeer render last message:', message, lastMsgID); this.renderMessage(message); } - + if(!dialog || lastMsgID != dialog.top_message) { let bubble = this.bubbles[lastMsgID]; @@ -1068,62 +1151,61 @@ export class AppImManager { this.scroll.scrollTop = this.scroll.scrollHeight; } } else if(dialog && dialog.top_message) { // add last message, bc in getHistory will load < max_id - this.renderMessage(appMessagesManager.getMessage(dialog.top_message)); + this.renderMessage(appMessagesManager.getMessage(dialog.top_message), false, true); + //this.scroll.scrollTop = this.scroll.scrollHeight; } - if(this.scroll) { - this.onScroll(); - } + this.onScroll(); + this.scrollable.onScroll(); this.preloader.detach(); - + //setTimeout(() => { - //appSidebarRight.fillProfileElements(); - appSidebarRight.loadSidebarMedia(); + //appSidebarRight.fillProfileElements(); + appSidebarRight.loadSidebarMedia(true); //}, 500); return true; })/* .catch(err => { this.log.error(err); }) */, - + appSidebarRight.fillProfileElements() ]).then(() => { if(this.peerID == peerID) { this.setPeerPromise = null; } - + return true; }).catch(err => { if(this.peerID == peerID) { this.setPeerPromise = null; } - + this.log.error('setPeer promises error:', err); return false; }); } - + public setTyping(action: any): Promise { if(!this.peerID) return Promise.resolve(false); - + if(typeof(action) == 'string') { action = {_: action}; } - + let input = appPeersManager.getInputPeerByID(this.peerID); return apiManager.invokeApi('messages.setTyping', { peer: input, action: action }) as Promise; } - + public updateUnreadByDialog(dialog: any) { let maxID = this.peerID == this.myID ? dialog.read_inbox_max_id : dialog.read_outbox_max_id; - + ///////this.log('updateUnreadByDialog', maxID, dialog, this.unreadOut); - - let length = this.unreadOut.length; + this.unreadOut.forEachReverse((msgID, idx) => { if(msgID > 0 && msgID <= maxID) { let bubble = this.bubbles[msgID]; @@ -1133,72 +1215,68 @@ export class AppImManager { } }); } - + public deleteMessagesByIDs(msgIDs: number[]) { msgIDs.forEach(id => { if(this.firstTopMsgID == id) { let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0]; - + if(dialog) { ///////this.log('setting firstTopMsgID after delete:', id, dialog.top_message, dialog); this.firstTopMsgID = dialog.top_message; } } - + if(!(id in this.bubbles)) return; let bubble = this.bubbles[id]; - let parent = bubble.parentNode as HTMLDivElement; delete this.bubbles[id]; - bubble.remove(); - - if(!parent.childNodes.length) { - parent.remove(); - } + this.scrollable.removeElement(bubble); + //bubble.remove(); }); - + lottieLoader.checkAnimations(); } - + public renderMessagesByIDs(msgIDs: number[]) { if(!this.bubbles[this.firstTopMsgID] && Object.keys(this.bubbles).length) { // seems search active //////this.log('seems search is active, skipping render:', msgIDs); return; } - + msgIDs.forEach((msgID: number) => { let message = appMessagesManager.getMessage(msgID); - + /////////this.log('got new message to append:', message); - + //this.unreaded.push(msgID); this.renderMessage(message); }); } - + public renderMessage(message: any, reverse = false, multipleRender?: boolean, bubble: HTMLDivElement = null, updatePosition = true) { /////this.log('message to render:', message); if(message.deleted) return; - + let peerID = this.peerID; let our = message.fromID == this.myID; - + let messageDiv = document.createElement('div'); messageDiv.classList.add('message'); - + //messageDiv.innerText = message.message; - + if(!multipleRender) { this.scrollPosition.prepareFor(reverse ? 'up' : 'down'); // лагает из-за этого } - + let bubbleContainer: HTMLDivElement; - + // bubble if(!bubble) { bubbleContainer = document.createElement('div'); bubbleContainer.classList.add('bubble__container'); - + bubble = document.createElement('div'); bubble.classList.add('bubble'); bubble.appendChild(bubbleContainer); @@ -1209,53 +1287,78 @@ export class AppImManager { bubbleContainer.innerHTML = ''; //bubble.innerHTML = ''; } - + + if(message._ == 'messageService') { + bubble.className = 'bubble service'; + + let action = message.action; + + let title = appPeersManager.getPeerTitle(message.fromID); + let name = document.createElement('div'); + name.classList.add('name'); + name.dataset.peerID = message.fromID; + name.innerHTML = title; + + let _ = action._; + if(_ == "messageActionPhoneCall") { + _ += '.' + action.type; + } + // @ts-ignore + let str = (name.innerText ? name.outerHTML + ' ' : '') + langPack[_]; + bubbleContainer.innerHTML = `
${str}
`; + + if(!multipleRender) { + this.scrollPosition.restore(); // лагает из-за этого + } + + return; + } + // time section - + let date = new Date(message.date * 1000); - let time = ('0' + date.getHours()).slice(-2) + - ':' + ('0' + date.getMinutes()).slice(-2); - + let time = ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2); + if(message.views) { bubble.classList.add('channel-post'); time = formatNumber(message.views, 1) + ' ' + time; } - + if(message.edit_date) { bubble.classList.add('is-edited'); time = 'edited ' + time; } - + let timeSpan = document.createElement('span'); timeSpan.classList.add('time'); - + let timeInner = document.createElement('div'); timeInner.classList.add('inner', 'tgico'); timeInner.innerHTML = time; - + let richText = RichTextProcessor.wrapRichText(message.message, { entities: message.totalEntities }); - + if(message.totalEntities) { let emojiEntities = message.totalEntities.filter((e: any) => e._ == 'messageEntityEmoji'); let strLength = message.message.length; let emojiStrLength = emojiEntities.reduce((acc: number, curr: any) => acc + curr.length, 0); - + if(emojiStrLength == strLength && emojiEntities.length <= 3) { let attachmentDiv = document.createElement('div'); attachmentDiv.classList.add('attachment'); - + attachmentDiv.innerHTML = richText; - + messageDiv.classList.add('message-empty'); bubble.classList.add('emoji-' + emojiEntities.length + 'x', 'emoji-big'); - + bubbleContainer.append(attachmentDiv); } else { messageDiv.innerHTML = richText; } - + /* if(strLength == emojiStrLength) { messageDiv.classList.add('emoji-only'); messageDiv.classList.add('message-empty'); @@ -1263,14 +1366,14 @@ export class AppImManager { } else { messageDiv.innerHTML = richText; } - + //messageDiv.innerHTML = 'samsung samsung samsung'; - + timeSpan.appendChild(timeInner); messageDiv.append(timeSpan); bubbleContainer.prepend(messageDiv); //bubble.prepend(timeSpan, messageDiv); // that's bad - + if(our) { if(message.pFlags.unread || message.mid < 0) this.unreadOut.push(message.mid); // message.mid < 0 added 11.02.2020 let status = ''; @@ -1281,164 +1384,164 @@ export class AppImManager { //this.log('not our message', message, message.pFlags.unread); if(message.pFlags.unread) this.unreaded.push(message.mid); } - + // media if(message.media) { let attachmentDiv = document.createElement('div'); attachmentDiv.classList.add('attachment'); - + if(!message.message) { messageDiv.classList.add('message-empty'); } - + let processingWebPage = false; switch(message.media._) { case 'messageMediaPending': { let pending = message.media; let preloader = pending.preloader as ProgressivePreloader; - + switch(pending.type) { case 'photo': { if(pending.size < 5e6) { let img = new Image(); img.src = URL.createObjectURL(pending.file); - + let {w, h} = calcImageInBox(pending.w, pending.h, 380, 380); - + attachmentDiv.style.width = w + 'px'; attachmentDiv.style.height = h + 'px'; - + attachmentDiv.append(img); preloader.attach(attachmentDiv, false); bubble.classList.add('hide-name', 'photo'); - + break; } } - + case 'audio': case 'document': { let docDiv = wrapDocument(pending, false, true); - + let icoDiv = docDiv.querySelector('.document-ico'); preloader.attach(icoDiv, false); - + messageDiv.classList.remove('message-empty'); messageDiv.append(docDiv); processingWebPage = true; break; } - + } - + break; } - + case 'messageMediaPhoto': { let photo = message.media.photo; ////////this.log('messageMediaPhoto', photo); - + bubble.classList.add('hide-name', 'photo'); - + wrapPhoto.call(this, photo, message, attachmentDiv); break; } - + case 'messageMediaWebPage': { processingWebPage = true; - + let webpage = message.media.webpage; ////////this.log('messageMediaWebPage', webpage); if(webpage._ == 'webPageEmpty') { break; } - + bubble.classList.add('webpage'); - + let box = document.createElement('div'); box.classList.add('box', 'web'); - + let quote = document.createElement('div'); quote.classList.add('quote'); - + let nameEl = document.createElement('a'); nameEl.classList.add('name'); - + let titleDiv = document.createElement('div'); titleDiv.classList.add('title'); - + let textDiv = document.createElement('div'); textDiv.classList.add('text'); - + let preview: HTMLDivElement = null; if(webpage.photo || webpage.document) { preview = document.createElement('div'); preview.classList.add('preview'); } - + let doc: any = null; if(webpage.document) { doc = webpage.document; - + if(doc.type == 'gif' || doc.type == 'video') { //if(doc.size <= 20e6) { - bubble.classList.add('video'); - wrapVideo.call(this, doc, preview, message); + bubble.classList.add('video'); + wrapVideo.call(this, doc, preview, message); //} } else { doc = null; } } - + if(webpage.photo && !doc) { bubble.classList.add('photo'); //appPhotosManager.savePhoto(webpage.photo); // hot-fix because no webpage manager - + wrapPhoto.call(this, webpage.photo, message, preview); } - + if(preview) { quote.append(preview); } - + nameEl.setAttribute('target', '_blank'); nameEl.href = webpage.url || '#'; nameEl.innerHTML = webpage.site_name ? RichTextProcessor.wrapEmojiText(webpage.site_name) : ''; - + if(webpage.description) { textDiv.innerHTML = RichTextProcessor.wrapRichText(webpage.description); } - + if(webpage.title) { titleDiv.innerHTML = RichTextProcessor.wrapRichText(webpage.title); } - + quote.append(nameEl, titleDiv, textDiv); box.append(quote); - + //bubble.prepend(box); bubbleContainer.prepend(timeSpan, box); - + //this.log('night running', bubble.scrollHeight); - + break; } - + case 'messageMediaDocument': { let doc = message.media.document; /* if(document.size > 1e6) { // 1mb break; } */ - + ////////this.log('messageMediaDocument', doc); - + if(doc.sticker && doc.size <= 1e6) { bubble.classList.add('sticker'); - + if(doc.animated) { bubble.classList.add('sticker-animated'); } - + appPhotosManager.setAttachmentSize(doc, attachmentDiv, undefined, undefined, true); let preloader = new ProgressivePreloader(attachmentDiv, false); bubbleContainer.style.height = attachmentDiv.style.height; @@ -1449,55 +1552,65 @@ export class AppImManager { this.log.warn('peer changed, canceling sticker attach'); return false; } - + return true; }, null, 'chat', false, !!message.pending || !multipleRender).then(() => { preloader.detach(); /* attachmentDiv.style.width = ''; attachmentDiv.style.height = ''; */ }); - + this.loadMediaQueuePush(load); - + break; } else if(doc.mime_type == 'video/mp4' && doc.size <= 20e6) { - ////////this.log('never get free 2', doc); - + this.log('never get free 2', doc); + if(doc.type == 'round') { bubble.classList.add('round'); } - + bubble.classList.add('video'); wrapVideo.call(this, doc, attachmentDiv, message, true, null, false, doc.type == 'round'); - + + break; + } else if(doc.mime_type == 'audio/ogg') { + let docDiv = wrapDocument(doc); + + messageDiv.classList.remove('message-empty'); + + bubble.classList.add('bubble-audio'); + messageDiv.append(docDiv); + processingWebPage = true; + break; } else { let docDiv = wrapDocument(doc); - + messageDiv.classList.remove('message-empty'); messageDiv.append(docDiv); processingWebPage = true; - + break; } } - + default: - messageDiv.classList.remove('message-empty'); - messageDiv.innerHTML = 'unrecognized media type: ' + message.media._; - messageDiv.append(timeSpan); - this.log.warn('unrecognized media type:', message.media._, message); - break; + messageDiv.classList.remove('message-empty'); + messageDiv.innerHTML = 'unrecognized media type: ' + message.media._; + messageDiv.append(timeSpan); + this.log.warn('unrecognized media type:', message.media._, message); + break; } - + if(!processingWebPage) { bubbleContainer.append(attachmentDiv); } } - + if((this.peerID < 0 && !our) || message.fwd_from || message.reply_to_mid) { // chat let title = appPeersManager.getPeerTitle(message.fwdFromID || message.fromID); - + let isHidden = message.fwd_from && !message.fwd_from.from_id && !message.fwd_from.channel_id; if(isHidden) { ///////this.log('message to render hidden', message); @@ -1506,24 +1619,19 @@ export class AppImManager { } //this.log(title); - + if(message.fwdFromID || message.fwd_from) { bubble.classList.add('forwarded'); - + if(message.savedFrom) { let fwd = document.createElement('div'); fwd.classList.add('forward'/* , 'tgico-forward' */); - fwd.innerHTML = ` - - - - - - `; + fwd.innerHTML = ` + `; bubbleContainer.append(fwd); bubble.dataset.savedFrom = message.savedFrom; } - + if(!bubble.classList.contains('sticker')) { let nameDiv = document.createElement('div'); nameDiv.classList.add('name'); @@ -1536,28 +1644,28 @@ export class AppImManager { if(message.reply_to_mid) { let originalMessage = appMessagesManager.getMessage(message.reply_to_mid); let originalPeerTitle = appPeersManager.getPeerTitle(originalMessage.fromID, true) || ''; - + /////////this.log('message to render reply', originalMessage, originalPeerTitle, bubble, message); - + // need to download separately if(originalMessage._ == 'messageEmpty') { //////////this.log('message to render reply empty, need download', message, message.reply_to_mid); appMessagesManager.wrapSingleMessage(message.reply_to_mid); this.needUpdate.push({replyMid: message.reply_to_mid, mid: message.mid}); - + originalPeerTitle = 'Loading...'; } - + if(originalMessage.mid) { bubble.setAttribute('data-original-mid', originalMessage.mid); } else { bubble.setAttribute('data-original-mid', message.reply_to_mid); } - + bubbleContainer.append(wrapReply(originalPeerTitle, originalMessage.message || '', originalMessage.media)); bubble.classList.add('is-reply'); } - + if(!bubble.classList.contains('sticker') && (peerID < 0 && peerID != message.fromID)) { let nameDiv = document.createElement('div'); nameDiv.classList.add('name'); @@ -1568,57 +1676,29 @@ export class AppImManager { } else /* if(!message.reply_to_mid) */ { bubble.classList.add('hide-name'); } - - //bubble.prepend(avatarDiv); - /* if(messageDiv.nextElementSibling) { - bubble.insertBefore(avatarDiv, messageDiv.nextElementSibling); - } else { */ - - //} } - - if(!our && this.peerID < 0 && - (!appPeersManager.isChannel(this.peerID) || appPeersManager.isMegagroup(this.peerID))) { + + if(!our && this.peerID < 0 && (!appPeersManager.isChannel(this.peerID) || appPeersManager.isMegagroup(this.peerID))) { let avatarDiv = document.createElement('div'); avatarDiv.classList.add('user-avatar'); - + /////////this.log('exec loadDialogPhoto', message); if(message.fromID) { // if no - user hidden appDialogsManager.loadDialogPhoto(avatarDiv, message.fromID); } else if(!title && message.fwd_from && message.fwd_from.from_name) { title = message.fwd_from.from_name; - + appDialogsManager.loadDialogPhoto(avatarDiv, title); } - + avatarDiv.dataset.peerID = message.fromID; - + bubbleContainer.append(avatarDiv); } } else { bubble.classList.add('hide-name'); } - - if(message._ == 'messageService') { - bubble.className = 'bubble service'; - - let action = message.action; - - let title = appPeersManager.getPeerTitle(message.fromID); - let name = document.createElement('div'); - name.classList.add('name'); - name.dataset.peerID = message.fromID; - name.innerHTML = title; - - let _ = action._; - if(_ == "messageActionPhoneCall") { - _ += '.' + action.type; - } - // @ts-ignore - let str = (name.innerText ? name.outerHTML + ' ' : '') + langPack[_]; - bubbleContainer.innerHTML = `
${str}
`; - } - + bubble.classList.add(our ? 'is-out' : 'is-in'); if(updatePosition) { if(reverse) { @@ -1627,26 +1707,27 @@ export class AppImManager { this.scrollable.append(bubble); } + this.bubbleGroups.addBubble(bubble, message, reverse); + let justDate = new Date(date.getFullYear(), date.getMonth(), date.getDate()); let dateTimestamp = justDate.getTime(); if(!(dateTimestamp in this.dateMessages)) { let str = ''; - + let today = new Date(); today.setHours(0); today.setMinutes(0); today.setSeconds(0); - + if(today < date) { str = 'Today'; } else { - const months = ['January', 'February', 'March', 'April', 'May', 'June', - 'July', 'August', 'September', 'October', 'November', 'December']; + const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; str = justDate.getFullYear() == new Date().getFullYear() ? - months[justDate.getMonth()] + ' ' + justDate.getDate() : - justDate.toISOString().split('T')[0].split('-').reverse().join('.'); + months[justDate.getMonth()] + ' ' + justDate.getDate() : + justDate.toISOString().split('T')[0].split('-').reverse().join('.'); } - + let div = document.createElement('div'); div.className = 'bubble service'; div.innerHTML = `
${str}
`; @@ -1656,7 +1737,7 @@ export class AppImManager { div, firstTimestamp: date.getTime() }; - + this.scrollable.insertBefore(div, bubble); } else { let dateMessage = this.dateMessages[dateTimestamp]; @@ -1664,67 +1745,65 @@ export class AppImManager { this.scrollable.insertBefore(dateMessage.div, bubble); } } + } else { + this.bubbleGroups.updateGroupByMessageID(message.mid); } - + /* if(bubble.classList.contains('webpage')) { this.log('night running', bubble, bubble.scrollHeight); } */ - + //return //this.scrollPosition.restore(); - + if(!multipleRender) { this.scrollPosition.restore(); // лагает из-за этого } - - //this.log('history msg', message); } - + // reverse means scroll up public getHistory(maxID = 0, reverse = false, isBackLimit = false) { let peerID = this.peerID; - + let dialog = appMessagesManager.getDialogByPeerID(peerID)[0]; if(!maxID && dialog && dialog.top_message) { maxID = dialog.top_message/* + 1 */; } - - let loadCount = Object.keys(this.bubbles).length > 0 ? - 20 : - this.scrollable.container.parentElement.scrollHeight / 30 * 1.25 | 0; - + + let loadCount = Object.keys(this.bubbles).length > 0 ? 20 : this.scrollable.container.parentElement.scrollHeight / 30 * 1.25 | 0; + /* if(testScroll) { loadCount = 1; if(Object.keys(this.bubbles).length > 0) return Promise.resolve(true); } */ - + //console.time('render getHistory'); //console.time('render history total'); - + let backLimit = 0; if(isBackLimit) { backLimit = loadCount; loadCount = 0; maxID += 1; } - + return this.getHistoryPromise = appMessagesManager.getHistory(this.peerID, maxID, loadCount, backLimit) .then((result: any) => { this.log('getHistory result by maxID:', maxID, reverse, isBackLimit, result); - + //console.timeEnd('render getHistory'); - + if(this.peerID != peerID) { this.log.warn('peer changed'); //console.timeEnd('render history total'); return Promise.reject(); } - + if(!result || !result.history) { //console.timeEnd('render history total'); return true; } - + // commented bot getProfile in getHistory! if(!result.history/* .filter((id: number) => id > 0) */.length) { if(!isBackLimit) { @@ -1733,45 +1812,45 @@ export class AppImManager { this.scrolledAllDown = true; } } - + //this.chatInner.innerHTML = ''; - + let history = result.history.slice(); if(reverse) history.reverse(); - + //console.time('render history'); - + if(!isBackLimit) { this.scrollPosition.prepareFor(reverse ? 'up' : 'down'); } - + /* for(let i = 0; i < 25; ++i) */ history.forEachReverse((msgID: number) => { let message = appMessagesManager.getMessage(msgID); - + this.renderMessage(message, reverse, true); }); - + if(!isBackLimit) { this.scrollPosition.restore(); } - + //console.timeEnd('render history'); - + this.getHistoryPromise = undefined; - + //console.timeEnd('render history total'); - + return true; }); } - + public setMutedState(muted = false) { appSidebarRight.profileElements.notificationsCheckbox.checked = !muted; appSidebarRight.profileElements.notificationsStatus.innerText = muted ? 'Disabled' : 'Enabled'; - + let peerID = this.peerID; - + this.muted = muted; if(peerID < 0) { // not human let isChannel = appPeersManager.isChannel(peerID) && !appPeersManager.isMegagroup(peerID); @@ -1785,47 +1864,47 @@ export class AppImManager { } else { this.btnMute.style.display = 'none'; } - + this.btnMenuMute.classList.remove('tgico-mute', 'tgico-unmute'); this.btnMenuMute.classList.add(muted ? 'tgico-unmute' : 'tgico-mute'); let rp = this.btnMenuMute.firstElementChild; this.btnMenuMute.innerText = muted ? 'Unmute' : 'Mute'; this.btnMenuMute.appendChild(rp); } - + public mutePeer() { let inputPeer = appPeersManager.getInputPeerByID(this.peerID); let inputNotifyPeer = { _: 'inputNotifyPeer', peer: inputPeer }; - - let settings: any = { + + let settings = { _: 'inputPeerNotifySettings', flags: 0, mute_until: 0 }; - + if(!this.muted) { settings.flags |= 1 << 2; settings.mute_until = 2147483646; } else { settings.flags |= 2; } - + apiManager.invokeApi('account.updateNotifySettings', { peer: inputNotifyPeer, settings: settings }).then(res => { this.handleUpdate({_: 'updateNotifySettings', peer: inputNotifyPeer, notify_settings: settings}); }); - + /* return apiManager.invokeApi('account.getNotifySettings', { peer: inputNotifyPeer }).then((settings: any) => { settings.flags |= 2 << 1; settings.mute_until = 2000000000; // 2147483646 - + return apiManager.invokeApi('account.updateNotifySettings', { peer: inputNotifyPeer, settings: Object.assign(settings, { @@ -1837,90 +1916,88 @@ export class AppImManager { }); */ } - + public handleUpdate(update: any) { switch(update._) { case 'updateUserTyping': case 'updateChatUserTyping': - if(this.myID == update.user_id) { - return; + if(this.myID == update.user_id) { + return; + } + + var peerID = update._ == 'updateUserTyping' ? update.user_id : -update.chat_id; + this.typingUsers[update.user_id] = peerID; + + if(!appUsersManager.hasUser(update.user_id)) { + if(update.chat_id && appChatsManager.hasChat(update.chat_id) && !appChatsManager.isChannel(update.chat_id)) { + appProfileManager.getChatFull(update.chat_id); } - - var peerID = update._ == 'updateUserTyping' ? update.user_id : -update.chat_id; - this.typingUsers[update.user_id] = peerID; - - if(!appUsersManager.hasUser(update.user_id)) { - if(update.chat_id && - appChatsManager.hasChat(update.chat_id) && - !appChatsManager.isChannel(update.chat_id)) { - appProfileManager.getChatFull(update.chat_id); - } - - //return; + + //return; + } + + appUsersManager.forceUserOnline(update.user_id); + + let dialog = appMessagesManager.getDialogByPeerID(peerID)[0]; + let currentPeer = this.peerID == peerID; + + if(this.typingTimeouts[peerID]) clearTimeout(this.typingTimeouts[peerID]); + else if(dialog) { + appDialogsManager.setTyping(dialog, appUsersManager.getUser(update.user_id)); + + if(currentPeer) { // user + this.setPeerStatus(); } - - appUsersManager.forceUserOnline(update.user_id); - - let dialog = appMessagesManager.getDialogByPeerID(peerID)[0]; - let currentPeer = this.peerID == peerID; - - if(this.typingTimeouts[peerID]) clearTimeout(this.typingTimeouts[peerID]); - else if(dialog) { - appDialogsManager.setTyping(dialog, appUsersManager.getUser(update.user_id)); - - if(currentPeer) { // user - this.setPeerStatus(); - } + } + + this.typingTimeouts[peerID] = setTimeout(() => { + this.typingTimeouts[peerID] = 0; + delete this.typingUsers[update.user_id]; + + if(dialog) { + appDialogsManager.unsetTyping(dialog); } - - this.typingTimeouts[peerID] = setTimeout(() => { - this.typingTimeouts[peerID] = 0; - delete this.typingUsers[update.user_id]; - - if(dialog) { - appDialogsManager.unsetTyping(dialog); - } - - // лень просчитывать случаи - this.setPeerStatus(); - }, 6000); - break; - + + // лень просчитывать случаи + this.setPeerStatus(); + }, 6000); + break; + case 'updateNotifySettings': { let {peer, notify_settings} = update; - + // peer was NotifyPeer peer = peer.peer; - + let peerID = appPeersManager.getPeerID(peer); - + let dialog = appMessagesManager.getDialogByPeerID(peerID)[0]; if(dialog) { dialog.notify_settings = notify_settings; } - + if(peerID == this.peerID) { let muted = notify_settings.mute_until ? new Date(notify_settings.mute_until * 1000) > new Date() : false; this.setMutedState(muted); } - + /////this.log('updateNotifySettings', peerID, notify_settings); break; } - + case 'updateChatPinnedMessage': case 'updateUserPinnedMessage': { let {id} = update; - + /////this.log('updateUserPinnedMessage', update); - + this.pinnedMsgID = id; // hz nado li tut appMessagesIDsManager.getFullMessageID(update.max_id, channelID); let peerID = update.user_id || -update.chat_id || -update.channel_id; if(peerID == this.peerID) { appMessagesManager.wrapSingleMessage(id); } - + break; } } diff --git a/src/lib/appManagers/appSidebarLeft.ts b/src/lib/appManagers/appSidebarLeft.ts index 8b825a1e..4910a646 100644 --- a/src/lib/appManagers/appSidebarLeft.ts +++ b/src/lib/appManagers/appSidebarLeft.ts @@ -87,9 +87,7 @@ class AppSidebarLeft { }; constructor() { - this.chatsPreloader = document.createElement('div'); - this.chatsPreloader.classList.add('preloader'); - putPreloader(this.chatsPreloader); + this.chatsPreloader = putPreloader(null, true); //this.chatsContainer.append(this.chatsPreloader); //this.chatsLoadCount = Math.round(document.body.scrollHeight / 70 * 1.5); @@ -98,7 +96,6 @@ class AppSidebarLeft { this.scroll.setVirtualContainer(appDialogsManager.chatList); this.scroll.onScrolledBottom = this.onChatsScroll.bind(this); appDialogsManager.chatsHidden = this.scroll.hiddenElements; - //this.scroll.container.addEventListener('scroll', this.onChatsScroll.bind(this)); this.scrollArchived = new Scrollable(this.chatsArchivedContainer as HTMLDivElement, false, true, 300, 'CLA'); this.scrollArchived.setVirtualContainer(appDialogsManager.chatListArchived); @@ -134,7 +131,7 @@ class AppSidebarLeft { for(let i = 0; i < 1000; ++i) { let li = document.createElement('li'); li.dataset.id = '' + i; - li.innerHTML = `

${i}18:33

Ильяс: Гагагагга

`; + li.innerHTML = `

${i}18:33

-_-_-_-: qweasd

`; this.scroll.append(li); } } diff --git a/src/lib/appManagers/appSidebarRight.ts b/src/lib/appManagers/appSidebarRight.ts index f0130343..79c85023 100644 --- a/src/lib/appManagers/appSidebarRight.ts +++ b/src/lib/appManagers/appSidebarRight.ts @@ -1,6 +1,6 @@ -import { horizontalMenu, formatPhoneNumber } from "../../components/misc"; +import { horizontalMenu, formatPhoneNumber, putPreloader } from "../../components/misc"; import Scrollable from '../../components/scrollable'; -import { isElementInViewport, $rootScope } from "../utils"; +import { $rootScope } from "../utils"; import appMessagesManager from "./appMessagesManager"; import appPhotosManager from "./appPhotosManager"; import appPeersManager from "./appPeersManager"; @@ -38,13 +38,13 @@ class AppSidebarRight { contentLinks: this.profileContentEl.querySelector('#content-links') as HTMLDivElement, contentAudio: this.profileContentEl.querySelector('#content-audio') as HTMLDivElement, }; - + public lastSharedMediaDiv: HTMLDivElement = null; - + private loadSidebarMediaPromises: { [type: string]: Promise } = {}; - + public sharedMediaTypes = [ 'inputMessagesFilterContacts', 'inputMessagesFilterPhotoVideo', @@ -54,7 +54,7 @@ class AppSidebarRight { ]; public sharedMediaType: string = ''; private sharedMediaSelected: HTMLDivElement = null; - + private lazyLoadQueueSidebar = new LazyLoadQueue(5); /* public minMediaID: { [type: string]: number @@ -62,17 +62,17 @@ class AppSidebarRight { public cleared: { [type: string]: boolean } = {}; - + public historiesStorage: { [peerID: number]: { [type: string]: number[] } } = {}; - + private log = logger('SR'); - + private peerID = 0; - + public sidebarScroll: Scrollable = null; private savedVirtualStates: { [id: number]: { @@ -80,25 +80,37 @@ class AppSidebarRight { paddings: any } } = {}; - + private profileTabs: HTMLUListElement; private prevTabID = -1; - + private mediaDivsByIDs: { [mid: number]: HTMLDivElement } = {}; - + constructor() { let container = this.profileContentEl.querySelector('.profile-tabs-content') as HTMLDivElement; this.profileTabs = this.profileContentEl.querySelector('.profile-tabs') as HTMLUListElement; - + this.sidebarScroll = new Scrollable(this.sidebarEl, false, true, 500, 'SR'); this.sidebarScroll.container.addEventListener('scroll', this.onSidebarScroll.bind(this)); - + this.sidebarScroll.onScrolledBottom = () => { + if(this.sharedMediaSelected && !this.sidebarScroll.hiddenElements.down.length + && this.sharedMediaSelected.childElementCount/* && false */) { + this.loadSidebarMedia(true); + } + }; + horizontalMenu(this.profileTabs, container, (id, tabContent) => { + if(this.prevTabID == id) return; + this.sharedMediaType = this.sharedMediaTypes[id]; this.sharedMediaSelected = tabContent.firstElementChild as HTMLDivElement; + if(this.prevTabID != -1 && !this.sharedMediaSelected.childElementCount) { // quick brown fix + this.loadSidebarMedia(true); + } + if(this.prevTabID != -1) { this.savedVirtualStates[this.prevTabID] = { hiddenElements: { @@ -111,254 +123,248 @@ class AppSidebarRight { } }; } - + this.prevTabID = id; - - //this.log('setVirtualContainer', id, this.sharedMediaSelected); + + this.log('setVirtualContainer', id, this.sharedMediaSelected); this.sidebarScroll.setVirtualContainer(this.sharedMediaSelected); - + if(this.savedVirtualStates[id]) { this.log(this.savedVirtualStates[id]); this.sidebarScroll.hiddenElements = this.savedVirtualStates[id].hiddenElements; this.sidebarScroll.paddings = this.savedVirtualStates[id].paddings; } }, this.onSidebarScroll.bind(this)); - - //(this.profileTabs.children[1] as HTMLLIElement).click(); // set media - + let sidebarCloseBtn = this.sidebarEl.querySelector('.sidebar-close-button') as HTMLButtonElement; sidebarCloseBtn.addEventListener('click', () => { this.toggleSidebar(false); }); - + this.sharedMedia.contentMedia.addEventListener('click', (e) => { let target = e.target as HTMLDivElement; - + let messageID = +target.getAttribute('message-id'); if(!messageID) { this.log.warn('no messageID by click on target:', target); return; } - + let message = appMessagesManager.getMessage(messageID); - + let ids = Object.keys(this.mediaDivsByIDs).map(k => +k).sort(); let idx = ids.findIndex(i => i == messageID); - + let prev = ids[idx + 1] || null; let next = ids[idx - 1] || null; - + appMediaViewer.openMedia(message, target, this.mediaDivsByIDs[prev] || null, this.mediaDivsByIDs[next] || null); }); - + this.profileElements.notificationsCheckbox.addEventListener('change', () => { - let checked = this.profileElements.notificationsCheckbox.checked; + //let checked = this.profileElements.notificationsCheckbox.checked; appImManager.mutePeer(); }); - + window.addEventListener('resize', () => { setTimeout(() => { this.sidebarScroll.onScroll(); this.onSidebarScroll(); }, 0); }); - + if(testScroll) { let div = document.createElement('div'); for(let i = 0; i < 500; ++i) { //div.insertAdjacentHTML('beforeend', `
`); div.insertAdjacentHTML('beforeend', `
${i / 3 | 0}
`); - + if((i + 1) % 3 == 0) { this.sharedMedia.contentMedia.append(div); div = document.createElement('div'); } - + div.dataset.id = '' + (i / 3 | 0); } this.sharedMedia.contentMedia.append(div); (this.profileTabs.children[1] as HTMLLIElement).click(); // set media } } - + public onSidebarScroll() { this.lazyLoadQueueSidebar.check(); - - if(this.sharedMediaSelected && !this.sidebarScroll.hiddenElements.down.length/* && false */) { - let media = Array.from(this.sharedMediaSelected.childNodes).slice(-15); - for(let div of media) { - if(isElementInViewport(div)) { - //this.log('Will load more media'); - this.loadSidebarMedia(true); - - break; - } - } - } } - + public toggleSidebar(enable?: boolean) { /////this.log('sidebarEl', this.sidebarEl, enable, isElementInViewport(this.sidebarEl)); - - /* if(enable !== undefined) { - this.sidebarEl.style.display = enable ? 'block' : 'none'; - return; - } - - this.sidebarEl.style.display = isElementInViewport(this.sidebarEl) ? 'none' : 'block'; */ + if(enable !== undefined) { - this.sidebarEl.style.width = enable ? '25%' : '0%'; + if(enable) this.sidebarEl.classList.add('active'); + else this.sidebarEl.classList.remove('active'); return; } - - this.sidebarEl.style.width = isElementInViewport(this.sidebarEl) ? '0%' : '25%'; + + if(this.sidebarEl.classList.contains('active')) { + this.sidebarEl.classList.remove('active'); + } else { + this.sidebarEl.classList.add('active'); + } } - + public loadSidebarMedia(single = false) { - if(testScroll) { + if(testScroll /* || 1 == 1 */) { return; } + //this.log('loadSidebarMedia', single, this.peerID); + let peerID = this.peerID; let typesToLoad = single ? [this.sharedMediaType] : this.sharedMediaTypes; - + if(!this.historiesStorage[peerID]) this.historiesStorage[peerID] = {}; let historyStorage = this.historiesStorage[peerID]; - + let promises = typesToLoad.map(type => { if(this.loadSidebarMediaPromises[type]) return this.loadSidebarMediaPromises[type]; - + if(!historyStorage[type]) historyStorage[type] = []; let history = historyStorage[type]; - + // заливать новую картинку сюда только после полной отправки! //let maxID = this.minMediaID[type] || 0; let maxID = history[history.length - 1] || 0; - + let ids = !maxID && appMessagesManager.historiesStorage[peerID] - ? appMessagesManager.historiesStorage[peerID].history.slice() : []; - + ? appMessagesManager.historiesStorage[peerID].history.slice() : []; + maxID = !maxID && ids.length ? ids[ids.length - 1] : maxID; //this.log('search house of glass pre', type, ids, maxID); - - return this.loadSidebarMediaPromises[type] = appMessagesManager.getSearch(peerID, '', {_: type}, maxID, 50) + + return this.loadSidebarMediaPromises[type] = appMessagesManager.getSearch(peerID, '', {_: type}, maxID, history.length ? 50 : 15) .then(value => { ids = ids.concat(value.history); history.push(...ids); - + //this.log('search house of glass', type, value, ids, this.cleared); - + if($rootScope.selectedPeerID != peerID) { this.log.warn('peer changed'); return; } - + if(this.cleared[type]) { ids = history; delete this.cleared[type]; } - - ids.forEach(mid => { - //this.minMediaID[type] = mid; + + let sharedMediaDiv: HTMLDivElement; + let messages: any[] = []; + for(let mid of ids) { let message = appMessagesManager.getMessage(mid); - if(!message.media) return; - - /*'inputMessagesFilterContacts', - 'inputMessagesFilterPhotoVideo', - 'inputMessagesFilterDocument', - 'inputMessagesFilterUrl', - 'inputMessagesFilterVoice'*/ - switch(type) { - case 'inputMessagesFilterPhotoVideo': { - /* if(!(message.media.photo || message.media.document || message.media.webpage.document)) { - this.log.error('no media!', message); - break; - } */ - + if(message.media) messages.push(message); + } + + /*'inputMessagesFilterContacts', + 'inputMessagesFilterPhotoVideo', + 'inputMessagesFilterDocument', + 'inputMessagesFilterUrl', + 'inputMessagesFilterVoice'*/ + switch(type) { + case 'inputMessagesFilterPhotoVideo': { + sharedMediaDiv = this.sharedMedia.contentMedia; + + for(let message of messages) { let media = message.media.photo || message.media.document || (message.media.webpage && message.media.webpage.document); - if(!media) { //this.log('no media!', message); - break; + continue;; } - + if(media._ == 'document' && media.type != 'video'/* && media.type != 'gif' */) { //this.log('broken video', media); - break; + continue; } - + let div = document.createElement('div'); //console.log(message, photo); - + let sizes = media.sizes || media.thumbs; if(sizes && sizes[0].bytes) { appPhotosManager.setAttachmentPreview(sizes[0].bytes, div, false, true); } /* else { this.log('no stripped size', message, media); } */ - + //this.log('inputMessagesFilterPhotoVideo', message, media); - + let load = () => appPhotosManager.preloadPhoto(media, appPhotosManager.choosePhotoSize(media, 380, 0)) .then((blob) => { if($rootScope.selectedPeerID != peerID) { this.log.warn('peer changed'); return; } - + div.style.backgroundImage = 'url(' + URL.createObjectURL(blob) + ')'; }); - - div.setAttribute('message-id', '' + mid); - + + div.setAttribute('message-id', '' + message.mid); + this.lazyLoadQueueSidebar.push({div, load}); - + this.lastSharedMediaDiv.append(div); if(this.lastSharedMediaDiv.childElementCount == 3) { - this.sharedMedia.contentMedia.append(this.lastSharedMediaDiv); + this.sidebarScroll.append(this.lastSharedMediaDiv); this.lastSharedMediaDiv = document.createElement('div'); } - - this.mediaDivsByIDs[mid] = div; - - //this.sharedMedia.contentMedia.append(div); - - break; + + this.mediaDivsByIDs[message.mid] = div; + + //sharedMediaDiv.append(div); } - case 'inputMessagesFilterDocument': { + break; + } + + case 'inputMessagesFilterDocument': { + sharedMediaDiv = this.sharedMedia.contentDocuments; + + for(let message of messages) { if(!message.media.document || message.media.document.type == 'voice') { - break; + continue; } - + let doc = message.media.document; if(doc.attributes) { if(doc.attributes.find((a: any) => a._ == "documentAttributeSticker")) { - break; + continue; } } - + //this.log('come back down to my knees', message); - + let div = wrapDocument(message.media.document, true); - this.sharedMedia.contentDocuments.append(div); - break; + this.sidebarScroll.append(div); } + break; + } + + case 'inputMessagesFilterUrl': { + sharedMediaDiv = this.sharedMedia.contentLinks; - case 'inputMessagesFilterUrl': { + for(let message of messages) { if(!message.media.webpage || message.media.webpage._ == 'webPageEmpty') { - break; + continue; } - + let webpage = message.media.webpage; let div = document.createElement('div'); let previewDiv = document.createElement('div'); previewDiv.classList.add('preview'); - + //this.log('wrapping webpage', webpage); - + if(webpage.photo) { let load = () => appPhotosManager.preloadPhoto(webpage.photo.id, appPhotosManager.choosePhotoSize(webpage.photo, 380, 0)) .then((blob) => { @@ -366,61 +372,69 @@ class AppSidebarRight { this.log.warn('peer changed'); return; } - + previewDiv.style.backgroundImage = 'url(' + URL.createObjectURL(blob) + ')'; }); - + this.lazyLoadQueueSidebar.push({div: previewDiv, load}); } else { previewDiv.innerText = (webpage.title || webpage.description || webpage.url || webpage.display_url).slice(0, 1); previewDiv.classList.add('empty'); } - + let title = webpage.rTitle || ''; let subtitle = webpage.rDescription || ''; let url = RichTextProcessor.wrapRichText(webpage.url || ''); - + if(!title) { //title = new URL(webpage.url).hostname; title = webpage.display_url.split('/', 1)[0]; } - + div.append(previewDiv); div.insertAdjacentHTML('beforeend', ` -
${title}
-
${subtitle}
-
${url}
+
${title}
+
${subtitle}
+
${url}
`); - + if(div.innerText.trim().length) { - this.sharedMedia.contentLinks.append(div); + this.sidebarScroll.append(div); } - - break; + } - - /* case 'inputMessagesFilterVoice': { - //this.log('wrapping audio', message.media); - if(!message.media || !message.media.document || message.media.document.type != 'voice') { - break; - } - - let doc = message.media.document; - - this.log('wrapping audio', doc); - - let audioDiv = wrapAudio(doc); - - this.sharedMedia.contentAudio.append(audioDiv); - - break; - } */ - default: - //console.warn('death is my friend', message); + break; + } + + /* case 'inputMessagesFilterVoice': { + //this.log('wrapping audio', message.media); + if(!message.media || !message.media.document || message.media.document.type != 'voice') { break; + } + + let doc = message.media.document; + + this.log('wrapping audio', doc); + + let audioDiv = wrapAudio(doc); + + this.sharedMedia.contentAudio.append(audioDiv); + + break; + } */ + + default: + //console.warn('death is my friend', message); + break; + } + + if(sharedMediaDiv) { + let parent = sharedMediaDiv.parentElement; + if(parent.lastElementChild.classList.contains('preloader')) { + parent.lastElementChild.remove(); } - }); + } this.onSidebarScroll(); }).then(() => { @@ -430,26 +444,17 @@ class AppSidebarRight { this.loadSidebarMediaPromises[type] = null; }); }); - + return promises; } - + public fillProfileElements() { let peerID = this.peerID = $rootScope.selectedPeerID; this.loadSidebarMediaPromises = {}; this.lastSharedMediaDiv = document.createElement('div'); - + //this.log('fillProfileElements'); - - this.savedVirtualStates = {}; - this.prevTabID = -1; - this.sidebarScroll.setVirtualContainer(null); - (this.profileTabs.children[1] as HTMLLIElement).click(); // set media - - if(this.sharedMediaSelected) { - //this.sidebarScroll.setVirtualContainer(this.sharedMediaSelected); - } - + this.profileContentEl.parentElement.scrollTop = 0; this.profileElements.bio.style.display = 'none'; this.profileElements.phone.style.display = 'none'; @@ -457,40 +462,48 @@ class AppSidebarRight { this.profileElements.notificationsRow.style.display = ''; this.profileElements.notificationsCheckbox.checked = true; this.profileElements.notificationsStatus.innerText = 'Enabled'; - + this.mediaDivsByIDs = {}; - + this.lazyLoadQueueSidebar.clear(); - + Object.keys(this.sharedMedia).forEach(key => { this.sharedMedia[key].innerHTML = ''; + + let parent = this.sharedMedia[key].parentElement; + if(!parent.querySelector('.preloader')) { + putPreloader(parent, true); + } }); - + this.sharedMediaTypes.forEach(type => { //this.minMediaID[type] = 0; this.cleared[type] = true; }); - + + this.savedVirtualStates = {}; + this.prevTabID = -1; + this.sidebarScroll.setVirtualContainer(null); + (this.profileTabs.children[1] as HTMLLIElement).click(); // set media + let setText = (text: string, el: HTMLDivElement) => { el.style.display = ''; if(el.childElementCount > 1) { el.firstElementChild.remove(); } - + let p = document.createElement('p'); p.innerHTML = text; el.prepend(p); }; - + // username if(peerID != appImManager.myID) { let username = appPeersManager.getPeerUsername(peerID); if(username) { setText(appPeersManager.getPeerUsername(peerID), this.profileElements.username); } - } - - if(peerID != appImManager.myID) { + let dialog: any = appMessagesManager.getDialogByPeerID(peerID); if(dialog.length) { dialog = dialog[0]; @@ -498,57 +511,57 @@ class AppSidebarRight { if(dialog.notify_settings && dialog.notify_settings.mute_until) { muted = new Date(dialog.notify_settings.mute_until * 1000) > new Date(); } - + appImManager.setMutedState(muted); } } else { this.profileElements.notificationsRow.style.display = 'none'; } - + if(peerID > 0) { let user = appUsersManager.getUser(peerID); if(user.phone && peerID != appImManager.myID) { setText('+' + formatPhoneNumber(user.phone).formatted, this.profileElements.phone); } - + appProfileManager.getProfile(peerID, true).then(userFull => { if(this.peerID != peerID) { this.log.warn('peer changed'); return; } - + if(userFull.rAbout && peerID != appImManager.myID) { setText(userFull.rAbout, this.profileElements.bio); } - + //this.log('userFull', userFull); - + if(userFull.pinned_msg_id) { // request pinned message appImManager.pinnedMsgID = userFull.pinned_msg_id; appMessagesManager.wrapSingleMessage(userFull.pinned_msg_id); } - + this.sidebarScroll.getScrollTopOffset(); }); } else { let chat = appPeersManager.getPeer(peerID); - + appProfileManager.getChatFull(chat.id).then((chatFull: any) => { if(this.peerID != peerID) { this.log.warn('peer changed'); return; } - + //this.log('chatInfo res 2:', chatFull); - + if(chatFull.about) { setText(RichTextProcessor.wrapRichText(chatFull.about), this.profileElements.bio); } - + this.sidebarScroll.getScrollTopOffset(); }); } - + this.sidebarScroll.getScrollTopOffset(); //this.loadSidebarMedia(); } diff --git a/src/lib/ckin.js b/src/lib/ckin.js index 8070fa4b..6de7eb17 100644 --- a/src/lib/ckin.js +++ b/src/lib/ckin.js @@ -3,18 +3,6 @@ NodeList.prototype.forEach = Array.prototype.forEach; })(); -String.prototype.toHHMMSS = function(leadZero) { - let sec_num = parseInt(this, 10); - let hours = Math.floor(sec_num / 3600); - let minutes = Math.floor((sec_num - (hours * 3600)) / 60); - let seconds = sec_num - (hours * 3600) - (minutes * 60); - - if(hours < 10) hours = "0" + hours; - if(minutes < 10) minutes = leadZero ? "0" + minutes : minutes; - if(seconds < 10) seconds = "0" + seconds; - return minutes + ':' + seconds; -} - function stylePlayer(player, video) { let skin = attachSkin(video.dataset.ckin); player.classList.add(skin); diff --git a/src/lib/lottieLoader.ts b/src/lib/lottieLoader.ts index 835fd146..7ebcd1d4 100644 --- a/src/lib/lottieLoader.ts +++ b/src/lib/lottieLoader.ts @@ -33,9 +33,9 @@ class LottieLoader { for(let i = length - 1; i >= 0; --i) { let {animation, container, paused, autoplay, canvas} = animations[i]; - if(canvas && isElementInViewport(container)) { + if(canvas) { let c = container.firstElementChild as HTMLCanvasElement; - if(!c.height && !c.width) { + if(!c.height && !c.width && isElementInViewport(container)) { //console.log('lottie need resize'); animation.resize(); } diff --git a/src/lib/polyfill.ts b/src/lib/polyfill.ts index dafe952a..81cf9c88 100644 --- a/src/lib/polyfill.ts +++ b/src/lib/polyfill.ts @@ -53,6 +53,23 @@ Array.prototype.forEachReverse = function(callback: (value: T, index?: number } }; +Array.prototype.findAndSplice = function(verify: (value: T, index?: number, array?: Array) => boolean) { + let index = this.findIndex(verify); + return index !== -1 ? this.splice(index, 1)[0] : undefined; +}; + +String.prototype.toHHMMSS = function(leadZero = false) { + let sec_num = parseInt(this + '', 10); + let hours: any = Math.floor(sec_num / 3600); + let minutes: any = Math.floor((sec_num - (hours * 3600)) / 60); + let seconds: any = sec_num - (hours * 3600) - (minutes * 60); + + if(hours < 10) hours = "0" + hours; + if(minutes < 10) minutes = leadZero ? "0" + minutes : minutes; + if(seconds < 10) seconds = "0" + seconds; + return minutes + ':' + seconds; +} + declare global { interface Uint8Array { hex: string; @@ -62,5 +79,10 @@ declare global { interface Array { forEachReverse(callback: (value: T, index?: number, array?: Array) => void): void; + findAndSplice(verify: (value: T, index?: number, array?: Array) => boolean): T; + } + + interface String { + toHHMMSS(leadZero?: boolean): string; } } diff --git a/src/lib/utils.js b/src/lib/utils.js index 563116b7..6597269b 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -297,7 +297,7 @@ export function getSelectedText() { export const $rootScope = { $broadcast: (name/* : string */, detail/*? : any */) => { - ////console.log(dT(), 'Broadcasting ' + name + ' event, with args:', detail); + //console.log(dT(), 'Broadcasting ' + name + ' event, with args:', detail); //console.trace(); let myCustomEvent = new CustomEvent(name, {detail}); document.dispatchEvent(myCustomEvent); diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index 5eedc733..165d6439 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -370,8 +370,8 @@ cursor: pointer; background: none!important; box-shadow: none; - max-width: 300px; - max-height: 300px; + /* max-width: 300px; + max-height: 300px; */ } img { @@ -387,6 +387,13 @@ } } + &.sticker { + .bubble__container { + max-width: 200px; + max-height: 200px; + } + } + &.round { .attachment { max-width: 200px; @@ -702,11 +709,11 @@ /* padding-bottom: 4px; */ color: $darkblue; font-size: .9rem; - width: max-content; max-width: 100%; overflow: hidden; text-overflow: ellipsis; - white-space: nowrap; + //width: max-content; + //white-space: nowrap; } &:not(.webpage) { @@ -732,16 +739,25 @@ margin-top: 6px; } - &:not(.sticker):not(.emoji-big):not(.round):last-child .bubble__container:after { + &:not(.sticker):not(.emoji-big):not(.round).is-group-last .bubble__container:after { position: absolute; - bottom: -1px; + bottom: 0; width: 11px; height: 20px; background-repeat: no-repeat repeat; content: ''; background-size: 11px 20px; + background-position-y: 1px; } } + + .bubble-audio.is-in .time { + width: inherit; + } + + .bubble-audio.is-out .time { + width: inherit; + } /* .bubble + .bubble { margin-top: 5px; @@ -761,11 +777,11 @@ border-radius: 6px 12px 12px 6px; } - &:first-child .bubble__container { + &.is-group-first .bubble__container { border-radius: 12px 12px 12px 6px; } - &:last-child .bubble__container { + &.is-group-last .bubble__container { border-radius: 6px 12px 12px 0px; //border-radius: 12px 12px 12px 0px; @@ -775,7 +791,7 @@ } } - &:first-child:last-child .bubble__container { + &.is-group-first.is-group-last .bubble__container { border-radius: 12px 12px 12px 0px; } @@ -835,11 +851,11 @@ border-radius: 12px 6px 6px 12px; } - &:first-child .bubble__container { + &.is-group-first .bubble__container { border-radius: 12px 12px 6px 12px; } - &:last-child .bubble__container { + &.is-group-last .bubble__container { border-radius: 12px 6px 0px 12px; &:after { @@ -848,7 +864,7 @@ } } - &:first-child:last-child .bubble__container { + &.is-group-first.is-group-last .bubble__container { border-radius: 12px 12px 0px 12px; } diff --git a/src/scss/partials/_rightSIdebar.scss b/src/scss/partials/_rightSIdebar.scss new file mode 100644 index 00000000..3293c30d --- /dev/null +++ b/src/scss/partials/_rightSIdebar.scss @@ -0,0 +1,286 @@ +.profile-container { + width: 0%; + /* grid-column: 3; */ + position: relative; + transition: .2s ease-in-out; + + > .scrollable { + min-width: 25vw; + display: flex; + flex-direction: column; + } + + @media (min-width: $large-screen) { + > .scrollable { + min-width: calc(#{$large-screen} / 4 - 1px); + } + } + + &:not(.active) { + border-left-width: 0; + } + + &.active { + width: 25%; + } + + .sidebar-header { + flex: 0 0 auto; + } +} + +.profile-content { + flex: 1 1 auto; + display: flex; + flex-direction: column; + + .profile-name { + text-align: center; + font-size: 23px; + font-weight: 500; + margin-bottom: 3px; + + span.emoji { + vertical-align: inherit; + min-width: min-content; + } + } + + .profile-subtitle { + text-align: center; + color: $darkgrey; + font-size: 14px; + + &.online { + color: $darkblue; + } + } + + .profile-row { + display: flex; + width: 100%; + flex-direction: column; + padding-left: 80px; + padding-top: 2px; + padding-right: 12px; + font-size: 15px; + position: relative; + margin-top: 1.75rem; + + &:before { + position: absolute; + left: 24px; + /* top: 0; */ + font-size: 24px; + color: $darkgrey; + } + + p { + color: #000; + margin: 0; + } + + &-bio { + .emoji { + width: 24px; + height: 24px; + } + } + } + + p.profile-row-label { + color: $placeholder-color; + font-size: 14px; + margin-top: 1px; + } + + .user-avatar { + width: 120px; + height: 120px; + margin: 0 auto 20px; + font-size: 4rem!important; + } + + [type="checkbox"] + span { + padding-left: 54px; + margin-left: -54px; + } + + .content-container { + width: 100%; + max-width: 100%; + overflow: hidden; + flex: 1; + } + + .profile-tabs { + margin-top: 40px; + } + + .profile-tabs-content { + height: 100%; + /* width: 500%; + margin-left: -100%; + */ + /* > div { + height: 0; + + &.active { + height: auto; + } + } */ + + > div { + height: 100%; + position: relative; + } + + /* > div > div:not(.scroll-padding) { + height: 100%; + } */ + + .preloader { + padding: 0; + position: absolute; + height: 100%; + + > svg { + height: 50px; + width: 50px; + } + } + + #content-media { + width: 100%; + display: flex; + flex-direction: column; + padding-top: 4px; + + > div { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-auto-rows: max-content; + grid-gap: 3.5px; + place-items: start; + padding-top: 3.5px; + + > div { + width: 100%; + cursor: pointer; + + background-repeat: no-repeat; + background-size: cover; + background-position: center center; + + display: flex; + background-color: #cecece; + justify-content: center; + align-items: center; + + &::before { + content: ""; + display: inline-block; + width: 1px; + height: 0; + padding-bottom: 100%; + } + } + } + } + + #content-docs { + padding: 7px 20px; + + .document { + padding-left: 4rem; + padding-right: 1rem; + //height: 54px; + height: calc(50px + 1.5rem); + + &-ico, &-download { + width: 48px; + height: 48px; + } + + /* & + .document { + margin-top: 1.5rem; + } */ + } + + .document-name { + font-weight: normal; + + width: 100%; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + } + } + + #content-links { + padding: 0 30px 15px 15px; + + > div { + display: flex; + flex-direction: column; + margin-top: 20px; + margin-left: 5px; + padding-bottom: 2px; + //padding-bottom: 10px; + position: relative; + padding-left: 60px; + overflow: hidden; + //min-height: 48px; + min-height: 58px; + + .preview { + height: 48px; + width: 48px; + border-radius: 5px; + overflow: hidden; + position: absolute; + left: 0; + top: 0; + background-repeat: no-repeat; + background-size: cover; + background-position: center center; + + &.empty { + display: flex; + align-items: center; + justify-content: center; + font-size: 2rem; + color: #fff; + text-transform: uppercase; + background-color: $blue; + } + } + + .url { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + } + } + + .title { + font-size: 16px; + margin-top: 3px; + } + + .subtitle { + font-size: 14px; + } + } + + #content-audio { + padding: 0 15px 15px 15px; + + > div { + margin-top: 15px; + padding-bottom: 10px; + min-height: 60px; + } + } + } +} diff --git a/src/scss/partials/_sidebar.scss b/src/scss/partials/_sidebar.scss index 99247fe6..757da4ea 100644 --- a/src/scss/partials/_sidebar.scss +++ b/src/scss/partials/_sidebar.scss @@ -29,234 +29,3 @@ margin-left: .5rem; } } - -.profile-content { - .profile-name { - text-align: center; - font-size: 23px; - font-weight: 500; - margin: 3px 0; - - span.emoji { - vertical-align: inherit; - min-width: min-content; - } - } - - .profile-subtitle { - text-align: center; - color: $darkgrey; - font-size: 14px; - margin: 0 0 18px; - - &.online { - color: $darkblue; - } - } - - .profile-row { - display: flex; - width: 100%; - flex-direction: column; - padding-left: 80px; - padding-top: 2px; - padding-right: 12px; - font-size: 15px; - position: relative; - margin: 1.75rem 0; - - &:before { - position: absolute; - left: 24px; - /* top: 0; */ - font-size: 24px; - color: $darkgrey; - } - - p { - color: #000; - margin: 0; - } - - .profile-row-label { - color: $placeholder-color; - font-size: 14px; - margin-top: 1px; - } - } - - .profile-row-bio { - .emoji { - width: 24px; - height: 24px; - } - } - - .user-avatar { - width: 120px; - height: 120px; - margin: 0 auto 20px; - font-size: 4rem!important; - } - - [type="checkbox"] + span { - padding-left: 54px; - margin-left: -54px; - } - - .content-container { - width: 100%; - max-width: 100%; - overflow: hidden; - } - - .profile-tabs { - margin-top: 40px; - } - - .profile-tabs-content { - /* width: 500%; - margin-left: -100%; - */ - /* > div { - height: 0; - - &.active { - height: auto; - } - } */ - - #content-media { - width: 100%; - display: flex; - flex-direction: column; - padding-top: 4px; - - > div { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - grid-auto-rows: max-content; - grid-gap: 3.5px; - place-items: start; - padding-top: 3.5px; - - > div { - width: 100%; - cursor: pointer; - - background-repeat: no-repeat; - background-size: cover; - background-position: center center; - - display: flex; - background-color: #cecece; - justify-content: center; - align-items: center; - - &::before { - content: ""; - display: inline-block; - width: 1px; - height: 0; - padding-bottom: 100%; - } - } - } - } - - #content-docs { - padding: 7px 20px; - - .document { - padding-left: 4rem; - padding-right: 1rem; - //height: 54px; - height: calc(50px + 1.5rem); - - &-ico, &-download { - width: 48px; - height: 48px; - } - - /* & + .document { - margin-top: 1.5rem; - } */ - } - - .document-name { - font-weight: normal; - - width: 100%; - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - } - } - - #content-links { - padding: 0 30px 15px 15px; - - > div { - display: flex; - flex-direction: column; - margin-top: 20px; - margin-left: 5px; - padding-bottom: 2px; - //padding-bottom: 10px; - position: relative; - padding-left: 60px; - overflow: hidden; - //min-height: 48px; - min-height: 58px; - - .preview { - height: 48px; - width: 48px; - border-radius: 5px; - overflow: hidden; - position: absolute; - left: 0; - top: 0; - background-repeat: no-repeat; - background-size: cover; - background-position: center center; - - &.empty { - display: flex; - align-items: center; - justify-content: center; - font-size: 2rem; - color: #fff; - text-transform: uppercase; - background-color: $blue; - } - } - - .url { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - } - } - - .title { - font-size: 16px; - margin-top: 3px; - } - - .subtitle { - font-size: 14px; - } - } - - #content-audio { - padding: 0 15px 15px 15px; - - > div { - margin-top: 15px; - padding-bottom: 10px; - min-height: 60px; - } - } - } -} diff --git a/src/scss/style.scss b/src/scss/style.scss index 2d193472..43aeb85b 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -29,11 +29,15 @@ $bg: #ffffff; $text-size: 16px; $time-size: 12px; +//$large-screen: 1680px; +$large-screen: 16800px; + @import "partials/ico"; @import "partials/chatlist"; @import "partials/chat"; @import "partials/sidebar"; @import "partials/leftSidebar"; +@import "partials/rightSidebar"; @import "partials/mediaViewer"; @import "partials/ckin"; @import "partials/emojiDropdown"; @@ -59,7 +63,18 @@ button, input, optgroup, select, textarea, html { height: 100vh; min-height: 100vh; width: 100%; - min-width: 100%; + //min-width: 100%; + margin: 0 auto; + max-width: $large-screen; + + @media (min-width: $large-screen) { + border-top-width: 0; + border-bottom-width: 0; + border-left-width: 1px; + border-right-width: 1px; + border-style: solid; + border-color: #DADCE0; + } } .container { @@ -481,13 +496,13 @@ input { position: relative; padding-left: 67px; min-height: 58px; - max-width: 286px; + max-width: 244px; overflow: visible!important; &-toggle, &-download { border-radius: 50%; background-color: $blue; - font-size: 2.2rem; + font-size: 2.3rem; align-items: center; } @@ -1563,19 +1578,6 @@ div.scrollable::-webkit-scrollbar-thumb { flex: 1; } - .profile-container { - //display: none; - width: 0%; - /* grid-column: 3; */ - position: relative; - - transition: .2s ease-in-out; - - > .scrollable { - min-width: 25vw; - } - } - .preloader { width: 50px; height: 50px; diff --git a/tsconfig.json b/tsconfig.json index 83a369f9..b345a110 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -65,9 +65,9 @@ "node_modules", "public", "coverage", - "./src/lib/StackBlur.js", + /* "./src/lib/StackBlur.js", "./src/lib/*.js", "./src/*.js", - "*.js", + "*.js", */ ] } diff --git a/webpack.prod.js b/webpack.prod.js index b181c6bc..5785e64b 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -59,7 +59,7 @@ module.exports = merge(common, { files.forEach(file => { //console.log('to unlink 1:', file); - if(file.includes('mitm.') || file.includes('sw.js')) return; + if(file.includes('mitm.') || file.includes('sw.js') || file.includes('.xml') || file.includes('.webmanifest')) return; let p = path.resolve(buildDir + file); if(!newlyCreatedAssets[file] && ['.gz', '.js'].find(ext => file.endsWith(ext)) !== undefined) {