Browse Source

Stickers helper

Hashable invokeApi
master
Eduard Kuzmenko 4 years ago
parent
commit
9b811795f5
  1. 113
      src/components/chat/input.ts
  2. 206
      src/components/emoticonsDropdown/tabs/stickers.ts
  3. 36
      src/components/wrappers.ts
  4. 117
      src/lib/appManagers/appStickersManager.ts
  5. 41
      src/lib/mtproto/mtprotoworker.ts
  6. 44
      src/scss/partials/_chat.scss
  7. 30
      src/scss/partials/_emojiDropdown.scss
  8. 1
      src/scss/partials/_rightSidebar.scss
  9. 32
      src/scss/style.scss

113
src/components/chat/input.ts

@ -1,8 +1,8 @@
import Recorder from '../../../public/recorder.min'; import Recorder from '../../../public/recorder.min';
import { isTouchSupported } from "../../helpers/touchSupport"; import { isTouchSupported } from "../../helpers/touchSupport";
import appChatsManager from '../../lib/appManagers/appChatsManager'; import appChatsManager from '../../lib/appManagers/appChatsManager';
import appDocsManager from "../../lib/appManagers/appDocsManager"; import appDocsManager, { MyDocument } from "../../lib/appManagers/appDocsManager";
import appImManager from "../../lib/appManagers/appImManager"; import appImManager, { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager";
import appMessagesManager from "../../lib/appManagers/appMessagesManager"; import appMessagesManager from "../../lib/appManagers/appMessagesManager";
import appPeersManager from '../../lib/appManagers/appPeersManager'; import appPeersManager from '../../lib/appManagers/appPeersManager';
import appWebPagesManager from "../../lib/appManagers/appWebPagesManager"; import appWebPagesManager from "../../lib/appManagers/appWebPagesManager";
@ -11,9 +11,9 @@ import apiManager from "../../lib/mtproto/mtprotoworker";
import opusDecodeController from "../../lib/opusDecodeController"; import opusDecodeController from "../../lib/opusDecodeController";
import { RichTextProcessor } from "../../lib/richtextprocessor"; import { RichTextProcessor } from "../../lib/richtextprocessor";
import rootScope from '../../lib/rootScope'; import rootScope from '../../lib/rootScope';
import { blurActiveElement, cancelEvent, CLICK_EVENT_NAME, findUpClassName, getRichValue, getSelectedNodes, isInputEmpty, isSelectionSingle, markdownTags, MarkdownType, placeCaretAtEnd, serializeNodes } from "../../helpers/dom"; import { blurActiveElement, cancelEvent, CLICK_EVENT_NAME, findUpClassName, getRichValue, getSelectedNodes, isInputEmpty, markdownTags, MarkdownType, placeCaretAtEnd, serializeNodes } from "../../helpers/dom";
import ButtonMenu, { ButtonMenuItemOptions } from '../buttonMenu'; import ButtonMenu, { ButtonMenuItemOptions } from '../buttonMenu';
import emoticonsDropdown from "../emoticonsDropdown"; import emoticonsDropdown, { EmoticonsDropdown } from "../emoticonsDropdown";
import PopupCreatePoll from "../popupCreatePoll"; import PopupCreatePoll from "../popupCreatePoll";
import PopupForward from '../popupForward'; import PopupForward from '../popupForward';
import PopupNewMedia from '../popupNewMedia'; import PopupNewMedia from '../popupNewMedia';
@ -24,12 +24,97 @@ import { wrapReply } from "../wrappers";
import InputField from '../inputField'; import InputField from '../inputField';
import { MessageEntity } from '../../layer'; import { MessageEntity } from '../../layer';
import ButtonIcon from '../buttonIcon'; import ButtonIcon from '../buttonIcon';
import appStickersManager from '../../lib/appManagers/appStickersManager';
import SetTransition from '../singleTransition';
import { SuperStickerRenderer } from '../emoticonsDropdown/tabs/stickers';
import LazyLoadQueue from '../lazyLoadQueue';
const RECORD_MIN_TIME = 500; const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.'; const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
type ChatInputHelperType = 'edit' | 'webpage' | 'forward' | 'reply'; type ChatInputHelperType = 'edit' | 'webpage' | 'forward' | 'reply';
export class StickersHelper {
private container: HTMLElement;
private stickersContainer: HTMLElement;
private scrollable: Scrollable;
private superStickerRenderer: SuperStickerRenderer;
private lazyLoadQueue: LazyLoadQueue;
private lastEmoticon = '';
constructor(private appendTo: HTMLElement) {
}
public checkEmoticon(emoticon: string) {
if(this.lastEmoticon == emoticon) return;
if(this.lastEmoticon && !emoticon) {
if(this.container) {
SetTransition(this.container, 'is-visible', false, 200/* , () => {
this.stickersContainer.innerHTML = '';
} */);
}
}
this.lastEmoticon = emoticon;
if(this.lazyLoadQueue) {
this.lazyLoadQueue.clear();
}
if(!emoticon) {
return;
}
appStickersManager.getStickersByEmoticon(emoticon)
.then(stickers => {
if(this.lastEmoticon != emoticon) {
return;
}
if(this.init) {
this.init();
this.init = null;
}
this.stickersContainer.innerHTML = '';
this.lazyLoadQueue.clear();
if(stickers.length) {
stickers.forEach(sticker => {
this.stickersContainer.append(this.superStickerRenderer.renderSticker(sticker as MyDocument));
});
}
SetTransition(this.container, 'is-visible', true, 200);
this.scrollable.scrollTop = 0;
});
}
private init() {
this.container = document.createElement('div');
this.container.classList.add('stickers-helper', 'z-depth-1');
this.stickersContainer = document.createElement('div');
this.stickersContainer.classList.add('stickers-helper-stickers', 'super-stickers');
this.stickersContainer.addEventListener('click', (e) => {
if(!findUpClassName(e.target, 'super-sticker')) {
return;
}
appImManager.chatInputC.clearInput();
EmoticonsDropdown.onMediaClick(e);
});
this.container.append(this.stickersContainer);
this.scrollable = new Scrollable(this.container);
this.lazyLoadQueue = new LazyLoadQueue();
this.superStickerRenderer = new SuperStickerRenderer(this.lazyLoadQueue, CHAT_ANIMATION_GROUP);
this.appendTo.append(this.container);
}
}
export class MarkupTooltip { export class MarkupTooltip {
public container: HTMLElement; public container: HTMLElement;
private wrapper: HTMLElement; private wrapper: HTMLElement;
@ -323,6 +408,7 @@ export class ChatInput {
private canUndoFromHTML = ''; private canUndoFromHTML = '';
public markupTooltip: MarkupTooltip; public markupTooltip: MarkupTooltip;
public stickersHelper: StickersHelper;
constructor() { constructor() {
if(!isTouchSupported) { if(!isTouchSupported) {
@ -382,6 +468,8 @@ export class ChatInput {
this.replyElements.titleEl = this.replyElements.container.querySelector('.reply-title') as HTMLDivElement; this.replyElements.titleEl = this.replyElements.container.querySelector('.reply-title') as HTMLDivElement;
this.replyElements.subtitleEl = this.replyElements.container.querySelector('.reply-subtitle') as HTMLDivElement; this.replyElements.subtitleEl = this.replyElements.container.querySelector('.reply-subtitle') as HTMLDivElement;
this.stickersHelper = new StickersHelper(this.replyElements.container.parentElement);
try { try {
this.recorder = new Recorder({ this.recorder = new Recorder({
//encoderBitRate: 32, //encoderBitRate: 32,
@ -789,11 +877,24 @@ export class ChatInput {
} */ } */
//console.log('messageInput input', this.messageInput.innerText, this.serializeNodes(Array.from(this.messageInput.childNodes))); //console.log('messageInput input', this.messageInput.innerText, this.serializeNodes(Array.from(this.messageInput.childNodes)));
const value = this.messageInput.innerText; //const value = this.messageInput.innerText;
const value = getRichValue(this.messageInput);
const entities = RichTextProcessor.parseEntities(value); const entities = RichTextProcessor.parseEntities(value);
//console.log('messageInput entities', entities); //console.log('messageInput entities', entities);
if(this.stickersHelper) {
let emoticon = '';
if(entities.length && entities[0]._ == 'messageEntityEmoji') {
const entity = entities[0];
if(entity.length == value.length && !entity.offset) {
emoticon = value;
}
}
this.stickersHelper.checkEmoticon(emoticon);
}
const html = this.messageInput.innerHTML; const html = this.messageInput.innerHTML;
if(this.canRedoFromHTML && html != this.canRedoFromHTML && !this.lockRedo) { if(this.canRedoFromHTML && html != this.canRedoFromHTML && !this.lockRedo) {
this.canRedoFromHTML = ''; this.canRedoFromHTML = '';
@ -1014,6 +1115,8 @@ export class ChatInput {
this.executedHistory.length = 0; this.executedHistory.length = 0;
this.canUndoFromHTML = ''; this.canUndoFromHTML = '';
} }
this.onMessageInput();
} }
public isInputEmpty() { public isInputEmpty() {

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

@ -1,7 +1,7 @@
import emoticonsDropdown, { EmoticonsDropdown, EMOTICONSSTICKERGROUP, EmoticonsTab } from ".."; import emoticonsDropdown, { EmoticonsDropdown, EMOTICONSSTICKERGROUP, EmoticonsTab } from "..";
import { readBlobAsText } from "../../../helpers/blob"; import { readBlobAsText } from "../../../helpers/blob";
import mediaSizes from "../../../helpers/mediaSizes"; import mediaSizes from "../../../helpers/mediaSizes";
import { StickerSet } from "../../../layer"; import { MessagesAllStickers, StickerSet } from "../../../layer";
import appDocsManager, { MyDocument } from "../../../lib/appManagers/appDocsManager"; import appDocsManager, { MyDocument } from "../../../lib/appManagers/appDocsManager";
import appDownloadManager from "../../../lib/appManagers/appDownloadManager"; import appDownloadManager from "../../../lib/appManagers/appDownloadManager";
import appStickersManager from "../../../lib/appManagers/appStickersManager"; import appStickersManager from "../../../lib/appManagers/appStickersManager";
@ -10,12 +10,106 @@ import apiManager from "../../../lib/mtproto/mtprotoworker";
import { RichTextProcessor } from "../../../lib/richtextprocessor"; import { RichTextProcessor } from "../../../lib/richtextprocessor";
import rootScope from "../../../lib/rootScope"; import rootScope from "../../../lib/rootScope";
import animationIntersector from "../../animationIntersector"; import animationIntersector from "../../animationIntersector";
import { LazyLoadQueueRepeat } from "../../lazyLoadQueue"; import LazyLoadQueue, { LazyLoadQueueRepeat } from "../../lazyLoadQueue";
import { putPreloader, renderImageFromUrl } from "../../misc"; import { putPreloader, renderImageFromUrl } from "../../misc";
import Scrollable, { ScrollableX } from "../../scrollable"; import Scrollable, { ScrollableX } from "../../scrollable";
import StickyIntersector from "../../stickyIntersector"; import StickyIntersector from "../../stickyIntersector";
import { wrapSticker } from "../../wrappers"; import { wrapSticker } from "../../wrappers";
export class SuperStickerRenderer {
lazyLoadQueue: LazyLoadQueueRepeat;
animatedDivs: Set<HTMLDivElement> = new Set();
constructor(private regularLazyLoadQueue: LazyLoadQueue, private group: string) {
this.lazyLoadQueue = new LazyLoadQueueRepeat(undefined, (target, visible) => {
if(!visible) {
this.processInvisibleDiv(target as HTMLDivElement);
}
});
}
renderSticker(doc: MyDocument, div?: HTMLDivElement) {
if(!div) {
div = document.createElement('div');
div.classList.add('grid-item', 'super-sticker');
if(doc.sticker == 2) {
this.animatedDivs.add(div);
this.lazyLoadQueue.observe({
div,
load: this.processVisibleDiv
});
}
}
// * This will wrap only a thumb
wrapSticker({
doc,
div,
lazyLoadQueue: this.regularLazyLoadQueue,
group: this.group,
onlyThumb: doc.sticker == 2
});
return div;
}
checkAnimationContainer = (div: HTMLElement, visible: boolean) => {
//console.error('checkAnimationContainer', div, visible);
const players = animationIntersector.getAnimations(div);
players.forEach(player => {
if(!visible) {
animationIntersector.checkAnimation(player, true, true);
} else {
animationIntersector.checkAnimation(player, false);
}
});
};
processVisibleDiv = (div: HTMLElement) => {
const docID = div.dataset.docID;
const doc = appDocsManager.getDoc(docID);
const size = mediaSizes.active.esgSticker.width;
const promise = wrapSticker({
doc,
div: div as HTMLDivElement,
width: size,
height: size,
lazyLoadQueue: null,
group: this.group,
onlyThumb: false,
play: true,
loop: true
});
promise.then(() => {
//clearTimeout(timeout);
this.checkAnimationContainer(div, this.lazyLoadQueue.intersector.isVisible(div));
});
/* let timeout = window.setTimeout(() => {
console.error('processVisibleDiv timeout', div, doc);
}, 1e3); */
return promise;
};
processInvisibleDiv = (div: HTMLElement) => {
const docID = div.dataset.docID;
const doc = appDocsManager.getDoc(docID);
//console.log('STICKER INvisible:', /* div, */docID);
this.checkAnimationContainer(div, false);
div.innerHTML = '';
this.renderSticker(doc, div as HTMLDivElement);
};
}
export default class StickersTab implements EmoticonsTab { export default class StickersTab implements EmoticonsTab {
public content: HTMLElement; public content: HTMLElement;
private stickersDiv: HTMLElement; private stickersDiv: HTMLElement;
@ -38,14 +132,13 @@ export default class StickersTab implements EmoticonsTab {
private stickyIntersector: StickyIntersector; private stickyIntersector: StickyIntersector;
private animatedDivs: Set<HTMLDivElement> = new Set(); private superStickerRenderer: SuperStickerRenderer;
private lazyLoadQueue: LazyLoadQueueRepeat;
categoryPush(categoryDiv: HTMLElement, categoryTitle: string, promise: Promise<MyDocument[]>, prepend?: boolean) { categoryPush(categoryDiv: HTMLElement, categoryTitle: string, promise: Promise<MyDocument[]>, prepend?: boolean) {
//if((docs.length % 5) != 0) categoryDiv.classList.add('not-full'); //if((docs.length % 5) != 0) categoryDiv.classList.add('not-full');
const itemsDiv = document.createElement('div'); const itemsDiv = document.createElement('div');
itemsDiv.classList.add('category-items'); itemsDiv.classList.add('category-items', 'super-stickers');
const titleDiv = document.createElement('div'); const titleDiv = document.createElement('div');
titleDiv.classList.add('category-title'); titleDiv.classList.add('category-title');
@ -60,7 +153,7 @@ export default class StickersTab implements EmoticonsTab {
promise.then(documents => { promise.then(documents => {
documents.forEach(doc => { documents.forEach(doc => {
//if(doc._ == 'documentEmpty') return; //if(doc._ == 'documentEmpty') return;
itemsDiv.append(this.renderSticker(doc)); itemsDiv.append(this.superStickerRenderer.renderSticker(doc));
}); });
if(this.queueCategoryPush.length) { if(this.queueCategoryPush.length) {
@ -80,33 +173,6 @@ export default class StickersTab implements EmoticonsTab {
}); });
} }
renderSticker(doc: MyDocument, div?: HTMLDivElement) {
if(!div) {
div = document.createElement('div');
div.classList.add('grid-item');
if(doc.sticker == 2) {
this.animatedDivs.add(div);
this.lazyLoadQueue.observe({
div,
load: this.processVisibleDiv
});
}
}
// * This will wrap only a thumb
wrapSticker({
doc,
div,
lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue,
group: EMOTICONSSTICKERGROUP,
onlyThumb: doc.sticker == 2
});
return div;
}
async renderStickerSet(set: StickerSet.stickerSet, prepend = false) { async renderStickerSet(set: StickerSet.stickerSet, prepend = false) {
const categoryDiv = document.createElement('div'); const categoryDiv = document.createElement('div');
categoryDiv.classList.add('sticker-category'); categoryDiv.classList.add('sticker-category');
@ -169,60 +235,6 @@ export default class StickersTab implements EmoticonsTab {
} }
} }
checkAnimationContainer = (div: HTMLElement, visible: boolean) => {
//console.error('checkAnimationContainer', div, visible);
const players = animationIntersector.getAnimations(div);
players.forEach(player => {
if(!visible) {
animationIntersector.checkAnimation(player, true, true);
} else {
animationIntersector.checkAnimation(player, false);
}
});
};
processVisibleDiv = (div: HTMLElement) => {
const docID = div.dataset.docID;
const doc = appDocsManager.getDoc(docID);
const size = mediaSizes.active.esgSticker.width;
const promise = wrapSticker({
doc,
div: div as HTMLDivElement,
width: size,
height: size,
lazyLoadQueue: null,
group: EMOTICONSSTICKERGROUP,
onlyThumb: false,
play: true,
loop: true
});
promise.then(() => {
//clearTimeout(timeout);
this.checkAnimationContainer(div, this.lazyLoadQueue.intersector.isVisible(div));
});
/* let timeout = window.setTimeout(() => {
console.error('processVisibleDiv timeout', div, doc);
}, 1e3); */
return promise;
};
processInvisibleDiv = (div: HTMLElement) => {
const docID = div.dataset.docID;
const doc = appDocsManager.getDoc(docID);
//console.log('STICKER INvisible:', /* div, */docID);
this.checkAnimationContainer(div, false);
div.innerHTML = '';
this.renderSticker(doc, div as HTMLDivElement);
};
init() { init() {
this.content = document.getElementById('content-stickers'); this.content = document.getElementById('content-stickers');
//let stickersDiv = contentStickersDiv.querySelector('.os-content') as HTMLDivElement; //let stickersDiv = contentStickersDiv.querySelector('.os-content') as HTMLDivElement;
@ -299,16 +311,10 @@ export default class StickersTab implements EmoticonsTab {
this.categoryPush(this.recentDiv, 'Recent', Promise.resolve(this.recentStickers), true); this.categoryPush(this.recentDiv, 'Recent', Promise.resolve(this.recentStickers), true);
}), }),
apiManager.invokeApi('messages.getAllStickers', {hash: 0}).then(async(res) => { appStickersManager.getAllStickers().then((res) => {
let stickers: {
_: 'messages.allStickers',
hash: number,
sets: Array<StickerSet.stickerSet>
} = res as any;
preloader.remove(); preloader.remove();
for(let set of stickers.sets) { for(let set of (res as MessagesAllStickers.messagesAllStickers).sets) {
this.renderStickerSet(set); this.renderStickerSet(set);
} }
}) })
@ -316,13 +322,9 @@ export default class StickersTab implements EmoticonsTab {
this.mounted = true; this.mounted = true;
}); });
this.lazyLoadQueue = new LazyLoadQueueRepeat(undefined, (target, visible) => { this.superStickerRenderer = new SuperStickerRenderer(EmoticonsDropdown.lazyLoadQueue, EMOTICONSSTICKERGROUP);
if(!visible) {
this.processInvisibleDiv(target as HTMLDivElement);
}
});
emoticonsDropdown.addLazyLoadQueueRepeat(this.lazyLoadQueue, this.processInvisibleDiv); emoticonsDropdown.addLazyLoadQueueRepeat(this.superStickerRenderer.lazyLoadQueue, this.superStickerRenderer.processInvisibleDiv);
/* setInterval(() => { /* setInterval(() => {
// @ts-ignore // @ts-ignore
@ -342,7 +344,7 @@ export default class StickersTab implements EmoticonsTab {
let div = this.recentDiv.querySelector(`[data-doc-i-d="${doc.id}"]`); let div = this.recentDiv.querySelector(`[data-doc-i-d="${doc.id}"]`);
if(!div) { if(!div) {
div = this.renderSticker(doc); div = this.superStickerRenderer.renderSticker(doc);
} }
const items = this.recentDiv.querySelector('.category-items'); const items = this.recentDiv.querySelector('.category-items');

36
src/components/wrappers.ts

@ -565,7 +565,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
const toneIndex = emoji ? getEmojiToneIndex(emoji) : -1; const toneIndex = emoji ? getEmojiToneIndex(emoji) : -1;
if((doc.thumbs?.length || doc.stickerCachedThumbs) && !div.firstElementChild && (!doc.downloaded || stickerType == 2 || onlyThumb)/* && doc.thumbs[0]._ != 'photoSizeEmpty' */) { if((doc.thumbs?.length || doc.stickerCachedThumbs) && !div.firstElementChild && (!doc.downloaded || stickerType == 2 || onlyThumb)/* && doc.thumbs[0]._ != 'photoSizeEmpty' */) {
const thumb = doc.stickerCachedThumbs && doc.stickerCachedThumbs[toneIndex] || doc.thumbs[0]; let thumb = doc.stickerCachedThumbs && doc.stickerCachedThumbs[toneIndex] || doc.thumbs[0];
//console.log('wrap sticker', thumb, div); //console.log('wrap sticker', thumb, div);
@ -581,23 +581,33 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
renderImageFromUrl(img, thumb.url, afterRender); renderImageFromUrl(img, thumb.url, afterRender);
} else if('bytes' in thumb) { } else if('bytes' in thumb) {
if(thumb._ == 'photoPathSize') { if(thumb._ == 'photoPathSize') {
//if(!doc.w) console.error('no w', doc); if(thumb.bytes.length) {
div.innerHTML = `<svg class="rlottie-vector" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 ${doc.w || 512} ${doc.h || 512}" xml:space="preserve"> //if(!doc.w) console.error('no w', doc);
<path d="${appPhotosManager.getPathFromPhotoPathSize(thumb)}"/> const d = appPhotosManager.getPathFromPhotoPathSize(thumb);
</svg>`; /* if(d == 'Mz' || d.includes('151,48,349,33z')) {
} else if(toneIndex <= 0) { console.error('no path', doc);
} */
div.innerHTML = `<svg class="rlottie-vector" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 ${doc.w || 512} ${doc.h || 512}" xml:space="preserve">
<path d="${d}"/>
</svg>`;
} else {
thumb = doc.thumbs.find(t => (t as PhotoSize.photoStrippedSize).bytes?.length) || thumb;
}
}
if(thumb && thumb._ != 'photoPathSize' && toneIndex <= 0) {
img = new Image(); img = new Image();
if((webpWorkerController.isWebpSupported() || doc.pFlags.stickerThumbConverted || thumb.url)/* && false */) { if((webpWorkerController.isWebpSupported() || doc.pFlags.stickerThumbConverted || thumb.url)/* && false */) {
renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true), afterRender); renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb as PhotoSize.photoStrippedSize, true), afterRender);
} else { } else {
webpWorkerController.convert(doc.id, thumb.bytes as Uint8Array).then(bytes => { webpWorkerController.convert(doc.id, (thumb as PhotoSize.photoStrippedSize).bytes as Uint8Array).then(bytes => {
thumb.bytes = bytes; (thumb as PhotoSize.photoStrippedSize).bytes = bytes;
doc.pFlags.stickerThumbConverted = true; doc.pFlags.stickerThumbConverted = true;
if(middleware && !middleware()) return; if(middleware && !middleware()) return;
if(!div.childElementCount) { if(!div.childElementCount) {
renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true), afterRender); renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb as PhotoSize.photoStrippedSize, true), afterRender);
} }
}).catch(() => {}); }).catch(() => {});
} }
@ -610,14 +620,14 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
const r = () => { const r = () => {
if(div.childElementCount || (middleware && !middleware())) return; if(div.childElementCount || (middleware && !middleware())) return;
renderImageFromUrl(img, thumb.url, afterRender); renderImageFromUrl(img, (thumb as PhotoSize.photoStrippedSize).url, afterRender);
}; };
if(thumb.url) { if((thumb as PhotoSize.photoStrippedSize).url) {
r(); r();
return Promise.resolve(); return Promise.resolve();
} else { } else {
return appDocsManager.getThumbURL(doc, thumb).promise.then(r); return appDocsManager.getThumbURL(doc, thumb as PhotoSize.photoStrippedSize).promise.then(r);
} }
}; };

117
src/lib/appManagers/appStickersManager.ts

@ -1,4 +1,4 @@
import { Document, InputFileLocation, InputStickerSet, MessagesRecentStickers, MessagesStickerSet, PhotoSize, StickerSet, StickerSetCovered } from '../../layer'; import { Document, InputFileLocation, InputStickerSet, MessagesAllStickers, MessagesFeaturedStickers, MessagesFoundStickerSets, MessagesRecentStickers, MessagesStickers, MessagesStickerSet, PhotoSize, StickerPack, StickerSet, StickerSetCovered } from '../../layer';
import { Modify } from '../../types'; import { Modify } from '../../types';
import apiManager from '../mtproto/mtprotoworker'; import apiManager from '../mtproto/mtprotoworker';
import { MOUNT_CLASS_TO } from '../mtproto/mtproto_config'; import { MOUNT_CLASS_TO } from '../mtproto/mtproto_config';
@ -15,18 +15,8 @@ export class AppStickersManager {
private saveSetsTimeout: number; private saveSetsTimeout: number;
private hashes: Partial<{ private getStickerSetPromises: {[setID: string]: Promise<MessagesStickerSet>} = {};
featured: Partial<{hash: number, result: StickerSetCovered[]}>, private getStickersByEmoticonsPromises: {[emoticon: string]: Promise<Document[]>} = {};
search: {
[query: string]: Partial<{
hash: number,
result: StickerSetCovered[]
}>
}
}> = {
featured: {},
search: {}
};
constructor() { constructor() {
appStateManager.getState().then(({stickerSets}) => { appStateManager.getState().then(({stickerSets}) => {
@ -74,25 +64,29 @@ export class AppStickersManager {
}> = {}): Promise<MessagesStickerSet> { }> = {}): Promise<MessagesStickerSet> {
if(this.stickerSets[set.id] && !params.overwrite && this.stickerSets[set.id].documents?.length) return this.stickerSets[set.id]; if(this.stickerSets[set.id] && !params.overwrite && this.stickerSets[set.id].documents?.length) return this.stickerSets[set.id];
const stickerSet = await apiManager.invokeApi('messages.getStickerSet', { if(this.getStickerSetPromises[set.id]) {
return this.getStickerSetPromises[set.id];
}
const promise = this.getStickerSetPromises[set.id] = apiManager.invokeApi('messages.getStickerSet', {
stickerset: this.getStickerSetInput(set) stickerset: this.getStickerSetInput(set)
}); });
const stickerSet = await promise;
delete this.getStickerSetPromises[set.id];
this.saveStickerSet(stickerSet, set.id); this.saveStickerSet(stickerSet, set.id);
return stickerSet as any; return stickerSet;
} }
public async getRecentStickers(): Promise<Modify<MessagesRecentStickers.messagesRecentStickers, { public async getRecentStickers(): Promise<Modify<MessagesRecentStickers.messagesRecentStickers, {
stickers: Document[] stickers: Document[]
}>> { }>> {
const res = await apiManager.invokeApi('messages.getRecentStickers') as MessagesRecentStickers.messagesRecentStickers; const res = await apiManager.invokeApiHashable('messages.getRecentStickers') as MessagesRecentStickers.messagesRecentStickers;
if(res._ == 'messages.recentStickers') { this.saveStickers(res.stickers);
this.saveStickers(res.stickers);
}
return res as any; return res;
} }
public getAnimatedEmojiSticker(emoji: string) { public getAnimatedEmojiSticker(emoji: string) {
@ -185,21 +179,13 @@ export class AppStickersManager {
} }
public async getFeaturedStickers() { public async getFeaturedStickers() {
const res = await apiManager.invokeApi('messages.getFeaturedStickers', { const res = await apiManager.invokeApiHashable('messages.getFeaturedStickers') as MessagesFeaturedStickers.messagesFeaturedStickers;
hash: this.hashes.featured?.hash || 0
});
const hashed = this.hashes.featured ?? (this.hashes.featured = {}); res.sets.forEach(covered => {
if(res._ != 'messages.featuredStickersNotModified') {
hashed.hash = res.hash;
hashed.result = res.sets;
}
hashed.result.forEach(covered => {
this.saveStickerSet({set: covered.set, documents: [], packs: []}, covered.set.id); this.saveStickerSet({set: covered.set, documents: [], packs: []}, covered.set.id);
}); });
return hashed.result; return res.sets;
} }
public async toggleStickerSet(set: StickerSet.stickerSet) { public async toggleStickerSet(set: StickerSet.stickerSet) {
@ -231,20 +217,13 @@ export class AppStickersManager {
public async searchStickerSets(query: string, excludeFeatured = true) { public async searchStickerSets(query: string, excludeFeatured = true) {
const flags = excludeFeatured ? 1 : 0; const flags = excludeFeatured ? 1 : 0;
const res = await apiManager.invokeApi('messages.searchStickerSets', { const res = await apiManager.invokeApiHashable('messages.searchStickerSets', {
flags, flags,
exclude_featured: excludeFeatured || undefined, exclude_featured: excludeFeatured || undefined,
q: query, q: query
hash: this.hashes.search[query]?.hash || 0 }) as MessagesFoundStickerSets.messagesFoundStickerSets;
});
const hashed = this.hashes.search[query] ?? (this.hashes.search[query] = {}); res.sets.forEach(covered => {
if(res._ != 'messages.foundStickerSetsNotModified') {
hashed.hash = res.hash;
hashed.result = res.sets;
}
hashed.result.forEach(covered => {
this.saveStickerSet({set: covered.set, documents: [], packs: []}, covered.set.id); this.saveStickerSet({set: covered.set, documents: [], packs: []}, covered.set.id);
}); });
@ -252,12 +231,60 @@ export class AppStickersManager {
for(let id in this.stickerSets) { for(let id in this.stickerSets) {
const {set} = this.stickerSets[id]; const {set} = this.stickerSets[id];
if(set.title.toLowerCase().includes(query.toLowerCase()) && !hashed.result.find(c => c.set.id == set.id)) { if(set.title.toLowerCase().includes(query.toLowerCase()) && !res.sets.find(c => c.set.id == set.id)) {
foundSaved.push({_: 'stickerSetCovered', set, cover: null}); foundSaved.push({_: 'stickerSetCovered', set, cover: null});
} }
} }
return hashed.result.concat(foundSaved); return res.sets.concat(foundSaved);
}
public getAllStickers() {
return apiManager.invokeApiHashable('messages.getAllStickers');
}
public preloadStickerSets() {
return this.getAllStickers().then(allStickers => {
return Promise.all((allStickers as MessagesAllStickers.messagesAllStickers).sets.map(set => this.getStickerSet(set)));
});
}
public getStickersByEmoticon(emoticon: string) {
if(this.getStickersByEmoticonsPromises[emoticon]) return this.getStickersByEmoticonsPromises[emoticon];
return this.getStickersByEmoticonsPromises[emoticon] = Promise.all([
apiManager.invokeApiHashable('messages.getStickers', {
emoticon
}),
this.preloadStickerSets(),
this.getRecentStickers()
]).then(([messagesStickers, installedSets, recentStickers]) => {
const foundStickers = (messagesStickers as MessagesStickers.messagesStickers).stickers.map(sticker => appDocsManager.saveDoc(sticker));
const cachedStickersAnimated: Document.document[] = [], cachedStickersStatic: Document.document[] = [];
//console.log('getStickersByEmoticon', messagesStickers, installedSets, recentStickers);
const iteratePacks = (packs: StickerPack.stickerPack[]) => {
for(const pack of packs) {
if(pack.emoticon.includes(emoticon)) {
for(const docID of pack.documents) {
const doc = appDocsManager.getDoc(docID);
(doc.animated ? cachedStickersAnimated : cachedStickersStatic).push(doc);
}
}
}
};
iteratePacks(recentStickers.packs);
for(const set of installedSets) {
iteratePacks(set.packs);
}
const stickers = [...new Set(cachedStickersAnimated.concat(cachedStickersStatic, foundStickers))];
return stickers;
});
} }
} }

41
src/lib/mtproto/mtprotoworker.ts

@ -22,6 +22,15 @@ type Task = {
const USEWORKERASWORKER = true; const USEWORKERASWORKER = true;
type HashResult = {
hash: number,
result: any
};
type HashOptions = {
[queryJSON: string]: HashResult
};
export class ApiManagerProxy extends CryptoWorkerMethods { export class ApiManagerProxy extends CryptoWorkerMethods {
public worker: Worker; public worker: Worker;
public postMessage: (...args: any[]) => void; public postMessage: (...args: any[]) => void;
@ -41,6 +50,8 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
private log = logger('API-PROXY'); private log = logger('API-PROXY');
private hashes: {[method: string]: HashOptions} = {};
constructor() { constructor() {
super(); super();
this.log('constructor'); this.log('constructor');
@ -230,6 +241,36 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
return this.performTaskWorker('invokeApi', method, params, o); return this.performTaskWorker('invokeApi', method, params, o);
} }
public invokeApiHashable<T extends keyof MethodDeclMap>(method: T, params: Omit<MethodDeclMap[T]['req'], 'hash'> = {} as any, options: InvokeApiOptions = {}): Promise<MethodDeclMap[T]['res']> {
//console.log('will invokeApi:', method, params, options);
const queryJSON = JSON.stringify(params);
let cached: HashResult;
if(this.hashes[method]) {
cached = this.hashes[method][queryJSON];
if(cached) {
(params as any).hash = cached.hash;
}
}
return this.performTaskWorker('invokeApi', method, params, options).then((result: any) => {
if(result._.includes('NotModified')) {
//this.log.warn('NotModified saved!', method, queryJSON);
return cached.result;
}
if(result.hash) {
if(!this.hashes[method]) this.hashes[method] = {};
this.hashes[method][queryJSON] = {
hash: result.hash,
result: result
};
}
return result;
});
}
public setBaseDcID(dcID: number) { public setBaseDcID(dcID: number) {
return this.performTaskWorker('setBaseDcID', dcID); return this.performTaskWorker('setBaseDcID', dcID);
} }

44
src/scss/partials/_chat.scss

@ -768,7 +768,7 @@ $chat-helper-size: 39px;
.btn-menu { .btn-menu {
padding: 8px 0; padding: 8px 0;
right: -8px; right: -11px;
bottom: calc(100% + 16px); bottom: calc(100% + 16px);
> div { > div {
@ -1420,3 +1420,45 @@ $chat-helper-size: 39px;
transition: none; transition: none;
} }
} }
.stickers-helper {
position: absolute !important;
bottom: calc(100% + 10px);
opacity: 0;
transition: opacity .2s ease-in-out;
overflow: hidden;
padding: 0 !important;
> .scrollable {
position: relative;
max-height: 220px;
min-height: var(--esg-sticker-size);
}
&-stickers {
display: flex;
flex-wrap: wrap;
}
&-sticker {
position: relative;
width: var(--esg-sticker-size);
height: var(--esg-sticker-size);
margin: 5px;
img {
width: 100%;
height: 100%;
}
}
&:not(.is-visible) {
display: none;
}
&.is-visible {
&:not(.backwards) {
opacity: 1;
}
}
}

30
src/scss/partials/_emojiDropdown.scss

@ -12,19 +12,19 @@
@include respond-to(esg-top) { @include respond-to(esg-top) {
position: absolute !important; position: absolute !important;
left: 1rem; left: 1rem;
bottom: calc(85px); bottom: 85px;
width: 420px !important; width: 420px !important;
height: 420px; height: 420px;
max-height: 420px; max-height: 420px;
box-shadow: 0px 5px 10px 5px rgba(16, 35, 47, .14); box-shadow: 0px 5px 10px 5px rgba(16, 35, 47, .14);
z-index: 3; z-index: 3;
border-radius: 10px; border-radius: 10px;
transition: all .2s ease-out; transition: transform .2s ease-out;
transform: scale(0); transform: scale(0);
transform-origin: 0 100%; transform-origin: 0 100%;
&.active { &.active {
transition: all .2s ease-in; transition: transform .2s ease-in;
transform: scale(1); transform: scale(1);
} }
} }
@ -225,29 +225,7 @@
grid-column-gap: 1px; grid-column-gap: 1px;
justify-content: space-between; justify-content: space-between;
> .grid-item {
html.no-touch &:hover {
border-radius: 12px;
background-color: var(--color-gray-hover);
}
/* &:nth-child(5n+5) {
margin-right: 0;
} */
> img, > .rlottie {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
> img {
animation: fade-in-opacity .2s ease forwards;
object-fit: contain;
}
}
} }
} }

1
src/scss/partials/_rightSidebar.scss

@ -613,6 +613,7 @@
&-sticker { &-sticker {
width: 68px; width: 68px;
height: 68px; height: 68px;
position: relative;
//padding: 0 5px; //padding: 0 5px;
&:hover { &:hover {

32
src/scss/style.scss

@ -721,6 +721,38 @@ img.emoji {
fill: rgba(0, 0, 0, .08); fill: rgba(0, 0, 0, .08);
} }
.super-stickers {
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fill, var(--esg-sticker-size)); // 64px
grid-column-gap: 1px;
justify-content: space-between;
}
.super-sticker {
html.no-touch &:hover {
border-radius: 12px;
background-color: var(--color-gray-hover);
}
/* &:nth-child(5n+5) {
margin-right: 0;
} */
> img, > .rlottie {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
> img {
animation: fade-in-opacity .2s ease forwards;
object-fit: contain;
}
}
.fade-in-transition { .fade-in-transition {
opacity: 1; opacity: 1;
transition: opacity .2s ease; transition: opacity .2s ease;

Loading…
Cancel
Save