From 6984d206741a087da62b74e781c505f69867024c Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Sat, 14 Jan 2023 01:28:45 +0400 Subject: [PATCH] Limit popup --- src/components/chat/bubbles.ts | 4 +- src/components/dialogsContextMenu.ts | 120 +-------------- .../emoticonsDropdown/tabs/emoji.ts | 2 +- src/components/popups/index.ts | 15 +- src/components/popups/limit.ts | 137 ++++++++++++++++++ .../sidebarLeft/tabs/chatFolders.ts | 29 ++-- src/components/swipeHandler.ts | 9 +- src/config/app.ts | 2 +- src/lang.ts | 3 + src/lib/appManagers/appImManager.ts | 6 + src/scss/partials/popups/_limit.scss | 46 ++++++ 11 files changed, 231 insertions(+), 142 deletions(-) create mode 100644 src/components/popups/limit.ts diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index ee916f10f..ab37cf089 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -5875,8 +5875,8 @@ export default class ChatBubbles { const message = await this.generateLocalFirstMessage(false, (message) => { const botInfo = userFull.bot_info; message.message = botInfo.description; - if(botInfo.description_document) message.media = {_: 'messageMediaDocument', document: botInfo.description_document}; - if(botInfo.description_photo) message.media = {_: 'messageMediaPhoto', photo: botInfo.description_photo}; + if(botInfo.description_document) message.media = {_: 'messageMediaDocument', document: botInfo.description_document, pFlags: {}}; + if(botInfo.description_photo) message.media = {_: 'messageMediaPhoto', photo: botInfo.description_photo, pFlags: {}}; }); if(!middleware()) { diff --git a/src/components/dialogsContextMenu.ts b/src/components/dialogsContextMenu.ts index ebc5ff213..885b99d03 100644 --- a/src/components/dialogsContextMenu.ts +++ b/src/components/dialogsContextMenu.ts @@ -13,15 +13,13 @@ import {ButtonMenuItemOptionsVerifiable, ButtonMenuSync} from './buttonMenu'; import PopupDeleteDialog from './popups/deleteDialog'; import {i18n, LangPackKey, _i18n} from '../lib/langPack'; import findUpTag from '../helpers/dom/findUpTag'; -import PopupPeer, {PopupPeerButton} from './popups/peer'; -import AppChatFoldersTab from './sidebarLeft/tabs/chatFolders'; -import appSidebarLeft from './sidebarLeft'; import {toastNew} from './toast'; import PopupMute from './popups/mute'; import {AppManagers} from '../lib/appManagers/managers'; import positionMenu from '../helpers/positionMenu'; import contextMenuController from '../helpers/contextMenuController'; import {GENERAL_TOPIC_ID} from '../lib/mtproto/mtproto_config'; +import showLimitPopup from './popups/limit'; export default class DialogsContextMenu { private element: HTMLElement; @@ -182,121 +180,7 @@ export default class DialogsContextMenu { } else if(filterId >= 1) { toastNew({langPackKey: 'PinFolderLimitReached'}); } else { - // const a: {[type in ApiLimitType]?: { - // title: LangPackKey, - // description: LangPackKey, - // descriptionPremium: LangPackKey, - // descriptionLocked: LangPackKey, - // icon: string - // }} = { - // pin: { - // title: 'LimitReached', - // description: 'LimitReachedPinDialogs', - // descriptionPremium: 'LimitReachedPinDialogsPremium', - // descriptionLocked: 'LimitReachedPinDialogsLocked', - // icon: 'limit_pin' - // } - // }; - - // class P extends PopupPeer { - // constructor(options: { - // isPremium: boolean, - // limit: number, - // limitPremium: number - // }, _a: typeof a[keyof typeof a]) { - // super('popup-limit', { - // buttons: options.isPremium === undefined ? [{ - // langKey: 'LimitReached.Ok', - // isCancel: true - // }] : (options.isPremium ? [{ - // langKey: 'OK', - // isCancel: true - // }] : [{ - // langKey: 'IncreaseLimit', - // callback: () => { - - // } - // }, { - // langKey: 'Cancel', - // isCancel: true - // }]), - // descriptionLangKey: options.isPremium === undefined ? _a.descriptionLocked : (options.isPremium ? _a.descriptionPremium : _a.description), - // descriptionLangArgs: options.isPremium ? [options.limitPremium] : [options.limit, options.limitPremium], - // titleLangKey: _a.title - // }); - - // const isLocked = options.isPremium === undefined; - // if(isLocked) { - // this.element.classList.add('is-locked'); - // } - - // const limitContainer = document.createElement('div'); - // limitContainer.classList.add('popup-limit-line'); - - // const hint = document.createElement('div'); - // hint.classList.add('popup-limit-hint'); - // const i = document.createElement('span'); - // i.classList.add('popup-limit-hint-icon', 'tgico-' + _a.icon); - // hint.append(i, '' + (options.isPremium ? options.limitPremium : options.limit)); - - // limitContainer.append(hint); - - // if(!isLocked) { - // const limit = document.createElement('div'); - // limit.classList.add('limit-line'); - - // const free = document.createElement('div'); - // free.classList.add('limit-line-free'); - - // const premium = document.createElement('div'); - // premium.classList.add('limit-line-premium'); - - // limit.append(free, premium); - - // _i18n(free, 'LimitFree'); - // premium.append(i18n('LimitPremium'), '' + options.limitPremium); - - // limitContainer.append(limit); - // } - - // this.container.insertBefore(limitContainer, this.description); - - // if(options.isPremium === false) { - // this.buttons.pop().element.remove(); - // } - // } - // } - - // async function showLimitPopup(type: keyof typeof a) { - // const _a = a[type]; - // const [appConfig, limit, limitPremium] = await Promise.all([ - // rootScope.managers.apiManager.getAppConfig(), - // ...[false, true].map((v) => rootScope.managers.apiManager.getLimit(type, v)) - // ]); - // const isLocked = appConfig.premium_purchase_blocked; - // new P({ - // isPremium: isLocked ? undefined : rootScope.premium, - // limit, - // limitPremium - // }, _a).show(); - // } - - // showLimitPopup('pin'); - - const config = await this.managers.apiManager.getConfig(); - new PopupPeer('pinned-dialogs-too-much', { - buttons: [{ - langKey: 'OK', - isCancel: true - }, { - langKey: 'FiltersSetupPinAlert', - callback: () => { - appSidebarLeft.createTab(AppChatFoldersTab).open(); - } - }], - descriptionLangKey: 'PinToTopLimitReached2', - descriptionLangArgs: [i18n('Chats', [config.pinned_dialogs_count_max])] - }).show(); + showLimitPopup('pin'); } } }); diff --git a/src/components/emoticonsDropdown/tabs/emoji.ts b/src/components/emoticonsDropdown/tabs/emoji.ts index 85238ea6c..4d22dcadd 100644 --- a/src/components/emoticonsDropdown/tabs/emoji.ts +++ b/src/components/emoticonsDropdown/tabs/emoji.ts @@ -719,7 +719,7 @@ export default class EmojiTab extends EmoticonsTabC { ) { const a = document.createElement('a'); a.onclick = () => { - appImManager.openUsername({userName: 'premiumbot'}); + appImManager.openPremiumBot(); hideToast(); }; toastNew({ diff --git a/src/components/popups/index.ts b/src/components/popups/index.ts index dd02d80b1..283643f80 100644 --- a/src/components/popups/index.ts +++ b/src/components/popups/index.ts @@ -28,9 +28,10 @@ export type PopupButton = { callback?: () => void, langKey?: LangPackKey, langArgs?: any[], - isDanger?: true, - isCancel?: true, - element?: HTMLButtonElement + isDanger?: boolean, + isCancel?: boolean, + element?: HTMLButtonElement, + noRipple?: boolean }; export type PopupOptions = Partial<{ @@ -180,16 +181,18 @@ export default class PopupElement extends const button = document.createElement('button'); button.className = 'btn' + (b.isDanger ? ' danger' : ' primary'); - ripple(button); + if(!b.noRipple) { + ripple(button); + } if(b.text) { - button.innerHTML = b.text; + button.textContent = b.text; } else { button.append(i18n(b.langKey, b.langArgs)); } attachClickEvent(button, () => { - b.callback && b.callback(); + b.callback?.(); this.destroy(); }, {listenerSetter: this.listenerSetter, once: true}); diff --git a/src/components/popups/limit.ts b/src/components/popups/limit.ts new file mode 100644 index 000000000..6ca7ffeee --- /dev/null +++ b/src/components/popups/limit.ts @@ -0,0 +1,137 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +import {doubleRaf} from '../../helpers/schedulers'; +import appImManager from '../../lib/appManagers/appImManager'; +import {LangPackKey, _i18n, i18n} from '../../lib/langPack'; +import {ApiLimitType} from '../../lib/mtproto/api_methods'; +import rootScope from '../../lib/rootScope'; +import PopupPeer from './peer'; + +const a: {[type in ApiLimitType]?: { + title: LangPackKey, + description: LangPackKey, + descriptionPremium: LangPackKey, + descriptionLocked: LangPackKey, + icon: string +}} = { + pin: { + title: 'LimitReached', + description: 'LimitReachedPinDialogs', + descriptionPremium: 'LimitReachedPinDialogsPremium', + descriptionLocked: 'LimitReachedPinDialogsLocked', + icon: 'limit_pin' + }, + folders: { + title: 'LimitReached', + description: 'LimitReachedFolders', + descriptionPremium: 'LimitReachedFoldersPremium', + descriptionLocked: 'LimitReachedFoldersLocked', + icon: 'limit_folders' + } +}; + +class P extends PopupPeer { + constructor(options: { + isPremium: boolean, + limit: number, + limitPremium: number + }, _a: typeof a[keyof typeof a]) { + super('popup-limit', { + buttons: options.isPremium === undefined ? [{ + langKey: 'LimitReached.Ok', + isCancel: true + }] : (options.isPremium ? [{ + langKey: 'OK', + isCancel: true + }] : [{ + langKey: 'IncreaseLimit', + callback: () => { + appImManager.openPremiumBot(); + }, + noRipple: true + }, { + langKey: 'Cancel', + isCancel: true + }]), + descriptionLangKey: options.isPremium === undefined ? _a.descriptionLocked : (options.isPremium ? _a.descriptionPremium : _a.description), + descriptionLangArgs: options.isPremium ? [options.limitPremium] : [options.limit, options.limitPremium], + titleLangKey: _a.title + }); + + const isLocked = options.isPremium === undefined; + if(isLocked) { + this.element.classList.add('is-locked'); + } else if(options.isPremium) { + this.element.classList.add('is-premium'); + } else { + const button = this.buttons.find((b) => !b.isCancel); + button.element.classList.add('popup-limit-button'); + const i = document.createElement('i'); + i.classList.add('popup-limit-button-icon', 'tgico-premium_double'); + button.element.append(i); + } + + const limitContainer = document.createElement('div'); + limitContainer.classList.add('popup-limit-line'); + + const hint = document.createElement('div'); + hint.classList.add('popup-limit-hint'); + const i = document.createElement('span'); + i.classList.add('popup-limit-hint-icon', 'tgico-' + _a.icon); + hint.append(i, '' + (options.isPremium ? options.limitPremium : options.limit)); + + limitContainer.append(hint); + + if(!isLocked) { + const limit = document.createElement('div'); + limit.classList.add('limit-line'); + + const free = document.createElement('div'); + free.classList.add('limit-line-free'); + + const premium = document.createElement('div'); + premium.classList.add('limit-line-premium'); + + limit.append(free, premium); + + _i18n(free, 'LimitFree'); + premium.append(i18n('LimitPremium'), '' + options.limitPremium); + + limitContainer.append(limit); + } + + this.container.insertBefore(limitContainer, this.description); + + // if(options.isPremium === false) { + // this.buttons.pop().element.remove(); + // } + + const setHintActive = () => { + hint.classList.add('active'); + }; + + if(rootScope.settings.animationsEnabled) { + doubleRaf().then(setHintActive); + } else { + setHintActive(); + } + } +} + +export default async function showLimitPopup(type: keyof typeof a) { + const _a = a[type]; + const [appConfig, limit, limitPremium] = await Promise.all([ + rootScope.managers.apiManager.getAppConfig(), + ...[false, true].map((v) => rootScope.managers.apiManager.getLimit(type, v)) + ]); + const isLocked = appConfig.premium_purchase_blocked; + new P({ + isPremium: isLocked ? undefined : rootScope.premium, + limit, + limitPremium + }, _a).show(); +} diff --git a/src/components/sidebarLeft/tabs/chatFolders.ts b/src/components/sidebarLeft/tabs/chatFolders.ts index f62bc9a35..77eff156b 100644 --- a/src/components/sidebarLeft/tabs/chatFolders.ts +++ b/src/components/sidebarLeft/tabs/chatFolders.ts @@ -9,7 +9,6 @@ import type {DialogFilterSuggested} from '../../../layer'; import type _rootScope from '../../../lib/rootScope'; import {SliderSuperTab} from '../../slider'; import lottieLoader, {LottieLoader} from '../../../lib/rlottie/lottieLoader'; -import {toast} from '../../toast'; import Button from '../../button'; import rootScope from '../../../lib/rootScope'; import AppEditFolderTab from './editFolder'; @@ -26,6 +25,7 @@ import SettingSection from '../../settingSection'; import Sortable from '../../../helpers/dom/sortable'; import whichChild from '../../../helpers/dom/whichChild'; import indexOfAndSplice from '../../../helpers/array/indexOfAndSplice'; +import showLimitPopup from '../../popups/limit'; export default class AppChatFoldersTab extends SliderSuperTab { private createFolderBtn: HTMLElement; @@ -160,7 +160,7 @@ export default class AppChatFoldersTab extends SliderSuperTab { this.foldersSection = new SettingSection({ name: 'Filters' }); - this.foldersSection.container.style.display = 'none'; + this.foldersSection.container.classList.add('hide'); this.list = document.createElement('div'); this.foldersSection.content.append(this.list); @@ -168,20 +168,26 @@ export default class AppChatFoldersTab extends SliderSuperTab { this.suggestedSection = new SettingSection({ name: 'FilterRecommended' }); - this.suggestedSection.container.style.display = 'none'; + this.suggestedSection.container.classList.add('hide'); - this.scrollable.append(this.stickerContainer, caption, this.createFolderBtn, this.foldersSection.container, this.suggestedSection.container); + this.scrollable.append( + this.stickerContainer, + caption, + this.createFolderBtn, + this.foldersSection.container, + this.suggestedSection.container + ); attachClickEvent(this.createFolderBtn, async() => { if(!(await this.canCreateFolder())) { - toast('Sorry, you can\'t create more folders.'); + showLimitPopup('folders'); } else { this.slider.createTab(AppEditFolderTab).open(); } }, {listenerSetter: this.listenerSetter}); const onFiltersContainerUpdate = () => { - this.foldersSection.container.style.display = Object.keys(this.filtersRendered).length ? '' : 'none'; + this.foldersSection.container.classList.toggle('hide', !Object.keys(this.filtersRendered).length); }; this.managers.filtersStorage.getDialogFilters().then(async(filters) => { @@ -310,7 +316,7 @@ export default class AppChatFoldersTab extends SliderSuperTab { private getSuggestedFilters() { return this.managers.filtersStorage.getSuggestedDialogsFilters().then(async(suggestedFilters) => { - this.suggestedSection.container.style.display = suggestedFilters.length ? '' : 'none'; + this.suggestedSection.container.classList.toggle('hide', !suggestedFilters.length); Array.from(this.suggestedSection.content.children).slice(1).forEach((el) => el.remove()); for(const filter of suggestedFilters) { @@ -322,7 +328,7 @@ export default class AppChatFoldersTab extends SliderSuperTab { cancelEvent(e); if(!(await this.canCreateFolder())) { - toast('Sorry, you can\'t create more folders.'); + showLimitPopup('folders'); return; } @@ -333,10 +339,9 @@ export default class AppChatFoldersTab extends SliderSuperTab { f.excludePeerIds = []; f.pinnedPeerIds = []; - this.managers.filtersStorage.createDialogFilter(f, true).then((bool) => { - if(bool) { - row.container.remove(); - } + this.managers.filtersStorage.createDialogFilter(f, true).then(() => { + row.container.remove(); + this.suggestedSection.container.classList.toggle('hide', this.suggestedSection.content.childElementCount === 1); }).finally(() => { button.removeAttribute('disabled'); }); diff --git a/src/components/swipeHandler.ts b/src/components/swipeHandler.ts index 074cba704..abbaa6d25 100644 --- a/src/components/swipeHandler.ts +++ b/src/components/swipeHandler.ts @@ -179,7 +179,13 @@ export default class SwipeHandler { this.listenerSetter.add(attachGlobalListenerTo)('mousemove', this.handleMove, MOUSE_MOVE_OPTIONS); } - this.onStart?.(); + if(this.onStart) { + this.onStart(); + + // have to initiate move instantly + this.hadMove = true; + this.handleMove(e); + } }; protected handleMove = (_e: EE) => { @@ -213,7 +219,6 @@ export default class SwipeHandler { this.onFirstSwipe?.(_e); } - /* reset values */ const onSwipeResult = this.onSwipe(xDiff, yDiff, _e); if(onSwipeResult !== undefined && onSwipeResult) { this.reset(); diff --git a/src/config/app.ts b/src/config/app.ts index bb0cb3de6..09040dd90 100644 --- a/src/config/app.ts +++ b/src/config/app.ts @@ -21,7 +21,7 @@ const App = { version: process.env.VERSION, versionFull: process.env.VERSION_FULL, build: +process.env.BUILD, - langPackVersion: '0.7.7', + langPackVersion: '0.7.8', langPack: 'webk', langPackCode: 'en', domains: MAIN_DOMAINS, diff --git a/src/lang.ts b/src/lang.ts index 1aecf7e28..8a0d1d07c 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -869,6 +869,9 @@ const lang = { 'TerminateWebSessionInfo': 'Tap to disconnect from your Telegram account.', 'EnablePhotoSpoiler': 'Hide with spoiler', 'DisablePhotoSpoiler': 'Remove spoiler', + 'LimitReachedFolders': 'You have reached the limit of **%1$d** folders. You can double the limit to **%2$d** folders by subscribing to **Telegram Premium**.', + 'LimitReachedFoldersPremium': 'You have reached the limit of **%1$d** folders for this account.', + 'LimitReachedFoldersLocked': 'You have reached the limit of **%1$d** folders for this account. We are working to let you increase this limit in the future.', // * macos 'AccountSettings.Filters': 'Chat Folders', diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index a784ddf5b..992055e8d 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -1293,6 +1293,12 @@ export class AppImManager extends EventListenerBase<{ }); } + public openPremiumBot() { + return this.managers.apiManager.getAppConfig().then((appConfig) => { + return this.openUsername({userName: appConfig.premium_bot_username}); + }); + } + public openUsername(options: { userName: string } & Omit) { diff --git a/src/scss/partials/popups/_limit.scss b/src/scss/partials/popups/_limit.scss index 53e423f51..859e33f7f 100644 --- a/src/scss/partials/popups/_limit.scss +++ b/src/scss/partials/popups/_limit.scss @@ -22,6 +22,15 @@ align-items: center; justify-content: center; font-weight: var(--font-weight-bold); + transform: scale(.6) translate(-280px, 16px) rotate(-30deg); + + @include animation-level(2) { + transition: transform .3s cubic-bezier(.12,1.1,.56,1.2); + } + + &.active { + transform: scale(1) translate(0, 0) rotate(0); + } &-icon { font-size: 1.25rem; @@ -30,6 +39,21 @@ } } + &-button { + &.primary { + background: linear-gradient(88.39deg, #6C93FF -2.56%, #976FFF 51.27%, #DF69D1 107.39%); + color: #fff !important; + display: flex; + align-items: center; + } + + &-icon { + margin-left: .625rem; + font-size: 1.5rem; + display: flex; + } + } + .limit-line { align-self: stretch; margin: 1rem .5rem 0; @@ -67,4 +91,26 @@ margin-right: -.25rem; background-color: var(--primary-color); } + + &.is-premium &-hint { + align-self: flex-end; + margin-right: .5rem; + border-bottom-right-radius: 0; + background: linear-gradient(84.4deg,#6c93ff -4.85%,#976fff 51.72%,#df69d1 110.7%); + background-size: 200px 2rem; + background-position-x: 100%; + + @include animation-level(2) { + transition: transform .3s cubic-bezier(.12,1.1,.56,1.1); + } + + &:after { + height: 12px; + left: 100%; + bottom: -11.5px; + margin-left: -20.6px; + background-position-x: 134%; + clip-path: path("M8.44528 0.5H20.5V10.1943C20.5 10.9154 19.9154 11.5 19.1943 11.5C18.8178 11.5 18.4597 11.3375 18.2117 11.0541L10.2274 1.92918C9.75146 1.38523 9.18812 0.924478 8.56057 0.565879L8.44528 0.5Z"); + } + } }