Several little fixes
Gif Safari & round videos fix
This commit is contained in:
parent
8000ed3285
commit
82043e4c4a
@ -1,22 +1,23 @@
|
||||
import appImManager from "../lib/appManagers/appImManager";
|
||||
import { horizontalMenu, renderImageFromUrl, putPreloader } from "./misc";
|
||||
import { renderImageFromUrl, putPreloader } from "./misc";
|
||||
import lottieLoader from "../lib/lottieLoader";
|
||||
//import Scrollable from "./scrollable";
|
||||
import Scrollable from "./scrollable_new";
|
||||
import { findUpTag, whichChild, calcImageInBox, emojiUnicode, $rootScope } from "../lib/utils";
|
||||
import { findUpTag, whichChild, calcImageInBox, emojiUnicode, $rootScope, cancelEvent, findUpClassName } from "../lib/utils";
|
||||
import { RichTextProcessor } from "../lib/richtextprocessor";
|
||||
import appStickersManager, { MTStickerSet } from "../lib/appManagers/appStickersManager";
|
||||
//import apiManager from '../lib/mtproto/apiManager';
|
||||
import apiManager from '../lib/mtproto/mtprotoworker';
|
||||
import LazyLoadQueue from "./lazyLoadQueue";
|
||||
import { wrapSticker } from "./wrappers";
|
||||
import { wrapSticker, wrapVideo } from "./wrappers";
|
||||
import appDocsManager from "../lib/appManagers/appDocsManager";
|
||||
import ProgressivePreloader from "./preloader";
|
||||
import ProgressivePreloader from "./preloader_new";
|
||||
import Config, { touchSupport } from "../lib/config";
|
||||
import { MTDocument } from "../types";
|
||||
import animationIntersector from "./animationIntersector";
|
||||
import appSidebarRight from "../lib/appManagers/appSidebarRight";
|
||||
import appStateManager from "../lib/appManagers/appStateManager";
|
||||
import { horizontalMenu } from "./horizontalMenu";
|
||||
|
||||
export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown';
|
||||
|
||||
@ -150,7 +151,7 @@ class EmojiTab implements EmoticonsTab {
|
||||
|
||||
const spanEmoji = document.createElement('span');
|
||||
const kek = RichTextProcessor.wrapEmojiText(emoji);
|
||||
|
||||
|
||||
/* if(!kek.includes('emoji')) {
|
||||
console.log(emoji, kek, spanEmoji, emoji.length, new TextEncoder().encode(emoji), emojiUnicode(emoji));
|
||||
return;
|
||||
@ -159,6 +160,10 @@ class EmojiTab implements EmoticonsTab {
|
||||
//console.log(kek);
|
||||
|
||||
spanEmoji.innerHTML = kek;
|
||||
|
||||
if(spanEmoji.firstElementChild) {
|
||||
(spanEmoji.firstElementChild as HTMLImageElement).setAttribute('loading', 'lazy');
|
||||
}
|
||||
|
||||
//spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement;
|
||||
//spanEmoji.setAttribute('emoji', emoji);
|
||||
@ -561,10 +566,10 @@ class GifsTab implements EmoticonsTab {
|
||||
preloader.remove();
|
||||
|
||||
for(let i = 0, length = res.gifs.length; i < length;) {
|
||||
let gif = res.gifs[i];
|
||||
const doc = res.gifs[i];
|
||||
|
||||
let gifWidth = gif.w;
|
||||
let gifHeight = gif.h;
|
||||
let gifWidth = doc.w;
|
||||
let gifHeight = doc.h;
|
||||
if(gifHeight < height) {
|
||||
gifWidth = height / gifHeight * gifWidth;
|
||||
gifHeight = height;
|
||||
@ -588,28 +593,103 @@ class GifsTab implements EmoticonsTab {
|
||||
//console.log('gif:', gif, w, h);
|
||||
|
||||
let div = document.createElement('div');
|
||||
div.classList.add('gif', 'fade-in-transition');
|
||||
div.style.width = w + 'px';
|
||||
div.style.opacity = '0';
|
||||
//div.style.height = h + 'px';
|
||||
div.dataset.docID = gif.id;
|
||||
div.dataset.docID = doc.id;
|
||||
|
||||
masonry.append(div);
|
||||
|
||||
let preloader = new ProgressivePreloader(div);
|
||||
EmoticonsDropdown.lazyLoadQueue.push({
|
||||
//let preloader = new ProgressivePreloader(div);
|
||||
|
||||
const posterURL = appDocsManager.getThumbURL(doc, false);
|
||||
let img: HTMLImageElement;
|
||||
if(posterURL) {
|
||||
img = new Image();
|
||||
img.src = posterURL;
|
||||
}
|
||||
|
||||
let mouseOut = false;
|
||||
const onMouseOver = (e: MouseEvent) => {
|
||||
//console.log('onMouseOver', doc.id);
|
||||
//cancelEvent(e);
|
||||
mouseOut = false;
|
||||
|
||||
wrapVideo({
|
||||
doc,
|
||||
container: div,
|
||||
//lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue,
|
||||
//group: EMOTICONSSTICKERGROUP,
|
||||
noInfo: true,
|
||||
});
|
||||
|
||||
const video = div.querySelector('video');
|
||||
video.addEventListener('loadeddata', () => {
|
||||
div.style.opacity = '';
|
||||
if(!mouseOut) {
|
||||
img && img.remove();
|
||||
} else {
|
||||
div.innerHTML = '';
|
||||
div.append(img);
|
||||
}
|
||||
}, {once: true});
|
||||
};
|
||||
|
||||
((posterURL ? renderImageFromUrl(img, posterURL) : Promise.resolve()) as Promise<any>).then(() => {
|
||||
if(img) {
|
||||
div.append(img);
|
||||
div.style.opacity = '';
|
||||
}
|
||||
|
||||
div.addEventListener('mouseover', onMouseOver, {once: true});
|
||||
div.addEventListener('mouseout', (e) => {
|
||||
const toElement = (e as any).toElement as Element;
|
||||
//console.log('onMouseOut', doc.id, e);
|
||||
if(findUpClassName(toElement, 'gif') == div) {
|
||||
return;
|
||||
}
|
||||
|
||||
//cancelEvent(e);
|
||||
|
||||
mouseOut = true;
|
||||
|
||||
div.innerHTML = '';
|
||||
div.append(img);
|
||||
div.addEventListener('mouseover', onMouseOver, {once: true});
|
||||
});
|
||||
});
|
||||
|
||||
/* wrapVideo({
|
||||
doc,
|
||||
container: div,
|
||||
lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue,
|
||||
group: EMOTICONSSTICKERGROUP,
|
||||
noInfo: true,
|
||||
}); */
|
||||
|
||||
/* EmoticonsDropdown.lazyLoadQueue.push({
|
||||
div,
|
||||
load: () => {
|
||||
let promise = appDocsManager.downloadDoc(gif);
|
||||
preloader.attach(div, true, promise);
|
||||
|
||||
promise.then(blob => {
|
||||
const download = appDocsManager.downloadDocNew(doc);
|
||||
|
||||
let thumbSize: string, posterURL: string;
|
||||
if(doc.thumbs?.length) {
|
||||
thumbSize = doc.thumbs[0].type;
|
||||
posterURL = appDocsManager.getThumbURL(doc, thumbSize);
|
||||
}
|
||||
|
||||
preloader.attach(div, true, appDocsManager.getInputFileName(doc, thumbSize));
|
||||
|
||||
download.promise.then(blob => {
|
||||
preloader.detach();
|
||||
|
||||
div.innerHTML = `<video autoplay="true" muted="true" loop="true" src="${gif.url}" type="video/mp4"></video>`;
|
||||
div.innerHTML = `<video autoplay="true" muted="true" loop="true" src="${doc.url}" poster="${posterURL}" type="${doc.mime_type}"></video>`;
|
||||
});
|
||||
|
||||
return promise;
|
||||
return download.promise;
|
||||
}
|
||||
});
|
||||
}); */
|
||||
}
|
||||
});
|
||||
|
||||
@ -661,6 +741,11 @@ class EmoticonsDropdown {
|
||||
//this.displayTimeout = setTimeout(() => {
|
||||
if(firstTime) {
|
||||
this.toggleEl.onmouseout = this.element.onmouseout = (e) => {
|
||||
const toElement = (e as any).toElement as Element;
|
||||
if(findUpClassName(toElement, 'emoji-dropdown')) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(this.displayTimeout);
|
||||
this.displayTimeout = setTimeout(() => {
|
||||
this.toggle();
|
||||
|
159
src/components/horizontalMenu.ts
Normal file
159
src/components/horizontalMenu.ts
Normal file
@ -0,0 +1,159 @@
|
||||
import { findUpTag, whichChild } from "../lib/utils";
|
||||
|
||||
function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) {
|
||||
/* if(toRight) {
|
||||
//prevTabContent.style.filter = `brightness(80%)`;
|
||||
prevTabContent.style.transform = `translateX(-25%)`;
|
||||
tabContent.style.transform = `translateX(20%)`;
|
||||
} else {
|
||||
//tabContent.style.filter = `brightness(80%)`;
|
||||
tabContent.style.transform = `translateX(-25%)`;
|
||||
prevTabContent.style.transform = `translateX(20%)`;
|
||||
} */
|
||||
if(toRight) {
|
||||
prevTabContent.style.filter = `brightness(80%)`;
|
||||
prevTabContent.style.transform = `translateX(-25%)`;
|
||||
tabContent.style.transform = `translateX(100%)`;
|
||||
} else {
|
||||
tabContent.style.filter = `brightness(80%)`;
|
||||
tabContent.style.transform = `translateX(-25%)`;
|
||||
prevTabContent.style.transform = `translateX(100%)`;
|
||||
}
|
||||
|
||||
tabContent.classList.add('active');
|
||||
void tabContent.offsetWidth; // reflow
|
||||
|
||||
tabContent.style.transform = '';
|
||||
tabContent.style.filter = '';
|
||||
}
|
||||
|
||||
function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) {
|
||||
if(toRight) {
|
||||
tabContent.style.transform = `translateX(100%)`;
|
||||
prevTabContent.style.transform = `translateX(-100%)`;
|
||||
} else {
|
||||
tabContent.style.transform = `translateX(-100%)`;
|
||||
prevTabContent.style.transform = `translateX(100%)`;
|
||||
}
|
||||
|
||||
tabContent.classList.add('active');
|
||||
void tabContent.offsetWidth; // reflow
|
||||
|
||||
tabContent.style.transform = '';
|
||||
}
|
||||
|
||||
export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?: (id: number, tabContent: HTMLDivElement) => void, onTransitionEnd?: () => void, transitionTime = 300) {
|
||||
const hideTimeouts: {[id: number]: number} = {};
|
||||
let prevTabContent: HTMLElement = null;
|
||||
let prevId = -1;
|
||||
|
||||
const selectTab = (id: number) => {
|
||||
if(id == prevId) return false;
|
||||
|
||||
//console.log('selectTab id:', id);
|
||||
|
||||
const p = prevTabContent;
|
||||
const tabContent = content.children[id] as HTMLElement;
|
||||
|
||||
if(content.dataset.slider == 'none') {
|
||||
if(p) {
|
||||
p.classList.remove('active');
|
||||
}
|
||||
|
||||
tabContent.classList.add('active');
|
||||
|
||||
prevId = id;
|
||||
prevTabContent = tabContent;
|
||||
|
||||
if(onTransitionEnd) onTransitionEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
const toRight = prevId < id;
|
||||
if(prevId != -1) {
|
||||
if(tabs || content.dataset.slider == 'tabs') {
|
||||
slideTabs(tabContent, prevTabContent, toRight);
|
||||
} else {
|
||||
slideNavigation(tabContent, prevTabContent, toRight);
|
||||
}
|
||||
} else {
|
||||
tabContent.classList.add('active');
|
||||
}
|
||||
|
||||
const _prevId = prevId;
|
||||
if(hideTimeouts.hasOwnProperty(id)) clearTimeout(hideTimeouts[id]);
|
||||
if(p/* && false */) {
|
||||
hideTimeouts[_prevId] = setTimeout(() => {
|
||||
p.style.transform = '';
|
||||
p.style.filter = '';
|
||||
p.classList.remove('active');
|
||||
|
||||
delete hideTimeouts[_prevId];
|
||||
|
||||
if(onTransitionEnd) onTransitionEnd();
|
||||
}, /* 420 */transitionTime);
|
||||
}
|
||||
|
||||
prevId = id;
|
||||
prevTabContent = tabContent;
|
||||
};
|
||||
|
||||
if(tabs) {
|
||||
let activeStripe: HTMLSpanElement;
|
||||
if(!tabs.classList.contains('no-stripe')) {
|
||||
activeStripe = document.createElement('span');
|
||||
activeStripe.classList.add('menu-horizontal__stripe');
|
||||
|
||||
tabs.append(activeStripe);
|
||||
}
|
||||
|
||||
const tagName = 'LI';//tabs.firstElementChild.tagName;
|
||||
tabs.addEventListener('click', function(e) {
|
||||
let target = e.target as HTMLElement;
|
||||
|
||||
if(target.tagName != tagName) {
|
||||
target = findUpTag(target, tagName);
|
||||
}
|
||||
|
||||
//console.log('tabs click:', target);
|
||||
|
||||
if(!target) return false;
|
||||
|
||||
let id: number;
|
||||
if(target.dataset.tab) {
|
||||
id = +target.dataset.tab;
|
||||
if(id == -1) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
id = whichChild(target);
|
||||
}
|
||||
|
||||
const tabContent = content.children[id] as HTMLDivElement;
|
||||
|
||||
if(onClick) onClick(id, tabContent);
|
||||
if(target.classList.contains('active') || id == prevId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const prev = tabs.querySelector(tagName.toLowerCase() + '.active') as HTMLElement;
|
||||
prev && prev.classList.remove('active');
|
||||
|
||||
if(activeStripe) {
|
||||
const tabsRect = tabs.getBoundingClientRect();
|
||||
const targetRect = target.getBoundingClientRect();
|
||||
const width = 50;
|
||||
activeStripe.style.cssText = `width: ${width}px; transform: translateX(${targetRect.left - tabsRect.left + ((targetRect.width - width) / 2)}px);`;
|
||||
/* const textRect = target.firstElementChild.getBoundingClientRect();
|
||||
activeStripe.style.cssText = `width: ${textRect.width + (2 * 2)}px; transform: translateX(${textRect.left - tabsRect.left}px);`; */
|
||||
//activeStripe.style.transform = `scaleX(${textRect.width}) translateX(${(textRect.left - tabsRect.left) / textRect.width + 0.5}px)`;
|
||||
//console.log('tabs click:', tabsRect, textRect);
|
||||
}
|
||||
|
||||
target.classList.add('active');
|
||||
selectTab(id);
|
||||
});
|
||||
}
|
||||
|
||||
return selectTab;
|
||||
}
|
@ -1,176 +1,18 @@
|
||||
import { whichChild, findUpTag, cancelEvent, findUpClassName } from "../lib/utils";
|
||||
import Config, { touchSupport, isApple, mediaSizes } from "../lib/config";
|
||||
|
||||
let rippleClickID = 0;
|
||||
export function ripple(elem: HTMLElement, callback: (id: number) => Promise<boolean | void> = () => Promise.resolve(), onEnd: (id: number) => void = null) {
|
||||
//return;
|
||||
if(elem.querySelector('.c-ripple')) return;
|
||||
elem.classList.add('rp');
|
||||
|
||||
let r = document.createElement('div');
|
||||
r.classList.add('c-ripple');
|
||||
|
||||
const isSquare = elem.classList.contains('rp-square');
|
||||
if(isSquare) {
|
||||
r.classList.add('is-square');
|
||||
}
|
||||
|
||||
const duration = isSquare ? 200 : 700;
|
||||
|
||||
elem.append(r);
|
||||
|
||||
let handler: () => void;
|
||||
let drawRipple = (clientX: number, clientY: number) => {
|
||||
let startTime = Date.now();
|
||||
let span = document.createElement('span');
|
||||
|
||||
let clickID = rippleClickID++;
|
||||
|
||||
//console.log('ripple drawRipple');
|
||||
|
||||
handler = () => {
|
||||
let elapsedTime = Date.now() - startTime;
|
||||
if(elapsedTime < duration) {
|
||||
let delay = Math.max(duration - elapsedTime, duration / 2);
|
||||
setTimeout(() => span.classList.add('hiding'), Math.max(delay - duration / 2, 0));
|
||||
|
||||
setTimeout(() => {
|
||||
//console.log('ripple elapsedTime total pre-remove:', Date.now() - startTime);
|
||||
span.remove();
|
||||
if(onEnd) onEnd(clickID);
|
||||
}, delay);
|
||||
} else {
|
||||
span.classList.add('hiding');
|
||||
setTimeout(() => {
|
||||
//console.log('ripple elapsedTime total pre-remove:', Date.now() - startTime);
|
||||
span.remove();
|
||||
if(onEnd) onEnd(clickID);
|
||||
}, duration / 2);
|
||||
}
|
||||
|
||||
handler = null;
|
||||
};
|
||||
|
||||
callback && callback(clickID);
|
||||
|
||||
/* callback().then((bad) => {
|
||||
if(bad) {
|
||||
span.remove();
|
||||
return;
|
||||
} */
|
||||
|
||||
//console.log('ripple after promise', Date.now() - startTime);
|
||||
//console.log('ripple tooSlow:', tooSlow);
|
||||
/* if(tooSlow) {
|
||||
span.remove();
|
||||
return;
|
||||
} */
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
span.classList.add('c-ripple__circle');
|
||||
let rect = r.getBoundingClientRect();
|
||||
|
||||
let clickX = clientX - rect.left;
|
||||
let clickY = clientY - rect.top;
|
||||
|
||||
let size: number, clickPos: number;
|
||||
if(rect.width > rect.height) {
|
||||
size = rect.width;
|
||||
clickPos = clickX;
|
||||
} else {
|
||||
size = rect.height;
|
||||
clickPos = clickY;
|
||||
}
|
||||
|
||||
let offsetFromCenter = clickPos > (size / 2) ? size - clickPos : clickPos;
|
||||
size = size - offsetFromCenter;
|
||||
size *= 1.1;
|
||||
|
||||
// center of circle
|
||||
let x = clickX - size / 2;
|
||||
let y = clickY - size / 2;
|
||||
|
||||
//console.log('ripple click', offsetFromCenter, size, clickX, clickY);
|
||||
|
||||
span.style.width = span.style.height = size + 'px';
|
||||
span.style.left = x + 'px';
|
||||
span.style.top = y + 'px';
|
||||
|
||||
r.append(span);
|
||||
//r.classList.add('active');
|
||||
//handler();
|
||||
});
|
||||
//});
|
||||
};
|
||||
|
||||
let touchStartFired = false;
|
||||
if(touchSupport) {
|
||||
let touchEnd = () => {
|
||||
handler && handler();
|
||||
};
|
||||
|
||||
elem.addEventListener('touchstart', (e) => {
|
||||
//console.log('ripple touchstart', e);
|
||||
if(e.touches.length > 1 || ((e.target as HTMLElement).tagName == 'BUTTON' && e.target != elem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//console.log('touchstart', e);
|
||||
touchStartFired = true;
|
||||
|
||||
let {clientX, clientY} = e.touches[0];
|
||||
drawRipple(clientX, clientY);
|
||||
window.addEventListener('touchend', touchEnd, {once: true});
|
||||
|
||||
window.addEventListener('touchmove', (e) => {
|
||||
e.cancelBubble = true;
|
||||
e.stopPropagation();
|
||||
handler && handler();
|
||||
window.removeEventListener('touchend', touchEnd);
|
||||
}, {once: true});
|
||||
}, {passive: true});
|
||||
} else {
|
||||
elem.addEventListener('mousedown', (e) => {
|
||||
if(elem.dataset.ripple == '0') {
|
||||
return false;
|
||||
} else if(touchStartFired) {
|
||||
touchStartFired = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
let {clientX, clientY} = e;
|
||||
drawRipple(clientX, clientY);
|
||||
window.addEventListener('mouseup', handler, {once: true});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const toastEl = document.createElement('div');
|
||||
toastEl.classList.add('toast');
|
||||
export function toast(html: string) {
|
||||
toastEl.innerHTML = html;
|
||||
document.body.append(toastEl);
|
||||
|
||||
if(toastEl.dataset.timeout) clearTimeout(+toastEl.dataset.timeout);
|
||||
toastEl.dataset.timeout = '' + setTimeout(() => {
|
||||
toastEl.remove();
|
||||
delete toastEl.dataset.timeout;
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
let loadedURLs: {[url: string]: boolean} = {};
|
||||
let set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLSourceElement, url: string) => {
|
||||
if(elem instanceof HTMLImageElement || elem instanceof HTMLSourceElement) elem.src = url;
|
||||
let set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string) => {
|
||||
if(elem instanceof HTMLImageElement || elem instanceof HTMLVideoElement) elem.src = url;
|
||||
else if(elem instanceof SVGImageElement) elem.setAttributeNS(null, 'href', url);
|
||||
else elem.style.backgroundImage = 'url(' + url + ')';
|
||||
};
|
||||
|
||||
export async function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLSourceElement, url: string): Promise<boolean> {
|
||||
export async function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string): Promise<boolean> {
|
||||
if(loadedURLs[url]) {
|
||||
set(elem, url);
|
||||
} else {
|
||||
if(elem instanceof HTMLSourceElement) {
|
||||
elem.src = url;
|
||||
if(elem instanceof HTMLVideoElement) {
|
||||
set(elem, url);
|
||||
} else {
|
||||
await new Promise((resolve, reject) => {
|
||||
let loader = new Image();
|
||||
@ -185,6 +27,7 @@ export async function renderImageFromUrl(elem: HTMLElement | HTMLImageElement |
|
||||
loader.addEventListener('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return !!loadedURLs[url];
|
||||
@ -211,164 +54,6 @@ export function putPreloader(elem: Element, returnDiv = false) {
|
||||
elem.innerHTML += html;
|
||||
}
|
||||
|
||||
function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) {
|
||||
/* if(toRight) {
|
||||
//prevTabContent.style.filter = `brightness(80%)`;
|
||||
prevTabContent.style.transform = `translateX(-25%)`;
|
||||
tabContent.style.transform = `translateX(20%)`;
|
||||
} else {
|
||||
//tabContent.style.filter = `brightness(80%)`;
|
||||
tabContent.style.transform = `translateX(-25%)`;
|
||||
prevTabContent.style.transform = `translateX(20%)`;
|
||||
} */
|
||||
if(toRight) {
|
||||
prevTabContent.style.filter = `brightness(80%)`;
|
||||
prevTabContent.style.transform = `translateX(-25%)`;
|
||||
tabContent.style.transform = `translateX(100%)`;
|
||||
} else {
|
||||
tabContent.style.filter = `brightness(80%)`;
|
||||
tabContent.style.transform = `translateX(-25%)`;
|
||||
prevTabContent.style.transform = `translateX(100%)`;
|
||||
}
|
||||
|
||||
tabContent.classList.add('active');
|
||||
void tabContent.offsetWidth; // reflow
|
||||
|
||||
tabContent.style.transform = '';
|
||||
tabContent.style.filter = '';
|
||||
}
|
||||
|
||||
function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) {
|
||||
if(toRight) {
|
||||
tabContent.style.transform = `translateX(100%)`;
|
||||
prevTabContent.style.transform = `translateX(-100%)`;
|
||||
} else {
|
||||
tabContent.style.transform = `translateX(-100%)`;
|
||||
prevTabContent.style.transform = `translateX(100%)`;
|
||||
}
|
||||
|
||||
tabContent.classList.add('active');
|
||||
void tabContent.offsetWidth; // reflow
|
||||
|
||||
tabContent.style.transform = '';
|
||||
}
|
||||
|
||||
export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?: (id: number, tabContent: HTMLDivElement) => void, onTransitionEnd?: () => void, transitionTime = 300) {
|
||||
const hideTimeouts: {[id: number]: number} = {};
|
||||
let prevTabContent: HTMLElement = null;
|
||||
let prevId = -1;
|
||||
|
||||
const selectTab = (id: number) => {
|
||||
if(id == prevId) return false;
|
||||
|
||||
//console.log('selectTab id:', id);
|
||||
|
||||
const p = prevTabContent;
|
||||
const tabContent = content.children[id] as HTMLElement;
|
||||
|
||||
if(content.dataset.slider == 'none') {
|
||||
if(p) {
|
||||
p.classList.remove('active');
|
||||
}
|
||||
|
||||
tabContent.classList.add('active');
|
||||
|
||||
prevId = id;
|
||||
prevTabContent = tabContent;
|
||||
|
||||
if(onTransitionEnd) onTransitionEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
const toRight = prevId < id;
|
||||
if(prevId != -1) {
|
||||
if(tabs || content.dataset.slider == 'tabs') {
|
||||
slideTabs(tabContent, prevTabContent, toRight);
|
||||
} else {
|
||||
slideNavigation(tabContent, prevTabContent, toRight);
|
||||
}
|
||||
} else {
|
||||
tabContent.classList.add('active');
|
||||
}
|
||||
|
||||
const _prevId = prevId;
|
||||
if(hideTimeouts.hasOwnProperty(id)) clearTimeout(hideTimeouts[id]);
|
||||
if(p/* && false */) {
|
||||
hideTimeouts[_prevId] = setTimeout(() => {
|
||||
p.style.transform = '';
|
||||
p.style.filter = '';
|
||||
p.classList.remove('active');
|
||||
|
||||
delete hideTimeouts[_prevId];
|
||||
|
||||
if(onTransitionEnd) onTransitionEnd();
|
||||
}, /* 420 */transitionTime);
|
||||
}
|
||||
|
||||
prevId = id;
|
||||
prevTabContent = tabContent;
|
||||
};
|
||||
|
||||
if(tabs) {
|
||||
let activeStripe: HTMLSpanElement;
|
||||
if(!tabs.classList.contains('no-stripe')) {
|
||||
activeStripe = document.createElement('span');
|
||||
activeStripe.classList.add('menu-horizontal__stripe');
|
||||
|
||||
tabs.append(activeStripe);
|
||||
}
|
||||
|
||||
const tagName = 'LI';//tabs.firstElementChild.tagName;
|
||||
tabs.addEventListener('click', function(e) {
|
||||
let target = e.target as HTMLElement;
|
||||
|
||||
if(target.tagName != tagName) {
|
||||
target = findUpTag(target, tagName);
|
||||
}
|
||||
|
||||
//console.log('tabs click:', target);
|
||||
|
||||
if(!target) return false;
|
||||
|
||||
let id: number;
|
||||
if(target.dataset.tab) {
|
||||
id = +target.dataset.tab;
|
||||
if(id == -1) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
id = whichChild(target);
|
||||
}
|
||||
|
||||
const tabContent = content.children[id] as HTMLDivElement;
|
||||
|
||||
if(onClick) onClick(id, tabContent);
|
||||
if(target.classList.contains('active') || id == prevId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const prev = tabs.querySelector(tagName.toLowerCase() + '.active') as HTMLElement;
|
||||
prev && prev.classList.remove('active');
|
||||
|
||||
if(activeStripe) {
|
||||
const tabsRect = tabs.getBoundingClientRect();
|
||||
const targetRect = target.getBoundingClientRect();
|
||||
const width = 50;
|
||||
activeStripe.style.cssText = `width: ${width}px; transform: translateX(${targetRect.left - tabsRect.left + ((targetRect.width - width) / 2)}px);`;
|
||||
/* const textRect = target.firstElementChild.getBoundingClientRect();
|
||||
activeStripe.style.cssText = `width: ${textRect.width + (2 * 2)}px; transform: translateX(${textRect.left - tabsRect.left}px);`; */
|
||||
//activeStripe.style.transform = `scaleX(${textRect.width}) translateX(${(textRect.left - tabsRect.left) / textRect.width + 0.5}px)`;
|
||||
//console.log('tabs click:', tabsRect, textRect);
|
||||
}
|
||||
|
||||
target.classList.add('active');
|
||||
selectTab(id);
|
||||
});
|
||||
}
|
||||
|
||||
return selectTab;
|
||||
}
|
||||
|
||||
export function formatPhoneNumber(str: string) {
|
||||
str = str.replace(/\D/g, '');
|
||||
let phoneCode = str.slice(0, 6);
|
||||
|
@ -2,10 +2,10 @@ import appPollsManager, { PollResults, Poll } from "../lib/appManagers/appPollsM
|
||||
import { RichTextProcessor } from "../lib/richtextprocessor";
|
||||
import { findUpClassName, $rootScope, cancelEvent } from "../lib/utils";
|
||||
import { mediaSizes, touchSupport } from "../lib/config";
|
||||
import { ripple } from "./misc";
|
||||
import appSidebarRight from "../lib/appManagers/appSidebarRight";
|
||||
import appImManager from "../lib/appManagers/appImManager";
|
||||
import serverTimeManager from "../lib/mtproto/serverTimeManager";
|
||||
import { ripple } from "./ripple";
|
||||
|
||||
let lineTotalLength = 0;
|
||||
const tailLength = 9;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import AvatarElement from "./avatar";
|
||||
import { ripple } from "./misc";
|
||||
import { ripple } from "./ripple";
|
||||
|
||||
export class PopupElement {
|
||||
protected element = document.createElement('div');
|
||||
|
@ -3,8 +3,7 @@ import Scrollable from "./scrollable_new";
|
||||
import appMessagesManager from "../lib/appManagers/appMessagesManager";
|
||||
import { $rootScope } from "../lib/utils";
|
||||
import { Poll } from "../lib/appManagers/appPollsManager";
|
||||
import { nextRandomInt, bigint } from "../lib/bin_utils";
|
||||
import { toast } from "./misc";
|
||||
import { toast } from "./toast";
|
||||
|
||||
const InputField = (placeholder: string, label: string, name: string) => {
|
||||
const div = document.createElement('div');
|
||||
|
145
src/components/ripple.ts
Normal file
145
src/components/ripple.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import { touchSupport } from "../lib/config";
|
||||
|
||||
let rippleClickID = 0;
|
||||
export function ripple(elem: HTMLElement, callback: (id: number) => Promise<boolean | void> = () => Promise.resolve(), onEnd: (id: number) => void = null) {
|
||||
//return;
|
||||
if(elem.querySelector('.c-ripple')) return;
|
||||
elem.classList.add('rp');
|
||||
|
||||
let r = document.createElement('div');
|
||||
r.classList.add('c-ripple');
|
||||
|
||||
const isSquare = elem.classList.contains('rp-square');
|
||||
if(isSquare) {
|
||||
r.classList.add('is-square');
|
||||
}
|
||||
|
||||
const duration = isSquare ? 200 : 700;
|
||||
|
||||
elem.append(r);
|
||||
|
||||
let handler: () => void;
|
||||
let drawRipple = (clientX: number, clientY: number) => {
|
||||
let startTime = Date.now();
|
||||
let span = document.createElement('span');
|
||||
|
||||
let clickID = rippleClickID++;
|
||||
|
||||
//console.log('ripple drawRipple');
|
||||
|
||||
handler = () => {
|
||||
let elapsedTime = Date.now() - startTime;
|
||||
if(elapsedTime < duration) {
|
||||
let delay = Math.max(duration - elapsedTime, duration / 2);
|
||||
setTimeout(() => span.classList.add('hiding'), Math.max(delay - duration / 2, 0));
|
||||
|
||||
setTimeout(() => {
|
||||
//console.log('ripple elapsedTime total pre-remove:', Date.now() - startTime);
|
||||
span.remove();
|
||||
if(onEnd) onEnd(clickID);
|
||||
}, delay);
|
||||
} else {
|
||||
span.classList.add('hiding');
|
||||
setTimeout(() => {
|
||||
//console.log('ripple elapsedTime total pre-remove:', Date.now() - startTime);
|
||||
span.remove();
|
||||
if(onEnd) onEnd(clickID);
|
||||
}, duration / 2);
|
||||
}
|
||||
|
||||
handler = null;
|
||||
};
|
||||
|
||||
callback && callback(clickID);
|
||||
|
||||
/* callback().then((bad) => {
|
||||
if(bad) {
|
||||
span.remove();
|
||||
return;
|
||||
} */
|
||||
|
||||
//console.log('ripple after promise', Date.now() - startTime);
|
||||
//console.log('ripple tooSlow:', tooSlow);
|
||||
/* if(tooSlow) {
|
||||
span.remove();
|
||||
return;
|
||||
} */
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
span.classList.add('c-ripple__circle');
|
||||
let rect = r.getBoundingClientRect();
|
||||
|
||||
let clickX = clientX - rect.left;
|
||||
let clickY = clientY - rect.top;
|
||||
|
||||
let size: number, clickPos: number;
|
||||
if(rect.width > rect.height) {
|
||||
size = rect.width;
|
||||
clickPos = clickX;
|
||||
} else {
|
||||
size = rect.height;
|
||||
clickPos = clickY;
|
||||
}
|
||||
|
||||
let offsetFromCenter = clickPos > (size / 2) ? size - clickPos : clickPos;
|
||||
size = size - offsetFromCenter;
|
||||
size *= 1.1;
|
||||
|
||||
// center of circle
|
||||
let x = clickX - size / 2;
|
||||
let y = clickY - size / 2;
|
||||
|
||||
//console.log('ripple click', offsetFromCenter, size, clickX, clickY);
|
||||
|
||||
span.style.width = span.style.height = size + 'px';
|
||||
span.style.left = x + 'px';
|
||||
span.style.top = y + 'px';
|
||||
|
||||
r.append(span);
|
||||
//r.classList.add('active');
|
||||
//handler();
|
||||
});
|
||||
//});
|
||||
};
|
||||
|
||||
let touchStartFired = false;
|
||||
if(touchSupport) {
|
||||
let touchEnd = () => {
|
||||
handler && handler();
|
||||
};
|
||||
|
||||
elem.addEventListener('touchstart', (e) => {
|
||||
//console.log('ripple touchstart', e);
|
||||
if(e.touches.length > 1 || ((e.target as HTMLElement).tagName == 'BUTTON' && e.target != elem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//console.log('touchstart', e);
|
||||
touchStartFired = true;
|
||||
|
||||
let {clientX, clientY} = e.touches[0];
|
||||
drawRipple(clientX, clientY);
|
||||
window.addEventListener('touchend', touchEnd, {once: true});
|
||||
|
||||
window.addEventListener('touchmove', (e) => {
|
||||
e.cancelBubble = true;
|
||||
e.stopPropagation();
|
||||
handler && handler();
|
||||
window.removeEventListener('touchend', touchEnd);
|
||||
}, {once: true});
|
||||
}, {passive: true});
|
||||
} else {
|
||||
elem.addEventListener('mousedown', (e) => {
|
||||
if(elem.dataset.ripple == '0') {
|
||||
return false;
|
||||
} else if(touchStartFired) {
|
||||
touchStartFired = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
let {clientX, clientY} = e;
|
||||
drawRipple(clientX, clientY);
|
||||
window.addEventListener('mouseup', handler, {once: true});
|
||||
});
|
||||
}
|
||||
}
|
@ -4,9 +4,9 @@ import apiManager from "../../lib/mtproto/mtprotoworker";
|
||||
import appMessagesManager, { DialogFilter } from "../../lib/appManagers/appMessagesManager";
|
||||
import { RichTextProcessor } from "../../lib/richtextprocessor";
|
||||
import appPeersManager from "../../lib/appManagers/appPeersManager";
|
||||
import { ripple } from "../misc";
|
||||
import { $rootScope, cancelEvent } from "../../lib/utils";
|
||||
import appSidebarLeft from "../../lib/appManagers/appSidebarLeft";
|
||||
import { ripple } from "../ripple";
|
||||
|
||||
type DialogFilterSuggested = {
|
||||
_: 'dialogFilterSuggested',
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { SliderTab } from "../slider";
|
||||
import appSidebarLeft, { AppSidebarLeft } from "../../lib/appManagers/appSidebarLeft";
|
||||
import lottieLoader, { RLottiePlayer } from "../../lib/lottieLoader";
|
||||
import appMessagesManager, { DialogFilter, Dialog } from "../../lib/appManagers/appMessagesManager";
|
||||
import { parseMenuButtonsTo, ripple, toast } from "../misc";
|
||||
import appMessagesManager, { DialogFilter } from "../../lib/appManagers/appMessagesManager";
|
||||
import { parseMenuButtonsTo } from "../misc";
|
||||
import appDialogsManager from "../../lib/appManagers/appDialogsManager";
|
||||
import { copy, deepEqual } from "../../lib/utils";
|
||||
import { toast } from "../toast";
|
||||
import { ripple } from "../ripple";
|
||||
|
||||
export default class AppEditFolderTab implements SliderTab {
|
||||
public container: HTMLElement;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { horizontalMenu } from "./misc";
|
||||
import { horizontalMenu } from "./horizontalMenu";
|
||||
|
||||
export interface SliderTab {
|
||||
onOpen?: () => void,
|
||||
|
12
src/components/toast.ts
Normal file
12
src/components/toast.ts
Normal file
@ -0,0 +1,12 @@
|
||||
const toastEl = document.createElement('div');
|
||||
toastEl.classList.add('toast');
|
||||
export function toast(html: string) {
|
||||
toastEl.innerHTML = html;
|
||||
document.body.append(toastEl);
|
||||
|
||||
if(toastEl.dataset.timeout) clearTimeout(+toastEl.dataset.timeout);
|
||||
toastEl.dataset.timeout = '' + setTimeout(() => {
|
||||
toastEl.remove();
|
||||
delete toastEl.dataset.timeout;
|
||||
}, 3000);
|
||||
}
|
@ -18,78 +18,92 @@ import AudioElement from './audio';
|
||||
import { Download } from '../lib/appManagers/appDownloadManager';
|
||||
import { webpWorkerController } from '../lib/webp/webpWorkerController';
|
||||
|
||||
export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue}: {
|
||||
export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue, noInfo, group}: {
|
||||
doc: MTDocument,
|
||||
container: HTMLDivElement,
|
||||
message: any,
|
||||
boxWidth: number,
|
||||
boxHeight: number,
|
||||
container?: HTMLDivElement,
|
||||
message?: any,
|
||||
boxWidth?: number,
|
||||
boxHeight?: number,
|
||||
withTail?: boolean,
|
||||
isOut?: boolean,
|
||||
middleware: () => boolean,
|
||||
lazyLoadQueue: LazyLoadQueue
|
||||
middleware?: () => boolean,
|
||||
lazyLoadQueue?: LazyLoadQueue,
|
||||
noInfo?: true,
|
||||
group?: string,
|
||||
}) {
|
||||
let span: HTMLSpanElement, spanPlay: HTMLSpanElement;
|
||||
if(doc.type != 'round') {
|
||||
span = document.createElement('span');
|
||||
span.classList.add('video-time');
|
||||
container.append(span);
|
||||
|
||||
if(doc.type != 'gif') {
|
||||
span.innerText = (doc.duration + '').toHHMMSS(false);
|
||||
|
||||
spanPlay = document.createElement('span');
|
||||
spanPlay.classList.add('video-play', 'tgico-largeplay', 'btn-circle', 'position-center');
|
||||
container.append(spanPlay);
|
||||
} else {
|
||||
span.innerText = 'GIF';
|
||||
}
|
||||
}
|
||||
|
||||
if(doc.type == 'video') {
|
||||
return wrapPhoto(doc, message, container, boxWidth, boxHeight, withTail, isOut, lazyLoadQueue, middleware);
|
||||
}
|
||||
|
||||
let img: HTMLImageElement;
|
||||
if(withTail) {
|
||||
img = wrapMediaWithTail(doc, message, container, boxWidth, boxHeight, isOut);
|
||||
} else {
|
||||
if(!boxWidth && !boxHeight) { // album
|
||||
let sizes = doc.thumbs;
|
||||
if(!doc.downloaded && sizes && sizes[0].bytes) {
|
||||
appPhotosManager.setAttachmentPreview(sizes[0].bytes, container, false);
|
||||
}
|
||||
} else {
|
||||
if(!container.firstElementChild || (container.firstElementChild.tagName != 'IMG' && container.firstElementChild.tagName != 'VIDEO')) {
|
||||
appPhotosManager.setAttachmentSize(doc, container, boxWidth, boxHeight);
|
||||
if(!noInfo) {
|
||||
if(doc.type != 'round') {
|
||||
let span: HTMLSpanElement, spanPlay: HTMLSpanElement;
|
||||
|
||||
span = document.createElement('span');
|
||||
span.classList.add('video-time');
|
||||
container.append(span);
|
||||
|
||||
if(doc.type != 'gif') {
|
||||
span.innerText = (doc.duration + '').toHHMMSS(false);
|
||||
|
||||
spanPlay = document.createElement('span');
|
||||
spanPlay.classList.add('video-play', 'tgico-largeplay', 'btn-circle', 'position-center');
|
||||
container.append(spanPlay);
|
||||
} else {
|
||||
span.innerText = 'GIF';
|
||||
}
|
||||
}
|
||||
|
||||
img = container.lastElementChild as HTMLImageElement;
|
||||
if(!img || img.tagName != 'IMG') {
|
||||
container.append(img = new Image());
|
||||
}
|
||||
}
|
||||
|
||||
if(img) {
|
||||
img.classList.add('thumbnail');
|
||||
}
|
||||
|
||||
const video = document.createElement('video');
|
||||
const source = document.createElement('source');
|
||||
video.append(source);
|
||||
|
||||
if(withTail) {
|
||||
const foreignObject = img.parentElement;
|
||||
video.width = +foreignObject.getAttributeNS(null, 'width');
|
||||
video.height = +foreignObject.getAttributeNS(null, 'height');
|
||||
foreignObject.append(video);
|
||||
} else {
|
||||
let img: HTMLImageElement;
|
||||
if(message) {
|
||||
if(doc.type == 'video') {
|
||||
return wrapPhoto(doc, message, container, boxWidth, boxHeight, withTail, isOut, lazyLoadQueue, middleware);
|
||||
}
|
||||
|
||||
if(withTail) {
|
||||
img = wrapMediaWithTail(doc, message, container, boxWidth, boxHeight, isOut);
|
||||
} else {
|
||||
if(!boxWidth && !boxHeight) { // album
|
||||
let sizes = doc.thumbs;
|
||||
if(!doc.downloaded && sizes && sizes[0].bytes) {
|
||||
appPhotosManager.setAttachmentPreview(sizes[0].bytes, container, false);
|
||||
}
|
||||
} else {
|
||||
if(!container.firstElementChild || (container.firstElementChild.tagName != 'IMG' && container.firstElementChild.tagName != 'VIDEO')) {
|
||||
appPhotosManager.setAttachmentSize(doc, container, boxWidth, boxHeight);
|
||||
}
|
||||
}
|
||||
|
||||
img = container.lastElementChild as HTMLImageElement;
|
||||
if(!img || img.tagName != 'IMG') {
|
||||
container.append(img = new Image());
|
||||
}
|
||||
}
|
||||
|
||||
if(img) {
|
||||
img.classList.add('thumbnail');
|
||||
}
|
||||
|
||||
if(withTail) {
|
||||
const foreignObject = img.parentElement;
|
||||
video.width = +foreignObject.getAttributeNS(null, 'width');
|
||||
video.height = +foreignObject.getAttributeNS(null, 'height');
|
||||
foreignObject.append(video);
|
||||
}
|
||||
}
|
||||
|
||||
if(!img?.parentElement) {
|
||||
const posterURL = appDocsManager.getThumbURL(doc, false);
|
||||
if(posterURL) {
|
||||
video.poster = posterURL;
|
||||
}
|
||||
}
|
||||
|
||||
if(!video.parentElement && container) {
|
||||
container.append(video);
|
||||
}
|
||||
|
||||
const loadVideo = async() => {
|
||||
if(message.media.preloader) { // means upload
|
||||
if(message?.media?.preloader) { // means upload
|
||||
(message.media.preloader as ProgressivePreloader).attach(container, undefined, undefined, false);
|
||||
} else if(!doc.downloaded) {
|
||||
/* const promise = appDocsManager.downloadDoc(doc.id);
|
||||
@ -117,13 +131,13 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
|
||||
/* if(!video.paused) {
|
||||
video.pause();
|
||||
} */
|
||||
animationIntersector.addAnimation(video, 'chat');
|
||||
if(group) {
|
||||
animationIntersector.addAnimation(video, group);
|
||||
}
|
||||
}, {once: true});
|
||||
}
|
||||
|
||||
renderImageFromUrl(source, doc.url);
|
||||
source.type = doc.mime_type;
|
||||
video.append(source);
|
||||
renderImageFromUrl(video, doc.url);
|
||||
video.setAttribute('playsinline', '');
|
||||
|
||||
/* if(!container.parentElement) {
|
||||
@ -160,9 +174,9 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
|
||||
|
||||
return;
|
||||
} */
|
||||
|
||||
//return;
|
||||
return doc.downloaded/* && false */ ? loadVideo() : lazyLoadQueue.push({div: container, load: loadVideo/* , wasSeen: true */});
|
||||
|
||||
doc.downloaded || !lazyLoadQueue/* && false */ ? loadVideo() : lazyLoadQueue.push({div: container, load: loadVideo/* , wasSeen: true */});
|
||||
return video;
|
||||
}
|
||||
|
||||
export const formatDate = (timestamp: number, monthShort = false, withYear = true) => {
|
||||
@ -445,14 +459,14 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
} else if(!onlyThumb && stickerType == 2 && withThumb && toneIndex <= 0) {
|
||||
img = new Image();
|
||||
|
||||
const load = () => appDocsManager.downloadDocThumb(doc, thumb.type).then(url => {
|
||||
const load = () => {
|
||||
if(div.childElementCount || (middleware && !middleware())) return;
|
||||
const promise = renderImageFromUrl(img, url);
|
||||
const promise = renderImageFromUrl(img, appDocsManager.getFileURL(doc, false, thumb));
|
||||
|
||||
//if(!downloaded) {
|
||||
promise.then(afterRender);
|
||||
//}
|
||||
});
|
||||
};
|
||||
|
||||
/* let downloaded = appDocsManager.hasDownloadedThumb(doc.id, thumb.type);
|
||||
if(downloaded) {
|
||||
@ -467,13 +481,13 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
if(onlyThumb && doc.thumbs) { // for sticker panel
|
||||
let thumb = doc.thumbs[0];
|
||||
|
||||
let load = () => appDocsManager.downloadDocThumb(doc, thumb.type).then(url => {
|
||||
let load = () => {
|
||||
let img = new Image();
|
||||
renderImageFromUrl(img, url).then(() => {
|
||||
return renderImageFromUrl(img, appDocsManager.getFileURL(doc, false, thumb)).then(() => {
|
||||
if(middleware && !middleware()) return;
|
||||
div.append(img);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return lazyLoadQueue ? (lazyLoadQueue.push({div, load}), Promise.resolve()) : load();
|
||||
}
|
||||
|
5
src/global.d.ts
vendored
5
src/global.d.ts
vendored
@ -4,4 +4,7 @@ declare module 'worker-loader!*' {
|
||||
}
|
||||
|
||||
export default WebpackWorker;
|
||||
}
|
||||
}
|
||||
|
||||
declare function setInterval(callback: (...args: any[]) => void, ms: number): number;
|
||||
declare function setTimeout(callback: (...args: any[]) => void, ms: number): number;
|
@ -2,8 +2,8 @@ import appMessagesManager from "./appMessagesManager";
|
||||
import apiManagerProxy from "../mtproto/mtprotoworker";
|
||||
import appPeersManager from "../appManagers/appPeersManager";
|
||||
import appMessagesIDsManager from "./appMessagesIDsManager";
|
||||
import { toast } from "../../components/misc";
|
||||
import { RichTextProcessor } from "../richtextprocessor";
|
||||
import { toast } from "../../components/toast";
|
||||
|
||||
export class AppInlineBotsManager {
|
||||
/* private inlineResults: any = {};
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { findUpClassName, $rootScope, escapeRegExp, whichChild, findUpTag, cancelEvent, formatNumber } from "../utils";
|
||||
import { findUpClassName, $rootScope, escapeRegExp, whichChild, findUpTag, cancelEvent } from "../utils";
|
||||
import appImManager, { AppImManager } from "./appImManager";
|
||||
import appPeersManager from './appPeersManager';
|
||||
import appMessagesManager, { AppMessagesManager, Dialog, DialogFilter } from "./appMessagesManager";
|
||||
import appUsersManager, { User } from "./appUsersManager";
|
||||
import { RichTextProcessor } from "../richtextprocessor";
|
||||
import { ripple, putPreloader, positionMenu, openBtnMenu, parseMenuButtonsTo, horizontalMenu, attachContextMenuListener } from "../../components/misc";
|
||||
import { putPreloader, positionMenu, openBtnMenu, parseMenuButtonsTo, attachContextMenuListener } from "../../components/misc";
|
||||
//import Scrollable from "../../components/scrollable";
|
||||
import Scrollable from "../../components/scrollable_new";
|
||||
import { logger, LogLevels } from "../logger";
|
||||
@ -14,6 +14,8 @@ import { PopupButton, PopupPeer } from "../../components/popup";
|
||||
import { SliderTab } from "../../components/slider";
|
||||
import appStateManager from "./appStateManager";
|
||||
import { touchSupport, isSafari } from "../config";
|
||||
import { horizontalMenu } from "../../components/horizontalMenu";
|
||||
import { ripple } from "../../components/ripple";
|
||||
|
||||
type DialogDom = {
|
||||
avatarEl: AvatarElement,
|
||||
|
@ -1,14 +1,14 @@
|
||||
import {RichTextProcessor} from '../richtextprocessor';
|
||||
import { CancellablePromise, deferredPromise } from '../polyfill';
|
||||
import { isObject, getFileURL } from '../utils';
|
||||
import { isObject, getFileURL, FileURLType } from '../utils';
|
||||
import opusDecodeController from '../opusDecodeController';
|
||||
import { MTDocument, inputDocumentFileLocation } from '../../types';
|
||||
import { MTDocument, inputDocumentFileLocation, MTPhotoSize } from '../../types';
|
||||
import { getFileNameByLocation } from '../bin_utils';
|
||||
import appDownloadManager, { Download, ResponseMethod } from './appDownloadManager';
|
||||
import appPhotosManager from './appPhotosManager';
|
||||
|
||||
class AppDocsManager {
|
||||
private docs: {[docID: string]: MTDocument} = {};
|
||||
private thumbs: {[docIDAndSize: string]: Promise<string>} = {};
|
||||
private downloadPromises: {[docID: string]: CancellablePromise<Blob>} = {};
|
||||
|
||||
public saveDoc(apiDoc: MTDocument, context?: any) {
|
||||
@ -49,16 +49,16 @@ class AppDocsManager {
|
||||
apiDoc.audioPerformer = attribute.performer;
|
||||
apiDoc.type = attribute.pFlags.voice && apiDoc.mime_type == "audio/ogg" ? 'voice' : 'audio';
|
||||
|
||||
if(apiDoc.type == 'audio') {
|
||||
/* if(apiDoc.type == 'audio') {
|
||||
apiDoc.supportsStreaming = true;
|
||||
}
|
||||
} */
|
||||
break;
|
||||
|
||||
case 'documentAttributeVideo':
|
||||
apiDoc.duration = attribute.duration;
|
||||
apiDoc.w = attribute.w;
|
||||
apiDoc.h = attribute.h;
|
||||
apiDoc.supportsStreaming = attribute.pFlags?.supports_streaming/* && apiDoc.size > 524288 */;
|
||||
//apiDoc.supportsStreaming = attribute.pFlags?.supports_streaming/* && apiDoc.size > 524288 */;
|
||||
if(apiDoc.thumbs && attribute.pFlags.round_message) {
|
||||
apiDoc.type = 'round';
|
||||
} else /* if(apiDoc.thumbs) */ {
|
||||
@ -124,6 +124,10 @@ class AppDocsManager {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if((apiDoc.type == 'gif' && apiDoc.size > 8e6) || apiDoc.type == 'audio' || apiDoc.type == 'video') {
|
||||
apiDoc.supportsStreaming = true;
|
||||
}
|
||||
|
||||
if(!apiDoc.file_name) {
|
||||
apiDoc.file_name = '';
|
||||
@ -140,7 +144,7 @@ class AppDocsManager {
|
||||
}
|
||||
|
||||
if(!apiDoc.url) {
|
||||
apiDoc.url = this.getFileURLByDoc(apiDoc);
|
||||
apiDoc.url = this.getFileURL(apiDoc);
|
||||
}
|
||||
|
||||
return apiDoc;
|
||||
@ -150,8 +154,7 @@ class AppDocsManager {
|
||||
return isObject(docID) && typeof(docID) !== 'string' ? docID : this.docs[docID as string];
|
||||
}
|
||||
|
||||
public getMediaInputByID(docID: any) {
|
||||
let doc = this.getDoc(docID);
|
||||
public getMediaInput(doc: MTDocument) {
|
||||
return {
|
||||
_: 'inputMediaDocument',
|
||||
flags: 0,
|
||||
@ -165,9 +168,7 @@ class AppDocsManager {
|
||||
};
|
||||
}
|
||||
|
||||
public getInputByID(docID: any, thumbSize?: string): inputDocumentFileLocation {
|
||||
let doc = this.getDoc(docID);
|
||||
|
||||
public getInput(doc: MTDocument, thumbSize?: string): inputDocumentFileLocation {
|
||||
return {
|
||||
_: 'inputDocumentFileLocation',
|
||||
id: doc.id,
|
||||
@ -176,35 +177,55 @@ class AppDocsManager {
|
||||
thumb_size: thumbSize
|
||||
};
|
||||
}
|
||||
|
||||
public getFileName(doc: MTDocument) {
|
||||
if(doc.file_name) {
|
||||
return doc.file_name;
|
||||
|
||||
public getFileURL(doc: MTDocument, download = false, thumb?: MTPhotoSize) {
|
||||
const inputFileLocation = this.getInput(doc, thumb?.type);
|
||||
|
||||
let type: FileURLType;
|
||||
if(download) {
|
||||
type = 'download';
|
||||
} else if(thumb) {
|
||||
type = 'thumb';
|
||||
} else if(doc.supportsStreaming) {
|
||||
type = 'stream';
|
||||
} else {
|
||||
type = 'document';
|
||||
}
|
||||
|
||||
var fileExt = '.' + doc.mime_type.split('/')[1];
|
||||
if(fileExt == '.octet-stream') {
|
||||
fileExt = '';
|
||||
let mimeType: string;
|
||||
if(thumb) {
|
||||
mimeType = doc.sticker ? 'image/webp' : 'image/jpeg'/* doc.mime_type */;
|
||||
} else {
|
||||
mimeType = doc.mime_type || 'application/octet-stream';
|
||||
}
|
||||
|
||||
return 't_' + (doc.type || 'file') + doc.id + fileExt;
|
||||
}
|
||||
|
||||
public getFileURLByDoc(doc: MTDocument, download = false) {
|
||||
const inputFileLocation = this.getInputByID(doc);
|
||||
const type = download ? 'download' : (doc.supportsStreaming ? 'stream' : 'document');
|
||||
|
||||
return getFileURL(type, {
|
||||
dcID: doc.dc_id,
|
||||
location: inputFileLocation,
|
||||
size: doc.size,
|
||||
mimeType: doc.mime_type || 'application/octet-stream',
|
||||
size: thumb ? thumb.size : doc.size,
|
||||
mimeType: mimeType,
|
||||
fileName: doc.file_name
|
||||
});
|
||||
}
|
||||
|
||||
public getInputFileName(doc: MTDocument) {
|
||||
return getFileNameByLocation(this.getInputByID(doc));
|
||||
public getThumbURL(doc: MTDocument, useBytes = true) {
|
||||
if(doc.thumbs?.length) {
|
||||
if(doc.thumbs[0].bytes && useBytes) {
|
||||
return appPhotosManager.getPreviewURLFromBytes(doc.thumbs[0].bytes, !!doc.sticker);
|
||||
}
|
||||
|
||||
const thumb = doc.thumbs.find(t => !t.bytes);
|
||||
if(thumb) {
|
||||
const url = appDocsManager.getFileURL(doc, false, thumb);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public getInputFileName(doc: MTDocument, thumbSize?: string) {
|
||||
return getFileNameByLocation(this.getInput(doc, thumbSize), {fileName: doc.file_name});
|
||||
}
|
||||
|
||||
public downloadDoc(docID: string | MTDocument, toFileEntry?: any): CancellablePromise<Blob> {
|
||||
@ -229,11 +250,7 @@ class AppDocsManager {
|
||||
|
||||
const deferred = deferredPromise<Blob>();
|
||||
|
||||
/* if(doc.supportsStreaming) {
|
||||
doc.url = '/stream/' + '';
|
||||
} */
|
||||
|
||||
const url = this.getFileURLByDoc(doc);
|
||||
const url = this.getFileURL(doc);
|
||||
fetch(url).then(res => res.blob())
|
||||
/* downloadPromise */.then((blob) => {
|
||||
if(blob) {
|
||||
@ -323,30 +340,8 @@ class AppDocsManager {
|
||||
return download;
|
||||
}
|
||||
|
||||
public downloadDocThumb(docID: any, thumbSize: string) {
|
||||
let doc = this.getDoc(docID);
|
||||
|
||||
let key = doc.id + '-' + thumbSize;
|
||||
if(this.thumbs[key]) {
|
||||
return this.thumbs[key];
|
||||
}
|
||||
|
||||
let input = this.getInputByID(doc, thumbSize);
|
||||
|
||||
if(doc._ == 'documentEmpty') {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
const url = getFileURL('thumb', {dcID: doc.dc_id, location: input, mimeType: doc.sticker ? 'image/webp' : doc.mime_type});
|
||||
return this.thumbs[key] = Promise.resolve(url);
|
||||
}
|
||||
|
||||
public hasDownloadedThumb(docID: string, thumbSize: string) {
|
||||
return !!this.thumbs[docID + '-' + thumbSize];
|
||||
}
|
||||
|
||||
public saveDocFile(doc: MTDocument) {
|
||||
const url = this.getFileURLByDoc(doc, true);
|
||||
const url = this.getFileURL(doc, true);
|
||||
const fileName = this.getInputFileName(doc);
|
||||
|
||||
return appDownloadManager.downloadToDisc(fileName, url);
|
||||
|
@ -16,7 +16,7 @@ import appSidebarLeft from "./appSidebarLeft";
|
||||
import appChatsManager, { Channel, Chat } from "./appChatsManager";
|
||||
import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker, wrapReply, wrapAlbum, wrapPoll, formatDate } from '../../components/wrappers';
|
||||
import ProgressivePreloader from '../../components/preloader';
|
||||
import { openBtnMenu, formatPhoneNumber, positionMenu, ripple, parseMenuButtonsTo, horizontalMenu, attachContextMenuListener } from '../../components/misc';
|
||||
import { openBtnMenu, formatPhoneNumber, positionMenu, parseMenuButtonsTo, attachContextMenuListener } from '../../components/misc';
|
||||
import { ChatInput } from '../../components/chatInput';
|
||||
//import Scrollable from '../../components/scrollable';
|
||||
import Scrollable from '../../components/scrollable_new';
|
||||
@ -37,6 +37,8 @@ import AppSearch, { SearchGroup } from '../../components/appSearch';
|
||||
import PopupDatePicker from '../../components/popupDatepicker';
|
||||
import appAudio from '../../components/appAudio';
|
||||
import appPollsManager from './appPollsManager';
|
||||
import { ripple } from '../../components/ripple';
|
||||
import { horizontalMenu } from '../../components/horizontalMenu';
|
||||
|
||||
//console.log('appImManager included33!');
|
||||
|
||||
@ -44,6 +46,8 @@ appSidebarLeft; // just to include
|
||||
|
||||
const testScroll = false;
|
||||
|
||||
const ANIMATIONGROUP = 'chat';
|
||||
|
||||
class ChatContextMenu {
|
||||
private element = document.getElementById('bubble-contextmenu') as HTMLDivElement;
|
||||
private buttons: {
|
||||
@ -1511,7 +1515,7 @@ export class AppImManager {
|
||||
|
||||
//console.timeEnd('appImManager setPeer pre promise');
|
||||
|
||||
animationIntersector.lockGroup('chat');
|
||||
animationIntersector.lockGroup(ANIMATIONGROUP);
|
||||
this.setPeerPromise = Promise.all([
|
||||
promise.then(() => {
|
||||
////this.log('setPeer removing preloader');
|
||||
@ -1532,8 +1536,8 @@ export class AppImManager {
|
||||
}
|
||||
|
||||
this.scrollable.container.append(this.chatInner);
|
||||
animationIntersector.unlockGroup('chat');
|
||||
animationIntersector.checkAnimations(false, 'chat'/* , true */);
|
||||
animationIntersector.unlockGroup(ANIMATIONGROUP);
|
||||
animationIntersector.checkAnimations(false, ANIMATIONGROUP/* , true */);
|
||||
//this.scrollable.attachSentinels();
|
||||
//this.scrollable.container.insertBefore(this.chatInner, this.scrollable.container.lastElementChild);
|
||||
|
||||
@ -1699,7 +1703,7 @@ export class AppImManager {
|
||||
//bubble.remove();
|
||||
});
|
||||
|
||||
animationIntersector.checkAnimations(false, 'chat');
|
||||
animationIntersector.checkAnimations(false, ANIMATIONGROUP);
|
||||
this.deleteEmptyDateGroups();
|
||||
}
|
||||
|
||||
@ -1805,15 +1809,12 @@ export class AppImManager {
|
||||
let promises: Promise<any>[] = [];
|
||||
(Array.from(bubble.querySelectorAll('img, video')) as HTMLImageElement[]).forEach(el => {
|
||||
if(el instanceof HTMLVideoElement) {
|
||||
let source = el.firstElementChild as HTMLSourceElement;
|
||||
if(!source || !source.src) {
|
||||
if(!el.src) {
|
||||
//this.log.warn('no source', el, source, 'src', source.src);
|
||||
return;
|
||||
} else if(el.readyState >= 4) return;
|
||||
} else if(el.complete || !el.src) return;
|
||||
|
||||
let src = el.src;
|
||||
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
let r: () => boolean;
|
||||
let onLoad = () => {
|
||||
@ -2046,7 +2047,7 @@ export class AppImManager {
|
||||
|
||||
let messageMedia = message.media;
|
||||
|
||||
if(totalEntities) {
|
||||
if(totalEntities && !messageMedia) {
|
||||
let emojiEntities = totalEntities.filter((e: any) => e._ == 'messageEntityEmoji');
|
||||
let strLength = messageMessage.length;
|
||||
let emojiStrLength = emojiEntities.reduce((acc: number, curr: any) => acc + curr.length, 0);
|
||||
@ -2237,7 +2238,8 @@ export class AppImManager {
|
||||
withTail: tailSupported,
|
||||
isOut: isOut,
|
||||
lazyLoadQueue: this.lazyLoadQueue,
|
||||
middleware: null
|
||||
middleware: null,
|
||||
group: ANIMATIONGROUP
|
||||
});
|
||||
|
||||
preloader.attach(attachmentDiv, false);
|
||||
@ -2342,7 +2344,8 @@ export class AppImManager {
|
||||
boxHeight: mediaSizes.active.webpage.height,
|
||||
lazyLoadQueue: this.lazyLoadQueue,
|
||||
middleware: this.getMiddleware(),
|
||||
isOut
|
||||
isOut,
|
||||
group: ANIMATIONGROUP
|
||||
});
|
||||
//}
|
||||
} else {
|
||||
@ -2428,7 +2431,7 @@ export class AppImManager {
|
||||
div: attachmentDiv,
|
||||
middleware: this.getMiddleware(),
|
||||
lazyLoadQueue: this.lazyLoadQueue,
|
||||
group: 'chat',
|
||||
group: ANIMATIONGROUP,
|
||||
//play: !!message.pending || !multipleRender,
|
||||
play: true,
|
||||
loop: true,
|
||||
@ -2463,7 +2466,8 @@ export class AppImManager {
|
||||
withTail: tailSupported,
|
||||
isOut: isOut,
|
||||
lazyLoadQueue: this.lazyLoadQueue,
|
||||
middleware: this.getMiddleware()
|
||||
middleware: this.getMiddleware(),
|
||||
group: ANIMATIONGROUP
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -311,9 +311,7 @@ export class AppMediaViewer {
|
||||
src = target.src;
|
||||
} else if(target instanceof HTMLVideoElement) {
|
||||
let video = mediaElement = document.createElement('video');
|
||||
let source = document.createElement('source');
|
||||
src = target.querySelector('source')?.src;
|
||||
video.append(source);
|
||||
video.src = target?.src;
|
||||
} else if(target instanceof SVGSVGElement) {
|
||||
let clipID = target.dataset.clipID;
|
||||
let newClipID = clipID + '-mv';
|
||||
@ -327,12 +325,6 @@ export class AppMediaViewer {
|
||||
newSvg.insertAdjacentHTML('beforeend', target.firstElementChild.outerHTML.replace(clipID, newClipID));
|
||||
newSvg.insertAdjacentHTML('beforeend', target.lastElementChild.outerHTML.replace(clipID, newClipID));
|
||||
|
||||
// FIX STREAM
|
||||
let source = newSvg.querySelector('source');
|
||||
if(source) {
|
||||
source.removeAttribute('src');
|
||||
}
|
||||
|
||||
// теперь надо выставить новую позицию для хвостика
|
||||
let defs = newSvg.firstElementChild;
|
||||
let use = defs.firstElementChild.firstElementChild as SVGUseElement;
|
||||
@ -655,7 +647,7 @@ export class AppMediaViewer {
|
||||
return promise;
|
||||
}
|
||||
|
||||
public updateMediaSource(target: HTMLElement, url: string, tagName: 'source' | 'img') {
|
||||
public updateMediaSource(target: HTMLElement, url: string, tagName: 'video' | 'img') {
|
||||
//if(target instanceof SVGSVGElement) {
|
||||
let el = target.querySelector(tagName) as HTMLElement;
|
||||
renderImageFromUrl(el, url);
|
||||
@ -778,11 +770,6 @@ export class AppMediaViewer {
|
||||
if(isVideo) {
|
||||
////////this.log('will wrap video', media, size);
|
||||
|
||||
/* let source = target.querySelector('source');
|
||||
if(source && source.src) {
|
||||
source.src = '';
|
||||
} */
|
||||
|
||||
setMoverPromise = this.setMoverToTarget(target, false, fromRight).then(() => {
|
||||
//return; // set and don't move
|
||||
//if(wasActive) return;
|
||||
@ -790,9 +777,7 @@ export class AppMediaViewer {
|
||||
|
||||
const div = mover.firstElementChild && mover.firstElementChild.classList.contains('media-viewer-aspecter') ? mover.firstElementChild : mover;
|
||||
const video = mover.querySelector('video') || document.createElement('video');
|
||||
const source = video.firstElementChild as HTMLSourceElement || document.createElement('source');
|
||||
|
||||
source.removeAttribute('src');
|
||||
video.src = '';
|
||||
|
||||
video.setAttribute('playsinline', '');
|
||||
if(media.type == 'gif') {
|
||||
@ -801,10 +786,6 @@ export class AppMediaViewer {
|
||||
video.loop = true;
|
||||
}
|
||||
|
||||
if(!source.parentElement) {
|
||||
video.append(source);
|
||||
}
|
||||
|
||||
const createPlayer = () => {
|
||||
if(media.type != 'gif') {
|
||||
video.dataset.ckin = 'default';
|
||||
@ -823,7 +804,7 @@ export class AppMediaViewer {
|
||||
} */
|
||||
};
|
||||
|
||||
if(!source.src || (media.url && media.url != source.src)) {
|
||||
if(!video.src || (media.url && media.url != video.src)) {
|
||||
const load = () => {
|
||||
const promise = appDocsManager.downloadDoc(media.id);
|
||||
|
||||
@ -845,12 +826,11 @@ export class AppMediaViewer {
|
||||
div.firstElementChild.lastElementChild.append(video);
|
||||
}
|
||||
|
||||
this.updateMediaSource(mover, url, 'source');
|
||||
this.updateMediaSource(mover, url, 'video');
|
||||
//this.updateMediaSource(target, url, 'source');
|
||||
} else {
|
||||
//const promise = new Promise((resolve) => video.addEventListener('loadeddata', resolve, {once: true}));
|
||||
renderImageFromUrl(source, url);
|
||||
source.type = media.mime_type;
|
||||
renderImageFromUrl(video, url);
|
||||
|
||||
//await promise;
|
||||
const first = div.firstElementChild as HTMLImageElement;
|
||||
@ -864,13 +844,13 @@ export class AppMediaViewer {
|
||||
}
|
||||
|
||||
// я хз что это такое, видео появляются просто чёрными и не включаются без этого кода снизу
|
||||
source.remove();
|
||||
/* source.remove();
|
||||
window.requestAnimationFrame(() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
//parent.append(video);
|
||||
video.append(source);
|
||||
});
|
||||
});
|
||||
}); */
|
||||
|
||||
player = createPlayer();
|
||||
});
|
||||
|
@ -1576,7 +1576,7 @@ export class AppMessagesManager {
|
||||
} else {
|
||||
let doc = messageMedia.document;
|
||||
appDocsManager.saveDoc(doc);
|
||||
inputMedia = appDocsManager.getMediaInputByID(doc.id);
|
||||
inputMedia = appDocsManager.getMediaInput(doc);
|
||||
}
|
||||
|
||||
inputs.push({
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { horizontalMenu, putPreloader, renderImageFromUrl, ripple } from "../../components/misc";
|
||||
import { putPreloader, renderImageFromUrl } from "../../components/misc";
|
||||
//import Scrollable from '../../components/scrollable';
|
||||
import Scrollable from '../../components/scrollable_new';
|
||||
import { $rootScope, findUpClassName } from "../utils";
|
||||
@ -25,6 +25,8 @@ import SearchInput from "../../components/searchInput";
|
||||
import appPollsManager from "./appPollsManager";
|
||||
import { roundPercents } from "../../components/poll";
|
||||
import appDialogsManager from "./appDialogsManager";
|
||||
import { ripple } from "../../components/ripple";
|
||||
import { horizontalMenu } from "../../components/horizontalMenu";
|
||||
|
||||
const testScroll = false;
|
||||
|
||||
|
@ -386,7 +386,7 @@ export function nextRandomInt(maxValue: number) {
|
||||
export function getFileNameByLocation(location: InputFileLocation | FileLocation, options?: Partial<{
|
||||
fileName: string
|
||||
}>) {
|
||||
const fileName = (options?.fileName || '').split('.');
|
||||
const fileName = '';//(options?.fileName || '').split('.');
|
||||
const ext = fileName[fileName.length - 1] || '';
|
||||
|
||||
switch(location._) {
|
||||
|
@ -71,7 +71,7 @@ export class ApiFileManager {
|
||||
const downloadPull = this.downloadPulls[dcID];
|
||||
//const downloadLimit = dcID == 'upload' ? 11 : 5;
|
||||
//const downloadLimit = 24;
|
||||
const downloadLimit = dcID == 'upload' ? 11 : 50;
|
||||
const downloadLimit = dcID == 'upload' ? 11 : 48;
|
||||
|
||||
if(this.downloadActives[dcID] >= downloadLimit || !downloadPull || !downloadPull.length) {
|
||||
return false;
|
||||
@ -115,7 +115,8 @@ export class ApiFileManager {
|
||||
}
|
||||
|
||||
public requestFilePart(dcID: number, location: InputFileLocation | FileLocation, offset: number, limit: number, checkCancel?: () => void) {
|
||||
const delta = limit / 1024 / 256;
|
||||
//const delta = limit / 1024 / 256;
|
||||
const delta = limit / 1024 / 128;
|
||||
return this.downloadRequest(dcID, async() => {
|
||||
checkCancel && checkCancel();
|
||||
|
||||
@ -138,9 +139,10 @@ export class ApiFileManager {
|
||||
private getLimitPart(size: number): number {
|
||||
let bytes: number;
|
||||
|
||||
if(size < 1e6 || !size) bytes = 512;
|
||||
bytes = 512;
|
||||
/* if(size < 1e6 || !size) bytes = 512;
|
||||
else if(size < 3e6) bytes = 256;
|
||||
else bytes = 128;
|
||||
else bytes = 128; */
|
||||
|
||||
return bytes * 1024;
|
||||
}
|
||||
@ -186,11 +188,11 @@ export class ApiFileManager {
|
||||
options.mimeType = 'application/json';
|
||||
}
|
||||
|
||||
const fileName = getFileNameByLocation(location);
|
||||
const fileName = getFileNameByLocation(location, {fileName: options.fileName});
|
||||
const cachedPromise = this.cachedDownloadPromises[fileName];
|
||||
const fileStorage = this.getFileStorage();
|
||||
|
||||
//this.log('downloadFile', fileName, size, location, options.mimeType, process);
|
||||
this.log('downloadFile', fileName, size, location, options.mimeType, process);
|
||||
|
||||
if(cachedPromise) {
|
||||
if(options.processPart) {
|
||||
@ -267,7 +269,7 @@ export class ApiFileManager {
|
||||
let startOffset = 0;
|
||||
let writeFilePromise: CancellablePromise<unknown> = Promise.resolve(),
|
||||
writeFileDeferred: CancellablePromise<unknown>;
|
||||
const maxRequests = options.processPart ? 5 : 10;
|
||||
const maxRequests = options.processPart ? 5 : 5;
|
||||
|
||||
/* if(fileWriter.length) {
|
||||
startOffset = fileWriter.length;
|
||||
@ -334,22 +336,26 @@ export class ApiFileManager {
|
||||
superpuper();
|
||||
}
|
||||
|
||||
//done += limit;
|
||||
done += result.bytes.byteLength;
|
||||
this.log('downloadFile requestFilePart result:', fileName, result);
|
||||
const isFinal = offset + limit >= size || !result.bytes.byteLength;
|
||||
if(result.bytes.byteLength) {
|
||||
//done += limit;
|
||||
done += result.bytes.byteLength;
|
||||
|
||||
const processedResult = await processDownloaded(result.bytes, offset);
|
||||
checkCancel();
|
||||
const processedResult = await processDownloaded(result.bytes, offset);
|
||||
checkCancel();
|
||||
|
||||
const isFinal = offset + limit >= size;
|
||||
//if(!isFinal) {
|
||||
////this.log('deferred notify 2:', {done: offset + limit, total: size}, deferred);
|
||||
deferred.notify({done, offset, total: size});
|
||||
//}
|
||||
//if(!isFinal) {
|
||||
////this.log('deferred notify 2:', {done: offset + limit, total: size}, deferred);
|
||||
deferred.notify({done, offset, total: size});
|
||||
//}
|
||||
|
||||
await writeFilePromise;
|
||||
checkCancel();
|
||||
await writeFilePromise;
|
||||
checkCancel();
|
||||
|
||||
await FileManager.write(fileWriter, processedResult);
|
||||
}
|
||||
|
||||
await FileManager.write(fileWriter, processedResult);
|
||||
writeFileDeferred.resolve();
|
||||
|
||||
if(isFinal) {
|
||||
|
@ -178,6 +178,23 @@ function timeout(delay: number): Promise<Response> {
|
||||
}));
|
||||
}
|
||||
|
||||
function responseForSafariFirstRange(range: [number, number], mimeType: string, size: number): Response {
|
||||
if(range[0] === 0 && range[1] === 1) {
|
||||
return new Response(new Uint8Array(2).buffer, {
|
||||
status: 206,
|
||||
statusText: 'Partial Content',
|
||||
headers: {
|
||||
'Accept-Ranges': 'bytes',
|
||||
'Content-Range': `bytes 0-1/${size || '*'}`,
|
||||
'Content-Length': '2',
|
||||
'Content-Type': mimeType || 'video/mp4',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
ctx.addEventListener('error', (error) => {
|
||||
log.error('error:', error);
|
||||
});
|
||||
@ -196,7 +213,17 @@ ctx.addEventListener('fetch', (event: FetchEvent): void => {
|
||||
case 'document':
|
||||
case 'photo': {
|
||||
const info: DownloadOptions = JSON.parse(decodeURIComponent(params));
|
||||
const fileName = getFileNameByLocation(info.location);
|
||||
|
||||
const rangeHeader = event.request.headers.get('Range');
|
||||
if(rangeHeader && info.mimeType && info.size) { // maybe safari
|
||||
const range = parseRange(event.request.headers.get('Range'));
|
||||
const possibleResponse = responseForSafariFirstRange(range, info.mimeType, info.size);
|
||||
if(possibleResponse) {
|
||||
return event.respondWith(possibleResponse);
|
||||
}
|
||||
}
|
||||
|
||||
const fileName = getFileNameByLocation(info.location, {fileName: info.fileName});
|
||||
|
||||
/* event.request.signal.addEventListener('abort', (e) => {
|
||||
console.log('[SW] user aborted request:', fileName);
|
||||
@ -221,7 +248,20 @@ ctx.addEventListener('fetch', (event: FetchEvent): void => {
|
||||
|
||||
log.debug('[fetch] file:', /* info, */fileName);
|
||||
|
||||
const promise = cancellablePromise.then(b => new Response(b));
|
||||
const promise = cancellablePromise.then(b => {
|
||||
const responseInit: ResponseInit = {};
|
||||
|
||||
if(rangeHeader) {
|
||||
responseInit.headers = {
|
||||
'Accept-Ranges': 'bytes',
|
||||
'Content-Range': `bytes 0-${info.size - 1}/${info.size || '*'}`,
|
||||
'Content-Length': `${info.size}`,
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(b, responseInit);
|
||||
});
|
||||
|
||||
event.respondWith(Promise.race([
|
||||
timeout(45 * 1000),
|
||||
promise
|
||||
@ -231,61 +271,55 @@ ctx.addEventListener('fetch', (event: FetchEvent): void => {
|
||||
}
|
||||
|
||||
case 'stream': {
|
||||
const [offset, end] = parseRange(event.request.headers.get('Range'));
|
||||
const range = parseRange(event.request.headers.get('Range'));
|
||||
const [offset, end] = range;
|
||||
|
||||
const info: DownloadOptions = JSON.parse(decodeURIComponent(params));
|
||||
//const fileName = getFileNameByLocation(info.location);
|
||||
|
||||
log.debug('[stream]', url, offset, end);
|
||||
|
||||
event.respondWith(new Promise((resolve, reject) => {
|
||||
// safari workaround
|
||||
if(offset === 0 && end === 1) {
|
||||
resolve(new Response(new Uint8Array(2).buffer, {
|
||||
status: 206,
|
||||
statusText: 'Partial Content',
|
||||
headers: {
|
||||
'Accept-Ranges': 'bytes',
|
||||
'Content-Range': `bytes 0-1/${info.size || '*'}`,
|
||||
'Content-Length': '2',
|
||||
'Content-Type': info.mimeType || 'video/mp4',
|
||||
},
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const limit = end && end < STREAM_CHUNK_UPPER_LIMIT ? alignLimit(end - offset + 1) : STREAM_CHUNK_UPPER_LIMIT;
|
||||
const alignedOffset = alignOffset(offset, limit);
|
||||
|
||||
//log.debug('[stream] requestFilePart:', info.dcID, info.location, alignedOffset, limit);
|
||||
|
||||
apiFileManager.requestFilePart(info.dcID, info.location, alignedOffset, limit).then(result => {
|
||||
let ab = result.bytes;
|
||||
|
||||
//log.debug('[stream] requestFilePart result:', result);
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'Accept-Ranges': 'bytes',
|
||||
'Content-Range': `bytes ${alignedOffset}-${alignedOffset + ab.byteLength - 1}/${info.size || '*'}`,
|
||||
'Content-Length': `${ab.byteLength}`,
|
||||
};
|
||||
|
||||
if(info.mimeType) headers['Content-Type'] = info.mimeType;
|
||||
|
||||
if(isSafari) {
|
||||
ab = ab.slice(offset - alignedOffset, end - alignedOffset + 1);
|
||||
headers['Content-Range'] = `bytes ${offset}-${offset + ab.byteLength - 1}/${info.size || '*'}`;
|
||||
headers['Content-Length'] = `${ab.byteLength}`;
|
||||
event.respondWith(Promise.race([
|
||||
timeout(45 * 1000),
|
||||
new Promise<Response>((resolve, reject) => {
|
||||
// safari workaround
|
||||
const possibleResponse = responseForSafariFirstRange(range, info.mimeType, info.size);
|
||||
if(possibleResponse) {
|
||||
return resolve(possibleResponse);
|
||||
}
|
||||
|
||||
resolve(new Response(ab, {
|
||||
status: 206,
|
||||
statusText: 'Partial Content',
|
||||
headers,
|
||||
}));
|
||||
});
|
||||
}));
|
||||
const limit = end && end < STREAM_CHUNK_UPPER_LIMIT ? alignLimit(end - offset + 1) : STREAM_CHUNK_UPPER_LIMIT;
|
||||
const alignedOffset = alignOffset(offset, limit);
|
||||
|
||||
//log.debug('[stream] requestFilePart:', info.dcID, info.location, alignedOffset, limit);
|
||||
|
||||
apiFileManager.requestFilePart(info.dcID, info.location, alignedOffset, limit).then(result => {
|
||||
let ab = result.bytes;
|
||||
|
||||
//log.debug('[stream] requestFilePart result:', result);
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'Accept-Ranges': 'bytes',
|
||||
'Content-Range': `bytes ${alignedOffset}-${alignedOffset + ab.byteLength - 1}/${info.size || '*'}`,
|
||||
'Content-Length': `${ab.byteLength}`,
|
||||
};
|
||||
|
||||
if(info.mimeType) headers['Content-Type'] = info.mimeType;
|
||||
|
||||
if(isSafari) {
|
||||
ab = ab.slice(offset - alignedOffset, end - alignedOffset + 1);
|
||||
headers['Content-Range'] = `bytes ${offset}-${offset + ab.byteLength - 1}/${info.size || '*'}`;
|
||||
headers['Content-Length'] = `${ab.byteLength}`;
|
||||
}
|
||||
|
||||
resolve(new Response(ab, {
|
||||
status: 206,
|
||||
statusText: 'Partial Content',
|
||||
headers,
|
||||
}));
|
||||
});
|
||||
})
|
||||
]));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -538,7 +538,8 @@ export function getEmojiToneIndex(input: string) {
|
||||
return match ? 5 - (57343 - match[0].charCodeAt(0)) : 0;
|
||||
}
|
||||
|
||||
export function getFileURL(type: 'photo' | 'thumb' | 'document' | 'stream' | 'download', options: {
|
||||
export type FileURLType = 'photo' | 'thumb' | 'document' | 'stream' | 'download';
|
||||
export function getFileURL(type: FileURLType, options: {
|
||||
dcID: number,
|
||||
location: InputFileLocation | FileLocation,
|
||||
size?: number,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Page from "./page";
|
||||
import { whichChild } from "../lib/utils";
|
||||
import { horizontalMenu } from "../components/misc";
|
||||
import lottieLoader from "../lib/lottieLoader";
|
||||
import { horizontalMenu } from "../components/horizontalMenu";
|
||||
|
||||
class PagesManager {
|
||||
private pageID = -1;
|
||||
|
@ -328,19 +328,22 @@
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> div {
|
||||
> .gif {
|
||||
flex: 1 0 auto;
|
||||
max-width: 100%;
|
||||
height: 100px;
|
||||
margin: 2.5px;
|
||||
cursor: pointer;
|
||||
background: #000;
|
||||
//background: #000;
|
||||
position: relative;
|
||||
|
||||
video {
|
||||
video, img {
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,39 +2,39 @@
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
//"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"target": "es2016", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
//"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"target": "es2016", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
"lib": ["es2016", "dom", "ES2018.Promise", "webworker"], /* Specify library files to be included in the compilation. */
|
||||
"allowJs": true, /* Allow javascript files to be compiled. */
|
||||
"lib": ["es2016", "dom", "ES2018.Promise", "webworker"], /* Specify library files to be included in the compilation. */
|
||||
"allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
"sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
"sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
// "outDir": "./dist/", /* Redirect output structure to the directory. */
|
||||
//"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "outDir": "./dist/", /* Redirect output structure to the directory. */
|
||||
//"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
//"importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
"downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
//"importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
"downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
"strictNullChecks": false, /* Enable strict null checks. */
|
||||
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
"strictNullChecks": false, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
"strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */
|
||||
"strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
//"noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
//"noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
@ -45,9 +45,9 @@
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
"types": ["chrome"/* , "node" */, "jest"],/* Type declaration files to be included in compilation. */
|
||||
"allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
"types": ["chrome", "jest"], /* Type declaration files to be included in compilation. */
|
||||
"allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
"resolveJsonModule": true
|
||||
|
@ -6,7 +6,7 @@ const postcssPresetEnv = require('postcss-preset-env');
|
||||
const ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin');
|
||||
const fs = require('fs');
|
||||
|
||||
const allowedIPs = ['195.66.140.39', '192.168.31.144', '127.0.0.1', '192.168.31.1', '192.168.31.192', '176.100.18.181'];
|
||||
const allowedIPs = ['195.66.140.39', '192.168.31.144', '127.0.0.1', '192.168.31.1', '192.168.31.192', '176.100.18.181', '46.219.250.22'];
|
||||
const devMode = process.env.NODE_ENV !== 'production';
|
||||
const useLocal = false;
|
||||
|
||||
@ -115,7 +115,10 @@ module.exports = {
|
||||
console.log('Bad IP connecting: ' + IP, req.url);
|
||||
res.status(404).send('Nothing interesting here.');
|
||||
} else {
|
||||
console.log(req.url, IP);
|
||||
if(req.url.indexOf('/assets/') !== 0) {
|
||||
console.log(req.url, IP);
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user