Browse Source

Fix stickers panel performance & styles

master
Eduard Kuzmenko 2 years ago
parent
commit
9b6fe590fa
  1. 4
      src/components/chat/inlineHelper.ts
  2. 14
      src/components/emoticonsDropdown/index.ts
  3. 364
      src/components/emoticonsDropdown/tabs/stickers.ts
  4. 2
      src/components/gifsMasonry.ts
  5. 4
      src/components/lazyLoadQueue.ts
  6. 9
      src/components/lazyLoadQueueRepeat.ts
  7. 5
      src/components/lazyLoadQueueRepeat2.ts
  8. 19
      src/components/visibilityIntersector.ts
  9. 6
      src/components/wrappers/stickerSetThumb.ts
  10. 3
      src/helpers/dropdownHover.ts
  11. 4
      src/index.hbs
  12. 9
      src/lib/appManagers/appStickersManager.ts
  13. 195
      src/scss/partials/_emojiDropdown.scss
  14. 4
      src/scss/style.scss

4
src/components/chat/inlineHelper.ts

@ -203,8 +203,8 @@ export default class InlineHelper extends AutocompleteHelper {
} else if(media.type === 'sticker') { } else if(media.type === 'sticker') {
container.classList.add('super-sticker'); container.classList.add('super-sticker');
this.superStickerRenderer.renderSticker(media, container, loadPromises); this.superStickerRenderer.renderSticker(media, container, loadPromises);
if(media.sticker === 2) { if(media.animated) {
this.superStickerRenderer.observeAnimatedDiv(container); this.superStickerRenderer.observeAnimated(container);
} }
} }
} else if(media) { } else if(media) {

14
src/components/emoticonsDropdown/index.ts

@ -187,6 +187,10 @@ export class EmoticonsDropdown extends DropdownHover {
return super.init(); return super.init();
} }
public getElement() {
return this.element;
}
private onSelectTabClick = (id: number) => { private onSelectTabClick = (id: number) => {
if(this.tabId === id) { if(this.tabId === id) {
return; return;
@ -248,11 +252,11 @@ export class EmoticonsDropdown extends DropdownHover {
setActive(which); setActive(which);
if(menuScroll) { if(menuScroll) {
if(which < menu.childElementCount - 4) { menuScroll.scrollIntoViewNew({
menuScroll.container.scrollLeft = (which - 3) * 47; element: menu.children[which] as HTMLElement,
} else { position: 'center',
menuScroll.container.scrollLeft = which * 47; axis: 'x'
} });
} }
}); });

364
src/components/emoticonsDropdown/tabs/stickers.ts

@ -5,7 +5,6 @@
*/ */
import emoticonsDropdown, { EmoticonsDropdown, EMOTICONSSTICKERGROUP, EmoticonsTab } from ".."; import emoticonsDropdown, { EmoticonsDropdown, EMOTICONSSTICKERGROUP, EmoticonsTab } from "..";
import findUpAttribute from "../../../helpers/dom/findUpAttribute";
import findUpClassName from "../../../helpers/dom/findUpClassName"; import findUpClassName from "../../../helpers/dom/findUpClassName";
import mediaSizes from "../../../helpers/mediaSizes"; import mediaSizes from "../../../helpers/mediaSizes";
import { MessagesAllStickers, StickerSet } from "../../../layer"; import { MessagesAllStickers, StickerSet } from "../../../layer";
@ -22,62 +21,72 @@ import PopupStickers from "../../popups/stickers";
import Scrollable, { ScrollableX } from "../../scrollable"; import Scrollable, { ScrollableX } from "../../scrollable";
import StickyIntersector from "../../stickyIntersector"; import StickyIntersector from "../../stickyIntersector";
import { wrapSticker, wrapStickerSetThumb } from "../../wrappers"; import { wrapSticker, wrapStickerSetThumb } from "../../wrappers";
import ButtonIcon from "../../buttonIcon";
import positionElementByIndex from "../../../helpers/dom/positionElementByIndex";
import VisibilityIntersector, { OnVisibilityChange } from "../../visibilityIntersector";
import findAndSplice from "../../../helpers/array/findAndSplice";
export class SuperStickerRenderer { export class SuperStickerRenderer {
public lazyLoadQueue: LazyLoadQueueRepeat; public lazyLoadQueue: LazyLoadQueueRepeat;
private animatedDivs: Set<HTMLDivElement> = new Set(); private animated: Set<HTMLElement> = new Set();
constructor( constructor(
private regularLazyLoadQueue: LazyLoadQueue, private regularLazyLoadQueue: LazyLoadQueue,
private group: string, private group: string,
private managers: AppManagers private managers: AppManagers,
private options?: IntersectionObserverInit
) { ) {
this.lazyLoadQueue = new LazyLoadQueueRepeat(undefined, (target, visible) => { this.lazyLoadQueue = new LazyLoadQueueRepeat(undefined, ({target, visible}) => {
if(!visible) { if(!visible) {
this.processInvisibleDiv(target as HTMLDivElement); this.processInvisible(target);
} }
}); }, options);
} }
public clear() { public clear() {
this.lazyLoadQueue.clear(); this.lazyLoadQueue.clear();
} }
public renderSticker(doc: MyDocument, div?: HTMLDivElement, loadPromises?: Promise<any>[]) { public renderSticker(doc: MyDocument, element?: HTMLElement, loadPromises?: Promise<any>[]) {
if(!div) { if(!element) {
div = document.createElement('div'); element = document.createElement('div');
div.classList.add('grid-item', 'super-sticker'); element.classList.add('grid-item', 'super-sticker');
element.dataset.docId = '' + doc.id;
if(doc.animated) { if(doc.animated) {
this.observeAnimatedDiv(div); this.observeAnimated(element);
} }
} }
// * This will wrap only a thumb // * This will wrap only a thumb
wrapSticker({ /* !doc.animated && */wrapSticker({
doc, doc,
div, div: element,
lazyLoadQueue: this.regularLazyLoadQueue, lazyLoadQueue: this.regularLazyLoadQueue,
group: this.group, group: this.group,
onlyThumb: doc.animated, onlyThumb: doc.animated,
loadPromises loadPromises
}); });
return div; return element;
} }
public observeAnimatedDiv(div: HTMLDivElement) { public observeAnimated(element: HTMLElement) {
this.animatedDivs.add(div); this.animated.add(element);
this.lazyLoadQueue.observe({ this.lazyLoadQueue.observe({
div, div: element,
load: this.processVisibleDiv load: this.processVisible
}); });
} }
private checkAnimationContainer = (div: HTMLElement, visible: boolean) => { public unobserveAnimated(element: HTMLElement) {
this.animated.delete(element);
this.lazyLoadQueue.unobserve(element);
}
private checkAnimationContainer = (element: HTMLElement, visible: boolean) => {
//console.error('checkAnimationContainer', div, visible); //console.error('checkAnimationContainer', div, visible);
const players = animationIntersector.getAnimations(div); const players = animationIntersector.getAnimations(element);
players.forEach((player) => { players.forEach((player) => {
if(!visible) { if(!visible) {
animationIntersector.checkAnimation(player, true, true); animationIntersector.checkAnimation(player, true, true);
@ -87,8 +96,8 @@ export class SuperStickerRenderer {
}); });
}; };
private processVisibleDiv = async(div: HTMLElement) => { private processVisible = async(element: HTMLElement) => {
const docId = div.dataset.docId; const docId = element.dataset.docId;
const doc = await this.managers.appDocsManager.getDoc(docId); const doc = await this.managers.appDocsManager.getDoc(docId);
const size = mediaSizes.active.esgSticker.width; const size = mediaSizes.active.esgSticker.width;
@ -97,7 +106,7 @@ export class SuperStickerRenderer {
const promise = wrapSticker({ const promise = wrapSticker({
doc, doc,
div: div as HTMLDivElement, div: element,
width: size, width: size,
height: size, height: size,
lazyLoadQueue: null, lazyLoadQueue: null,
@ -109,7 +118,7 @@ export class SuperStickerRenderer {
promise.then(() => { promise.then(() => {
//clearTimeout(timeout); //clearTimeout(timeout);
this.checkAnimationContainer(div, this.lazyLoadQueue.intersector.isVisible(div)); this.checkAnimationContainer(element, this.lazyLoadQueue.intersector.isVisible(element));
}); });
/* let timeout = window.setTimeout(() => { /* let timeout = window.setTimeout(() => {
@ -119,123 +128,140 @@ export class SuperStickerRenderer {
return promise; return promise;
}; };
public processInvisibleDiv = async(div: HTMLElement) => { public processInvisible = async(element: HTMLElement) => {
const docId = div.dataset.docId; const docId = element.dataset.docId;
const doc = await this.managers.appDocsManager.getDoc(docId); const doc = await this.managers.appDocsManager.getDoc(docId);
//console.log('STICKER INvisible:', /* div, */docId); //console.log('STICKER INvisible:', /* div, */docId);
this.checkAnimationContainer(div, false); this.checkAnimationContainer(element, false);
div.innerHTML = ''; element.textContent = '';
this.renderSticker(doc, div as HTMLDivElement); this.renderSticker(doc, element as HTMLDivElement);
}; };
} }
type StickersTabCategory = {
elements: {
container: HTMLElement,
title: HTMLElement,
items: HTMLElement,
menuTab: HTMLElement,
menuTabPadding: HTMLElement
},
set: StickerSet.stickerSet,
items: {
document: MyDocument,
element: HTMLElement
}[]
};
const RECENT_STICKERS_COUNT = 20;
export default class StickersTab implements EmoticonsTab { export default class StickersTab implements EmoticonsTab {
private content: HTMLElement; private content: HTMLElement;
private stickersDiv: HTMLElement;
private stickerSets: {[id: string]: {
stickers: HTMLElement,
tab: HTMLElement
}} = {};
private recentDiv: HTMLElement; private categories: {[id: string]: StickersTabCategory};
private recentStickers: MyDocument[] = []; private categoriesMap: Map<HTMLElement, StickersTabCategory>;
private categoriesIntersector: VisibilityIntersector;
private scroll: Scrollable; private scroll: Scrollable;
private menu: HTMLElement; private menu: HTMLElement;
private mounted = false; private mounted = false;
private queueCategoryPush: {element: HTMLElement, prepend: boolean}[] = [];
private stickyIntersector: StickyIntersector; private stickyIntersector: StickyIntersector;
private superStickerRenderer: SuperStickerRenderer; private superStickerRenderer: SuperStickerRenderer;
constructor(private managers: AppManagers) { constructor(private managers: AppManagers) {
this.categories = {};
this.categoriesMap = new Map();
} }
categoryPush(categoryDiv: HTMLElement, categoryTitle: DocumentFragment | string = '', promise: Promise<MyDocument[]>, prepend?: boolean) { private createCategory(stickerSet: StickerSet.stickerSet, _title: HTMLElement | DocumentFragment) {
//if((docs.length % 5) !== 0) categoryDiv.classList.add('not-full'); const container = document.createElement('div');
container.classList.add('emoji-category', 'hide');
const itemsDiv = document.createElement('div'); const items = document.createElement('div');
itemsDiv.classList.add('category-items', 'super-stickers'); items.classList.add('category-items', 'super-stickers');
const titleDiv = document.createElement('div'); const title = document.createElement('div');
titleDiv.classList.add('category-title'); title.classList.add('category-title');
title.append(_title);
if(categoryTitle) { const menuTab = ButtonIcon(undefined, {noRipple: true});
if(typeof(categoryTitle) === 'string') titleDiv.innerHTML = categoryTitle; menuTab.classList.add('menu-horizontal-div-item');
else titleDiv.append(categoryTitle);
} const menuTabPadding = document.createElement('div');
menuTabPadding.classList.add('menu-horizontal-div-item-padding');
categoryDiv.append(titleDiv, itemsDiv); menuTab.append(menuTabPadding);
this.stickyIntersector.observeStickyHeaderChanges(categoryDiv); const category: StickersTabCategory = {
elements: {
container,
title,
items,
menuTab,
menuTabPadding
},
set: stickerSet,
items: []
};
this.queueCategoryPush.push({element: categoryDiv, prepend}); container.append(title, items);
promise.then((documents) => { this.categories[stickerSet.id] = category;
documents.forEach((doc) => { this.categoriesMap.set(container, category);
//if(doc._ === 'documentEmpty') return;
itemsDiv.append(this.superStickerRenderer.renderSticker(doc));
});
if(this.queueCategoryPush.length) { this.categoriesIntersector.observe(container);
this.queueCategoryPush.forEach(({element, prepend}) => {
if(prepend) {
if(this.recentDiv.parentElement) {
this.stickersDiv.prepend(element);
this.stickersDiv.prepend(this.recentDiv);
} else {
this.stickersDiv.prepend(element);
}
} else this.stickersDiv.append(element);
});
this.queueCategoryPush.length = 0; return category;
} }
});
return {titleDiv}; private categoryPush(
} category: StickersTabCategory,
promise: Promise<MyDocument[]>
) {
const {container, items} = category.elements;
this.stickyIntersector.observeStickyHeaderChanges(container);
async renderStickerSet(set: StickerSet.stickerSet, prepend = false) { promise.then((documents) => {
const categoryDiv = document.createElement('div'); documents.forEach((document) => {
categoryDiv.classList.add('sticker-category'); const element = this.superStickerRenderer.renderSticker(document);
categoryDiv.dataset.id = '' + set.id; category.items.push({document, element});
categoryDiv.dataset.access_hash = '' + set.access_hash; // items.append(element);
});
const button = document.createElement('button'); const containerWidth = 410;
button.classList.add('btn-icon', 'menu-horizontal-div-item'); const stickerSize = mediaSizes.active.esgSticker.width;
this.stickerSets[set.id] = { const itemsPerRow = Math.floor(containerWidth / stickerSize);
stickers: categoryDiv, const rows = Math.ceil(documents.length / itemsPerRow);
tab: button const height = rows * stickerSize;
};
if(prepend) { items.style.height = height + 'px';
this.menu.insertBefore(button, this.menu.firstElementChild.nextSibling);
} else { container.classList.remove('hide');
this.menu.append(button); });
} }
//stickersScroll.append(categoryDiv); private async renderStickerSet(set: StickerSet.stickerSet, prepend = false) {
const category = this.createCategory(set, wrapEmojiText(set.title));
const {menuTab, menuTabPadding, container} = category.elements;
positionElementByIndex(menuTab, this.menu, prepend ? 1 : 0xFFFF);
const promise = this.managers.appStickersManager.getStickerSet(set); const promise = this.managers.appStickersManager.getStickerSet(set);
this.categoryPush(categoryDiv, wrapEmojiText(set.title), promise.then((stickerSet) => stickerSet.documents as MyDocument[]), prepend); this.categoryPush(
const stickerSet = await promise; category,
promise.then((stickerSet) => stickerSet.documents as MyDocument[])
);
// const stickerSet = await promise;
//console.log('got stickerSet', stickerSet, li); positionElementByIndex(container, this.scroll.container, prepend ? 1 : 0xFFFF, -1);
wrapStickerSetThumb({ wrapStickerSetThumb({
set, set,
container: button, container: menuTabPadding,
group: EMOTICONSSTICKERGROUP, group: EMOTICONSSTICKERGROUP,
lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue, lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue,
width: 32, width: 32,
@ -244,21 +270,18 @@ export default class StickersTab implements EmoticonsTab {
}); });
} }
init() { public init() {
this.content = document.getElementById('content-stickers'); this.content = document.getElementById('content-stickers');
//let stickersDiv = contentStickersDiv.querySelector('.os-content') as HTMLDivElement;
this.recentDiv = document.createElement('div'); const menuWrapper = this.content.previousElementSibling as HTMLDivElement;
this.recentDiv.classList.add('sticker-category', 'stickers-recent');
let menuWrapper = this.content.previousElementSibling as HTMLDivElement;
this.menu = menuWrapper.firstElementChild as HTMLUListElement; this.menu = menuWrapper.firstElementChild as HTMLUListElement;
let menuScroll = new ScrollableX(menuWrapper); const menuScroll = new ScrollableX(menuWrapper);
this.stickersDiv = document.createElement('div'); this.scroll = new Scrollable(this.content, 'STICKERS');
this.stickersDiv.classList.add('stickers-categories'); this.scroll.onAdditionalScroll = () => {
this.content.append(this.stickersDiv); setTyping();
};
/* stickersDiv.addEventListener('mouseover', (e) => { /* stickersDiv.addEventListener('mouseover', (e) => {
let target = e.target as HTMLElement; let target = e.target as HTMLElement;
@ -277,30 +300,43 @@ export default class StickersTab implements EmoticonsTab {
} }
}); */ }); */
rootScope.addEventListener('stickers_installed', (e) => { const onCategoryVisibility: OnVisibilityChange = ({target, visible, entry}) => {
const set: StickerSet.stickerSet = e; const category = this.categoriesMap.get(target);
// console.log('roll the windows up', category, target, visible, entry);
if(!visible) {
category.elements.items.textContent = '';
} else {
category.elements.items.append(...category.items.map(({element}) => element));
}
};
if(!this.stickerSets[set.id] && this.mounted) { const intersectionOptions: IntersectionObserverInit = {root: emoticonsDropdown.getElement()};
this.categoriesIntersector = new VisibilityIntersector(onCategoryVisibility, intersectionOptions);
rootScope.addEventListener('stickers_installed', (set) => {
if(!this.categories[set.id] && this.mounted) {
this.renderStickerSet(set, true); this.renderStickerSet(set, true);
} }
}); });
rootScope.addEventListener('stickers_deleted', (e) => { rootScope.addEventListener('stickers_deleted', ({id}) => {
const set: StickerSet.stickerSet = e; const set = this.categories[id];
if(set && this.mounted) {
if(this.stickerSets[set.id] && this.mounted) { set.elements.container.remove();
const elements = this.stickerSets[set.id]; set.elements.menuTab.remove();
elements.stickers.remove(); this.categoriesIntersector.unobserve(set.elements.container);
elements.tab.remove(); set.items.forEach(({element}) => this.superStickerRenderer.unobserveAnimated(element));
delete this.stickerSets[set.id]; delete this.categories[id];
this.categoriesMap.delete(set.elements.container);
} }
}); });
this.stickersDiv.addEventListener('click', (e) => { this.scroll.container.addEventListener('click', (e) => {
const target = e.target as HTMLElement; const target = e.target as HTMLElement;
if(findUpClassName(target, 'category-title')) { if(findUpClassName(target, 'category-title')) {
const el = findUpAttribute(target, 'data-id'); const container = findUpClassName(target, 'emoji-category');
new PopupStickers({id: el.dataset.id, access_hash: el.dataset.access_hash}).show(); const category = this.categoriesMap.get(container);
new PopupStickers({id: category.set.id, access_hash: category.set.access_hash}).show();
return; return;
} }
@ -311,12 +347,6 @@ export default class StickersTab implements EmoticonsTab {
rootScope.dispatchEvent('choosing_sticker', !cancel); rootScope.dispatchEvent('choosing_sticker', !cancel);
}; };
this.scroll = new Scrollable(this.content, 'STICKERS');
this.scroll.setVirtualContainer(this.stickersDiv);
this.scroll.onAdditionalScroll = () => {
setTyping();
};
emoticonsDropdown.addEventListener('closed', () => { emoticonsDropdown.addEventListener('closed', () => {
setTyping(true); setTyping(true);
}); });
@ -329,20 +359,19 @@ export default class StickersTab implements EmoticonsTab {
const preloader = putPreloader(this.content, true); const preloader = putPreloader(this.content, true);
const recentCategory = this.createCategory({id: 'recent'} as any, i18n('Stickers.Recent'));
recentCategory.elements.title.classList.add('disable-hover');
recentCategory.elements.menuTab.classList.add('tgico-recent', 'active');
recentCategory.elements.menuTabPadding.remove();
positionElementByIndex(recentCategory.elements.container, this.scroll.container, 0);
positionElementByIndex(recentCategory.elements.menuTab, this.menu, 0);
Promise.all([ Promise.all([
this.managers.appStickersManager.getRecentStickers().then((stickers) => { this.managers.appStickersManager.getRecentStickers().then((stickers) => {
this.recentStickers = stickers.stickers.slice(0, 20) as MyDocument[]; const sliced = stickers.stickers.slice(0, RECENT_STICKERS_COUNT) as MyDocument[];
//stickersScroll.prepend(categoryDiv);
this.stickerSets['recent'] = {
stickers: this.recentDiv,
tab: this.menu.firstElementChild as HTMLElement
};
preloader.remove(); preloader.remove();
const {titleDiv} = this.categoryPush(this.recentDiv, '', Promise.resolve(this.recentStickers), true); this.categoryPush(recentCategory, Promise.resolve(sliced));
titleDiv.append(i18n('Stickers.Recent'));
}), }),
this.managers.appStickersManager.getAllStickers().then((res) => { this.managers.appStickersManager.getAllStickers().then((res) => {
@ -357,38 +386,61 @@ export default class StickersTab implements EmoticonsTab {
setTyping(); setTyping();
}); });
this.superStickerRenderer = new SuperStickerRenderer(EmoticonsDropdown.lazyLoadQueue, EMOTICONSSTICKERGROUP, this.managers); this.superStickerRenderer = new SuperStickerRenderer(EmoticonsDropdown.lazyLoadQueue, EMOTICONSSTICKERGROUP, this.managers, intersectionOptions);
emoticonsDropdown.addLazyLoadQueueRepeat(this.superStickerRenderer.lazyLoadQueue, this.superStickerRenderer.processInvisibleDiv); const rendererLazyLoadQueue = this.superStickerRenderer.lazyLoadQueue;
emoticonsDropdown.addLazyLoadQueueRepeat(rendererLazyLoadQueue, this.superStickerRenderer.processInvisible);
/* setInterval(() => { // emoticonsDropdown.addEventListener('close', () => {
// @ts-ignore // this.categoriesIntersector.lock();
const players = Object.values(lottieLoader.players).filter((p) => p.width === 80); // });
// emoticonsDropdown.addEventListener('closed', () => {
// for(const [container] of this.categoriesMap) {
// onCategoryVisibility(container, false);
// }
// });
console.log('STICKERS RENDERED IN PANEL:', players.length, players.filter((p) => !p.paused).length, this.superStickerRenderer.lazyLoadQueue.intersector.getVisible().length); // emoticonsDropdown.addEventListener('opened', () => {
}, .25e3); */ // this.categoriesIntersector.unlockAndRefresh();
// });
// setInterval(() => {
// // @ts-ignore
// const players = Object.values(lottieLoader.players).filter((p) => p.width >= 80);
// console.log(
// 'STICKERS RENDERED IN PANEL:',
// players.length,
// players.filter((p) => !p.paused).length,
// rendererLazyLoadQueue.intersector.getVisible().length
// );
// }, .25e3);
this.init = null; this.init = null;
} }
pushRecentSticker(doc: MyDocument) { public pushRecentSticker(doc: MyDocument) {
this.managers.appStickersManager.pushRecentSticker(doc); this.managers.appStickersManager.pushRecentSticker(doc.id);
if(!this.recentDiv?.parentElement) { const set = this.categories['recent'];
if(!set) {
return; return;
} }
let div = this.recentDiv.querySelector(`[data-doc-id="${doc.id}"]`); const items = set.elements.items;
if(!div) { let item = findAndSplice(set.items, (item) => item.document.id === doc.id);
div = this.superStickerRenderer.renderSticker(doc); if(!item) {
item = {
element: this.superStickerRenderer.renderSticker(doc),
document: doc
};
} }
const items = this.recentDiv.querySelector('.category-items'); set.items.unshift(item);
items.prepend(div); if(items.childElementCount) items.prepend(item.element);
if(items.childElementCount > RECENT_STICKERS_COUNT) {
if(items.childElementCount > 20) { (Array.from(items.children) as HTMLElement[]).slice(RECENT_STICKERS_COUNT).forEach((el) => el.remove());
(Array.from(items.children) as HTMLElement[]).slice(20).forEach((el) => el.remove());
} }
} }

2
src/components/gifsMasonry.ts

@ -34,7 +34,7 @@ export default class GifsMasonry {
) { ) {
this.managers = rootScope.managers; this.managers = rootScope.managers;
this.lazyLoadQueue = new LazyLoadQueueRepeat2(undefined, (target, visible) => { this.lazyLoadQueue = new LazyLoadQueueRepeat2(undefined, ({target, visible}) => {
if(visible) { if(visible) {
this.processVisibleDiv(target); this.processVisibleDiv(target);
} else { } else {

4
src/components/lazyLoadQueue.ts

@ -4,7 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
import VisibilityIntersector from "./visibilityIntersector"; import VisibilityIntersector, { OnVisibilityChangeItem } from "./visibilityIntersector";
import findAndSpliceAll from "../helpers/array/findAndSpliceAll"; import findAndSpliceAll from "../helpers/array/findAndSpliceAll";
import findAndSplice from "../helpers/array/findAndSplice"; import findAndSplice from "../helpers/array/findAndSplice";
import LazyLoadQueueIntersector, { LazyLoadElement } from "./lazyLoadQueueIntersector"; import LazyLoadQueueIntersector, { LazyLoadElement } from "./lazyLoadQueueIntersector";
@ -16,7 +16,7 @@ export default class LazyLoadQueue extends LazyLoadQueueIntersector {
this.intersector = new VisibilityIntersector(this.onVisibilityChange); this.intersector = new VisibilityIntersector(this.onVisibilityChange);
} }
private onVisibilityChange = (target: HTMLElement, visible: boolean) => { private onVisibilityChange = ({target, visible}: OnVisibilityChangeItem) => {
if(visible) { if(visible) {
/* if(DEBUG) { /* if(DEBUG) {
this.log('isIntersecting', target); this.log('isIntersecting', target);

9
src/components/lazyLoadQueueRepeat.ts

@ -11,10 +11,11 @@ import VisibilityIntersector, { OnVisibilityChange } from "./visibilityIntersect
export default class LazyLoadQueueRepeat extends LazyLoadQueueIntersector { export default class LazyLoadQueueRepeat extends LazyLoadQueueIntersector {
private _queue: Map<HTMLElement, LazyLoadElement> = new Map(); private _queue: Map<HTMLElement, LazyLoadElement> = new Map();
constructor(parallelLimit?: number, protected onVisibilityChange?: OnVisibilityChange) { constructor(parallelLimit?: number, protected onVisibilityChange?: OnVisibilityChange, options?: IntersectionObserverInit) {
super(parallelLimit); super(parallelLimit);
this.intersector = new VisibilityIntersector((target, visible) => { this.intersector = new VisibilityIntersector((item) => {
const {target, visible} = item;
const spliced = findAndSpliceAll(this.queue, (i) => i.div === target); const spliced = findAndSpliceAll(this.queue, (i) => i.div === target);
if(visible) { if(visible) {
const items = spliced.length ? spliced : [this._queue.get(target)]; const items = spliced.length ? spliced : [this._queue.get(target)];
@ -23,9 +24,9 @@ export default class LazyLoadQueueRepeat extends LazyLoadQueueIntersector {
}); });
} }
this.onVisibilityChange && this.onVisibilityChange(target, visible); this.onVisibilityChange && this.onVisibilityChange(item);
this.setProcessQueueTimeout(); this.setProcessQueueTimeout();
}); }, options);
} }
public clear() { public clear() {

5
src/components/lazyLoadQueueRepeat2.ts

@ -12,7 +12,8 @@ export default class LazyLoadQueueRepeat2 extends LazyLoadQueueIntersector {
constructor(parallelLimit?: number, protected onVisibilityChange?: OnVisibilityChange) { constructor(parallelLimit?: number, protected onVisibilityChange?: OnVisibilityChange) {
super(parallelLimit); super(parallelLimit);
this.intersector = new VisibilityIntersector((target, visible) => { this.intersector = new VisibilityIntersector((item) => {
const {target, visible} = item;
const spliced = findAndSpliceAll(this.queue, (i) => i.div === target); const spliced = findAndSpliceAll(this.queue, (i) => i.div === target);
if(visible && spliced.length) { if(visible && spliced.length) {
spliced.forEach((item) => { spliced.forEach((item) => {
@ -20,7 +21,7 @@ export default class LazyLoadQueueRepeat2 extends LazyLoadQueueIntersector {
}); });
} }
this.onVisibilityChange && this.onVisibilityChange(target, visible); this.onVisibilityChange && this.onVisibilityChange(item);
this.setProcessQueueTimeout(); this.setProcessQueueTimeout();
}); });
} }

19
src/components/visibilityIntersector.ts

@ -5,20 +5,21 @@
*/ */
type TargetType = HTMLElement; type TargetType = HTMLElement;
export type OnVisibilityChange = (target: TargetType, visible: boolean) => void; export type OnVisibilityChangeItem = {target: TargetType, visible: boolean, entry: IntersectionObserverEntry};
export type OnVisibilityChange = (item: OnVisibilityChangeItem) => void;
export default class VisibilityIntersector { export default class VisibilityIntersector {
private observer: IntersectionObserver; private observer: IntersectionObserver;
private items: Map<TargetType, boolean> = new Map(); private items: Map<TargetType, boolean> = new Map();
private locked = false; private locked = false;
constructor(onVisibilityChange: OnVisibilityChange) { constructor(onVisibilityChange: OnVisibilityChange, options?: IntersectionObserverInit) {
this.observer = new IntersectionObserver((entries) => { this.observer = new IntersectionObserver((entries) => {
if(this.locked) { if(this.locked) {
return; return;
} }
const changed: {target: TargetType, visible: boolean}[] = []; const changed: OnVisibilityChangeItem[] = [];
entries.forEach((entry) => { entries.forEach((entry) => {
const target = entry.target as TargetType; const target = entry.target as TargetType;
@ -37,15 +38,19 @@ export default class VisibilityIntersector {
return; return;
} */ } */
changed[entry.isIntersecting ? 'unshift' : 'push']({target, visible: entry.isIntersecting}); const change: typeof changed[0] = {target, visible: entry.isIntersecting, entry};
// ! order will be incorrect so can't use it
// changed[entry.isIntersecting ? 'unshift' : 'push'](change);
changed.push(change);
//onVisibilityChange(target, entry.isIntersecting); //onVisibilityChange(target, entry.isIntersecting);
}); });
changed.forEach((smth) => { changed.forEach((item) => {
onVisibilityChange(smth.target, smth.visible); onVisibilityChange(item);
});
}); });
}, options);
} }
public getVisible() { public getVisible() {

6
src/components/wrappers/stickerSetThumb.ts

@ -11,6 +11,7 @@ import appDownloadManager from "../../lib/appManagers/appDownloadManager";
import { AppManagers } from "../../lib/appManagers/managers"; import { AppManagers } from "../../lib/appManagers/managers";
import lottieLoader from "../../lib/rlottie/lottieLoader"; import lottieLoader from "../../lib/rlottie/lottieLoader";
import rootScope from "../../lib/rootScope"; import rootScope from "../../lib/rootScope";
import animationIntersector from "../animationIntersector";
import LazyLoadQueue from "../lazyLoadQueue"; import LazyLoadQueue from "../lazyLoadQueue";
import wrapSticker from "./sticker"; import wrapSticker from "./sticker";
@ -62,6 +63,7 @@ export default async function wrapStickerSetThumb({set, lazyLoadQueue, container
return promise.then((blob) => { return promise.then((blob) => {
renderImageFromUrl(media, URL.createObjectURL(blob), () => { renderImageFromUrl(media, URL.createObjectURL(blob), () => {
container.append(media); container.append(media);
animationIntersector.addAnimation(media as HTMLVideoElement, group);
}); });
}); });
} }
@ -79,7 +81,9 @@ export default async function wrapStickerSetThumb({set, lazyLoadQueue, container
div: container, div: container,
group: group, group: group,
lazyLoadQueue, lazyLoadQueue,
managers managers,
width,
height
}); // kostil }); // kostil
} }
} }

3
src/helpers/dropdownHover.ts

@ -61,9 +61,8 @@ export default class DropdownHover extends EventListenerBase<{
} }
private onMouseOut = (e: MouseEvent) => { private onMouseOut = (e: MouseEvent) => {
if(KEEP_OPEN) return; if(KEEP_OPEN || !this.isActive()) return;
clearTimeout(this.displayTimeout); clearTimeout(this.displayTimeout);
if(!this.isActive()) return;
const toElement = (e as any).toElement as Element; const toElement = (e as any).toElement as Element;
if(toElement && findUpAsChild(toElement, this.element)) { if(toElement && findUpAsChild(toElement, this.element)) {

4
src/index.hbs

@ -158,9 +158,7 @@
</div> </div>
<div class="tabs-tab stickers-padding"> <div class="tabs-tab stickers-padding">
<div class="menu-wrapper"> <div class="menu-wrapper">
<nav class="menu-horizontal-div no-stripe justify-start"> <nav class="menu-horizontal-div no-stripe justify-start"></nav>
<button class="menu-horizontal-div-item btn-icon tgico-recent active"></button>
</nav>
</div> </div>
<div class="emoticons-content" id="content-stickers"></div> <div class="emoticons-content" id="content-stickers"></div>
</div> </div>

9
src/lib/appManagers/appStickersManager.ts

@ -103,7 +103,7 @@ export class AppStickersManager extends AppManager {
}); });
} }
public saveStickers(docs: Document[]) { private saveStickers(docs: Document[]) {
forEachReverse(docs, (doc, idx) => { forEachReverse(docs, (doc, idx) => {
doc = this.appDocsManager.saveDoc(doc); doc = this.appDocsManager.saveDoc(doc);
@ -295,7 +295,7 @@ export class AppStickersManager extends AppManager {
}); });
} }
public saveStickerSet(res: Omit<MessagesStickerSet.messagesStickerSet, '_'>, id: DocId) { private saveStickerSet(res: Omit<MessagesStickerSet.messagesStickerSet, '_'>, id: DocId) {
//console.log('stickers save set', res);w //console.log('stickers save set', res);w
const newSet: MessagesStickerSet = { const newSet: MessagesStickerSet = {
@ -401,6 +401,8 @@ export class AppStickersManager extends AppManager {
} }
public async toggleStickerSet(set: StickerSet.stickerSet) { public async toggleStickerSet(set: StickerSet.stickerSet) {
set = this.storage.getFromCache(set.id).set;
if(set.installed_date) { if(set.installed_date) {
const res = await this.apiManager.invokeApi('messages.uninstallStickerSet', { const res = await this.apiManager.invokeApi('messages.uninstallStickerSet', {
stickerset: this.getStickerSetInput(set) stickerset: this.getStickerSetInput(set)
@ -559,7 +561,8 @@ export class AppStickersManager extends AppManager {
}); });
} }
public pushRecentSticker(doc: MyDocument) { public pushRecentSticker(docId: DocId) {
const doc = this.appDocsManager.getDoc(docId);
const docEmoticon = fixEmoji(doc.stickerEmojiRaw); const docEmoticon = fixEmoji(doc.stickerEmojiRaw);
for(const emoticon in this.getStickersByEmoticonsPromises) { for(const emoticon in this.getStickersByEmoticonsPromises) {
const promise = this.getStickersByEmoticonsPromises[emoticon]; const promise = this.getStickersByEmoticonsPromises[emoticon];

195
src/scss/partials/_emojiDropdown.scss

@ -5,6 +5,7 @@
*/ */
.emoji-dropdown { .emoji-dropdown {
--menu-height: 3.0625rem;
display: flex/* !important */; display: flex/* !important */;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
@ -41,16 +42,16 @@
} }
} }
/* @include respond-to(handhelds) {
width: calc(100% + 1rem);
margin-left: -.5rem;
} */
.emoji-container { .emoji-container {
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
overflow: hidden; overflow: hidden;
height: 100%; height: 100%;
.menu-horizontal-div {
z-index: 4;
background-color: var(--surface-color);
}
} }
.emoji-tabs { .emoji-tabs {
@ -62,34 +63,32 @@
&-search { &-search {
position: absolute; position: absolute;
left: 0; left: 0;
margin-left: 4px !important; margin-left: .5rem !important;
} }
&-delete { &-delete {
position: absolute; position: absolute;
right: 0; right: 0;
margin-right: 4px !important; margin-right: .5rem !important;
}
} }
.tabs-container { .menu-horizontal-div-item {
/* width: 300%; */ margin: 0 1rem;
height: 100%; }
}
.category-title { .category-title {
//position: sticky; font-size: var(--font-size-14);
top: 0;
//font-size: .85rem;
font-size: 14px;
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);
line-height: var(--line-height-14);
color: var(--secondary-text-color); color: var(--secondary-text-color);
//background: linear-gradient(to bottom,#fff 0,rgba(255,255,255,.9) 60%,rgba(255,255,255,0) 100%); padding: .8125rem .875rem .6875rem;
z-index: 2;
//padding: .53333rem 6PX .66667rem;
padding: 12px 6px 6px 6px;
width: 100%; width: 100%;
} }
.tabs-container {
height: 100%;
.sticky_sentinel { .sticky_sentinel {
&--top { &--top {
top: 0; top: 0;
@ -110,172 +109,82 @@
position: relative; position: relative;
background-color: var(--surface-color); background-color: var(--surface-color);
} }
.scrollable {
padding: 0 10px;
} }
.emoji-padding {
.super-emojis {
padding: 0 .5rem;
} }
.emoji-padding.active {
@include respond-to(handhelds) { @include respond-to(handhelds) {
.menu-horizontal-div .menu-horizontal-div-item { .menu-horizontal-div-item {
flex: unset; flex: unset;
padding: 0; padding: 0;
} }
.category-items {
grid-template-columns: repeat(auto-fill, 40px);
> span {
width: 40px;
height: 40px;
justify-self: center;
}
}
.category-title {
padding: 12px 6px 6px 10px;
}
.scrollable {
padding: 0;
}
.emoji-category .category-items {
grid-column-gap: unset;
}
}
}
.emoji-padding,
.stickers-padding {
.menu-horizontal-div {
// padding: 2px;
z-index: 4;
background-color: var(--surface-color);
.menu-horizontal-div-item {
margin: 0;
}
} }
} }
.emoji-category { .emoji-category {
//padding-top: 1px;
position: relative;
margin: 0 -.125rem;
/* &:first-child {
//padding-top: 5px;
} */
/* &::after {
content: "";
flex: auto;
} */
}
.sticker-category {
position: relative; position: relative;
.category-title {
cursor: pointer;
}
&.stickers-recent {
.category-title {
pointer-events: none;
}
}
/* &::after {
content: "";
flex: auto;
} */
/* &.not-full::after {
content: "";
flex: auto;
} */
.category-items {
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fill, var(--esg-sticker-size)); // 64px
grid-column-gap: 1px;
justify-content: space-between;
}
}
#content-stickers {
.scrollable {
padding: 0px 5px 0;
}
} }
.menu-horizontal-div { .menu-horizontal-div {
width: 100%; width: 100%;
height: 48px; height: var(--menu-height);
min-height: var(--menu-height);
align-items: center;
.menu-horizontal-div-item { &-item {
font-size: 1.5rem; font-size: 1.5rem;
margin: 0 12px; width: 2.5rem;
width: 48px; height: 2.5rem;
height: 48px; line-height: 2.5rem;
line-height: 48px;
display: flex; display: flex;
align-items: center; align-items: center;
flex: 0 0 auto; flex: 0 0 auto;
padding: 0;
} }
} }
.stickers-padding { .stickers-padding {
&.active { .category-title {
.scrollable { cursor: pointer;
padding: 0;
//box-shadow: 0px 1px 5px -1px rgba(0, 0, 0, .21);
} }
.menu-horizontal-div { .category-items {
.menu-horizontal-div-item { width: 100%;
height: 48px; display: grid;
width: 48px; grid-template-columns: repeat(auto-fill, var(--esg-sticker-size)); // 64px
padding: 0; justify-content: space-between;
margin-right: 1px; padding: 0 .3125rem;
margin-left: 1px;
}
}
} }
.menu-wrapper { .menu-wrapper {
padding: 0; padding: 0;
height: 48px; height: var(--menu-height);
max-width: 100%; max-width: 100%;
position: relative; position: relative;
border-bottom: 1px solid var(--border-color);
background-color: var(--surface-color);
} }
.menu-horizontal-div-item { .menu-horizontal-div {
/* width: calc(100% / 7); */ &-item {
flex: 0 0 auto; flex: 0 0 auto;
padding: .25rem;
margin: 0 .3125rem;
&-padding {
width: 100%;
height: 100%;
position: relative;
}
&.active { &.active {
&:not(.tgico-recent) { &:not(.tgico-recent) {
background-color: var(--light-secondary-text-color); background-color: var(--light-secondary-text-color);
} }
} }
> canvas, > img {
//padding: .75rem;
padding: 8px;
max-width: 100%;
max-height: 100%;
}
> canvas {
width: 100%;
height: 100%;
} }
} }
} }

4
src/scss/style.scss

@ -968,13 +968,13 @@ img.emoji {
width: 100%; width: 100%;
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, var(--esg-sticker-size)); // 64px grid-template-columns: repeat(auto-fill, var(--esg-sticker-size)); // 64px
grid-column-gap: 1px; // grid-column-gap: 1px;
justify-content: space-between; justify-content: space-between;
} }
.super-sticker { .super-sticker {
@include hover-background-effect() { @include hover-background-effect() {
border-radius: 10px; border-radius: $border-radius-medium;
} }
/* &:nth-child(5n+5) { /* &:nth-child(5n+5) {

Loading…
Cancel
Save