diff --git a/src/components/emoticonsDropdown/tabs/stickers.ts b/src/components/emoticonsDropdown/tabs/stickers.ts index de2461eb..04326132 100644 --- a/src/components/emoticonsDropdown/tabs/stickers.ts +++ b/src/components/emoticonsDropdown/tabs/stickers.ts @@ -25,6 +25,9 @@ import ButtonIcon from "../../buttonIcon"; import positionElementByIndex from "../../../helpers/dom/positionElementByIndex"; import VisibilityIntersector, { OnVisibilityChange } from "../../visibilityIntersector"; import findAndSplice from "../../../helpers/array/findAndSplice"; +import { attachClickEvent } from "../../../helpers/dom/clickEvent"; +import confirmationPopup from "../../confirmationPopup"; +import noop from "../../../helpers/noop"; export class SuperStickerRenderer { public lazyLoadQueue: LazyLoadQueueRepeat; @@ -213,37 +216,49 @@ export default class StickersTab implements EmoticonsTab { this.categoriesMap.set(container, category); this.categoriesIntersector.observe(container); + this.stickyIntersector.observeStickyHeaderChanges(container); return category; } - private categoryPush( + private categoryAppendStickers( category: StickersTabCategory, promise: Promise ) { - const {container, items} = category.elements; - this.stickyIntersector.observeStickyHeaderChanges(container); + const {container} = category.elements; promise.then((documents) => { + const isVisible = this.isCategoryVisible(category); + documents.forEach((document) => { const element = this.superStickerRenderer.renderSticker(document); category.items.push({document, element}); - // items.append(element); - }); - const containerWidth = 410; - const stickerSize = mediaSizes.active.esgSticker.width; - - const itemsPerRow = Math.floor(containerWidth / stickerSize); - const rows = Math.ceil(documents.length / itemsPerRow); - const height = rows * stickerSize; - - items.style.height = height + 'px'; + if(isVisible) { + category.elements.items.append(element); + } + }); + this.setCategoryItemsHeight(category); container.classList.remove('hide'); }); } + private isCategoryVisible(category: StickersTabCategory) { + return this.categoriesIntersector.getVisible().includes(category.elements.container); + } + + private setCategoryItemsHeight(category: StickersTabCategory) { + const containerWidth = this.content.getBoundingClientRect().width - 10; + const stickerSize = mediaSizes.active.esgSticker.width; + + const itemsPerRow = Math.floor(containerWidth / stickerSize); + const rows = Math.ceil(category.items.length / itemsPerRow); + const height = rows * stickerSize; + + category.elements.items.style.minHeight = height + 'px'; + } + private async renderStickerSet(set: StickerSet.stickerSet, prepend = false) { const category = this.createCategory(set, wrapEmojiText(set.title)); const {menuTab, menuTabPadding, container} = category.elements; @@ -251,7 +266,7 @@ export default class StickersTab implements EmoticonsTab { positionElementByIndex(menuTab, this.menu, prepend ? 1 : 0xFFFF); const promise = this.managers.appStickersManager.getStickerSet(set); - this.categoryPush( + this.categoryAppendStickers( category, promise.then((stickerSet) => stickerSet.documents as MyDocument[]) ); @@ -313,29 +328,21 @@ export default class StickersTab implements EmoticonsTab { 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); - } - }); - - rootScope.addEventListener('stickers_deleted', ({id}) => { - const set = this.categories[id]; - if(set && this.mounted) { - set.elements.container.remove(); - set.elements.menuTab.remove(); - this.categoriesIntersector.unobserve(set.elements.container); - set.items.forEach(({element}) => this.superStickerRenderer.unobserveAnimated(element)); - delete this.categories[id]; - this.categoriesMap.delete(set.elements.container); - } - }); + const clearCategoryItems = (category: StickersTabCategory) => { + category.elements.items.textContent = ''; + category.items.forEach(({element}) => this.superStickerRenderer.unobserveAnimated(element)); + category.items.length = 0; + }; this.scroll.container.addEventListener('click', (e) => { const target = e.target as HTMLElement; if(findUpClassName(target, 'category-title')) { const container = findUpClassName(target, 'emoji-category'); const category = this.categoriesMap.get(container); + if(category.set.id === 'recent') { + return; + } + new PopupStickers({id: category.set.id, access_hash: category.set.access_hash}).show(); return; } @@ -355,7 +362,8 @@ export default class StickersTab implements EmoticonsTab { setTyping(); }); - this.stickyIntersector = EmoticonsDropdown.menuOnClick(this.menu, this.scroll, menuScroll).stickyIntersector; + const {stickyIntersector, setActive} = EmoticonsDropdown.menuOnClick(this.menu, this.scroll, menuScroll); + this.stickyIntersector = stickyIntersector; const preloader = putPreloader(this.content, true); @@ -363,15 +371,34 @@ export default class StickersTab implements EmoticonsTab { 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); + this.toggleRecentCategory(recentCategory, false); + + const clearButton = ButtonIcon('close', {noRipple: true}); + recentCategory.elements.title.append(clearButton); + attachClickEvent(clearButton, () => { + confirmationPopup({ + titleLangKey: 'ClearRecentStickersAlertTitle', + descriptionLangKey: 'ClearRecentStickersAlertMessage', + button: { + langKey: 'Clear' + } + }).then(() => { + this.managers.appStickersManager.clearRecentStickers(); + }, noop); + }); + + const onRecentStickers = (stickers: MyDocument[]) => { + const sliced = stickers.slice(0, RECENT_STICKERS_COUNT) as MyDocument[]; + + clearCategoryItems(recentCategory); + this.toggleRecentCategory(recentCategory, !!sliced.length); + this.categoryAppendStickers(recentCategory, Promise.resolve(sliced)); + }; Promise.all([ this.managers.appStickersManager.getRecentStickers().then((stickers) => { - const sliced = stickers.stickers.slice(0, RECENT_STICKERS_COUNT) as MyDocument[]; - preloader.remove(); - this.categoryPush(recentCategory, Promise.resolve(sliced)); + onRecentStickers(stickers.stickers as MyDocument[]); }), this.managers.appStickersManager.getAllStickers().then((res) => { @@ -384,6 +411,7 @@ export default class StickersTab implements EmoticonsTab { ]).finally(() => { this.mounted = true; setTyping(); + setActive(0); }); this.superStickerRenderer = new SuperStickerRenderer(EmoticonsDropdown.lazyLoadQueue, EMOTICONSSTICKERGROUP, this.managers, intersectionOptions); @@ -416,20 +444,66 @@ export default class StickersTab implements EmoticonsTab { // rendererLazyLoadQueue.intersector.getVisible().length // ); // }, .25e3); + + rootScope.addEventListener('stickers_installed', (set) => { + if(!this.categories[set.id] && this.mounted) { + this.renderStickerSet(set, true); + } + }); + + rootScope.addEventListener('stickers_deleted', ({id}) => { + const category = this.categories[id]; + if(category && this.mounted) { + category.elements.container.remove(); + category.elements.menuTab.remove(); + this.categoriesIntersector.unobserve(category.elements.container); + clearCategoryItems(category); + delete this.categories[id]; + this.categoriesMap.delete(category.elements.container); + } + }); + + rootScope.addEventListener('stickers_recent', (stickers) => { + if(this.mounted) { + onRecentStickers(stickers); + } + }); + + const resizeCategories = () => { + for(const [container, category] of this.categoriesMap) { + this.setCategoryItemsHeight(category); + } + }; + + mediaSizes.addEventListener('resize', resizeCategories); + + emoticonsDropdown.addEventListener('opened', resizeCategories); this.init = null; } + private toggleRecentCategory(category: StickersTabCategory, visible: boolean) { + if(!visible) { + category.elements.menuTab.remove(); + category.elements.container.remove(); + } else { + positionElementByIndex(category.elements.menuTab, this.menu, 0); + positionElementByIndex(category.elements.container, this.scroll.container, 0); + } + + // category.elements.container.classList.toggle('hide', !visible); + } + public pushRecentSticker(doc: MyDocument) { this.managers.appStickersManager.pushRecentSticker(doc.id); - const set = this.categories['recent']; - if(!set) { + const category = this.categories['recent']; + if(!category) { return; } - const items = set.elements.items; - let item = findAndSplice(set.items, (item) => item.document.id === doc.id); + const items = category.elements.items; + let item = findAndSplice(category.items, (item) => item.document.id === doc.id); if(!item) { item = { element: this.superStickerRenderer.renderSticker(doc), @@ -437,11 +511,14 @@ export default class StickersTab implements EmoticonsTab { }; } - set.items.unshift(item); + category.items.unshift(item); if(items.childElementCount) items.prepend(item.element); if(items.childElementCount > RECENT_STICKERS_COUNT) { (Array.from(items.children) as HTMLElement[]).slice(RECENT_STICKERS_COUNT).forEach((el) => el.remove()); } + + this.setCategoryItemsHeight(category); + this.toggleRecentCategory(category, true); } onClose() { diff --git a/src/helpers/dropdownHover.ts b/src/helpers/dropdownHover.ts index f56631d0..d0c125bf 100644 --- a/src/helpers/dropdownHover.ts +++ b/src/helpers/dropdownHover.ts @@ -48,7 +48,10 @@ export default class DropdownHover extends EventListenerBase<{ listenerSetter.add(button)('mouseover', (e) => { //console.log('onmouseover button'); if(firstTime) { - listenerSetter.add(button)('mouseout', this.onMouseOut); + listenerSetter.add(button)('mouseout', (e) => { + clearTimeout(this.displayTimeout); + this.onMouseOut(e); + }); firstTime = false; } diff --git a/src/lang.ts b/src/lang.ts index dc4684fd..4aede0fa 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -750,6 +750,8 @@ const lang = { "Clear": "Clear", "Save": "Save", "PaymentCheckoutName": "Name", + "ClearRecentStickersAlertTitle": "Clear recent stickers", + "ClearRecentStickersAlertMessage": "Do you want to clear all your recent stickers?", // * macos "AccountSettings.Filters": "Chat Folders", diff --git a/src/lib/appManagers/appStickersManager.ts b/src/lib/appManagers/appStickersManager.ts index 8180f772..e8fa940f 100644 --- a/src/lib/appManagers/appStickersManager.ts +++ b/src/lib/appManagers/appStickersManager.ts @@ -72,6 +72,12 @@ export class AppStickersManager extends AppManager { const stickerSet = update.stickerset as MyMessagesStickerSet; this.saveStickerSet(stickerSet, stickerSet.set.id); this.rootScope.dispatchEvent('stickers_installed', stickerSet.set); + }, + + updateRecentStickers: () => { + this.getRecentStickers().then(({stickers}) => { + this.rootScope.dispatchEvent('stickers_recent', stickers as MyDocument[]); + }); } }); } @@ -576,4 +582,9 @@ export class AppStickersManager extends AppManager { }); } } + + public clearRecentStickers() { + this.rootScope.dispatchEvent('stickers_recent', []); + return this.apiManager.invokeApi('messages.clearRecentStickers'); + } } diff --git a/src/lib/rootScope.ts b/src/lib/rootScope.ts index 0010a374..9f6bfc0a 100644 --- a/src/lib/rootScope.ts +++ b/src/lib/rootScope.ts @@ -16,6 +16,7 @@ import type { AppManagers } from "./appManagers/managers"; import type { State } from "../config/state"; import type { Progress } from "./appManagers/appDownloadManager"; import type { CallId } from "./appManagers/appCallsManager"; +import type { MyDocument } from "./appManagers/appDocsManager"; import { NULL_PEER_ID, UserAuth } from "./mtproto/mtproto_config"; import EventListenerBase from "../helpers/eventListenerBase"; import { MOUNT_CLASS_TO } from "../config/debug"; @@ -85,6 +86,7 @@ export type BroadcastEvents = { 'stickers_installed': StickerSet.stickerSet, 'stickers_deleted': StickerSet.stickerSet, + 'stickers_recent': MyDocument[], 'state_cleared': void, 'state_synchronized': ChatId | void, diff --git a/src/scss/partials/_emojiDropdown.scss b/src/scss/partials/_emojiDropdown.scss index 459c336f..840149e2 100644 --- a/src/scss/partials/_emojiDropdown.scss +++ b/src/scss/partials/_emojiDropdown.scss @@ -84,6 +84,17 @@ color: var(--secondary-text-color); padding: .8125rem .875rem .6875rem; width: 100%; + position: relative; + + .btn-icon { + position: absolute; + right: 0.5rem; + top: 50%; + transform: translateY(-50%); + font-size: 1.25rem; + z-index: 1; + pointer-events: all; + } } .tabs-container { @@ -152,10 +163,6 @@ } .category-items { - width: 100%; - display: grid; - grid-template-columns: repeat(auto-fill, var(--esg-sticker-size)); // 64px - justify-content: space-between; padding: 0 .3125rem; }