From 3f7392699afac2eb74b66d3a1cd3702e82479ffd Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Wed, 8 Feb 2023 00:47:49 +0100 Subject: [PATCH] Stickers: Add 'Copy Link' option (#2502) --- src/components/common/StickerSetModal.tsx | 85 +++++++++++++++------ src/components/main/WebAppModal.module.scss | 12 ++- src/components/main/WebAppModal.tsx | 10 +-- src/components/ui/Menu.scss | 2 +- src/lib/teact/teact.ts | 14 ++-- 5 files changed, 83 insertions(+), 40 deletions(-) diff --git a/src/components/common/StickerSetModal.tsx b/src/components/common/StickerSetModal.tsx index e39246c34..06b3f8776 100644 --- a/src/components/common/StickerSetModal.tsx +++ b/src/components/common/StickerSetModal.tsx @@ -1,25 +1,27 @@ import React, { - memo, useCallback, useEffect, useRef, + memo, useCallback, useEffect, useMemo, useRef, } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import type { FC } from '../../lib/teact/teact'; import type { ApiSticker, ApiStickerSet } from '../../api/types'; -import { EMOJI_SIZE_MODAL, STICKER_SIZE_MODAL } from '../../config'; +import { EMOJI_SIZE_MODAL, STICKER_SIZE_MODAL, TME_LINK_PREFIX } from '../../config'; import { selectCanScheduleUntilOnline, selectChat, selectCurrentMessageList, selectIsChatWithSelf, selectIsCurrentUserPremium, - selectIsSetPremium, selectShouldSchedule, selectStickerSet, } from '../../global/selectors'; +import renderText from './helpers/renderText'; +import { copyTextToClipboard } from '../../util/clipboard'; +import { getAllowedAttachmentOptions, getCanPostInChat } from '../../global/helpers'; + import { useIntersectionObserver } from '../../hooks/useIntersectionObserver'; import useLang from '../../hooks/useLang'; -import renderText from './helpers/renderText'; -import { getAllowedAttachmentOptions, getCanPostInChat } from '../../global/helpers'; +import useAppLayout from '../../hooks/useAppLayout'; import useSchedule from '../../hooks/useSchedule'; import usePrevious from '../../hooks/usePrevious'; @@ -27,6 +29,8 @@ import Modal from '../ui/Modal'; import Button from '../ui/Button'; import Loading from '../ui/Loading'; import StickerButton from './StickerButton'; +import DropdownMenu from '../ui/DropdownMenu'; +import MenuItem from '../ui/MenuItem'; import './StickerSetModal.scss'; @@ -43,7 +47,6 @@ type StateProps = { canScheduleUntilOnline?: boolean; shouldSchedule?: boolean; isSavedMessages?: boolean; - isSetPremium?: boolean; isCurrentUserPremium?: boolean; }; @@ -58,7 +61,6 @@ const StickerSetModal: FC = ({ canScheduleUntilOnline, shouldSchedule, isSavedMessages, - isSetPremium, isCurrentUserPremium, onClose, }) => { @@ -66,7 +68,7 @@ const StickerSetModal: FC = ({ loadStickers, toggleStickerSet, sendMessage, - openPremiumModal, + showNotification, } = getActions(); // eslint-disable-next-line no-null/no-null @@ -76,12 +78,13 @@ const StickerSetModal: FC = ({ const lang = useLang(); + const { isMobile } = useAppLayout(); + const prevStickerSet = usePrevious(stickerSet); const renderingStickerSet = stickerSet || prevStickerSet; const isAdded = Boolean(!renderingStickerSet?.isArchived && renderingStickerSet?.installedDate); const isEmoji = renderingStickerSet?.isEmoji; - const isButtonLocked = !isAdded && isSetPremium && !isCurrentUserPremium; const [requestCalendar, calendar] = useSchedule(canScheduleUntilOnline); @@ -118,20 +121,24 @@ const StickerSetModal: FC = ({ const handleButtonClick = useCallback(() => { if (renderingStickerSet) { - if (isButtonLocked) { - openPremiumModal({ initialSection: 'animated_emoji' }); - return; - } toggleStickerSet({ stickerSetId: renderingStickerSet.id }); onClose(); } - }, [isButtonLocked, onClose, openPremiumModal, renderingStickerSet, toggleStickerSet]); + }, [onClose, renderingStickerSet, toggleStickerSet]); + + const handleCopyLink = useCallback(() => { + if (!renderingStickerSet) return; + const { shortName } = renderingStickerSet; + const suffix = isEmoji ? 'addemoji' : 'addstickers'; + const url = `${TME_LINK_PREFIX}${suffix}/${shortName}`; + copyTextToClipboard(url); + showNotification({ + message: lang('LinkCopied'), + }); + }, [isEmoji, lang, renderingStickerSet, showNotification]); const renderButtonText = () => { if (!renderingStickerSet) return lang('Loading'); - if (isButtonLocked) { - return lang('EmojiInput.UnlockPack', renderingStickerSet.title); - } const suffix = isEmoji ? 'Emoji' : 'Sticker'; @@ -142,14 +149,48 @@ const StickerSetModal: FC = ({ ); }; + const MoreMenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => { + return ({ onTrigger, isOpen: isMenuOpen }) => ( + + ); + }, [isMobile]); + + function renderHeader() { + return ( +
+ +
+ {renderingStickerSet ? renderText(renderingStickerSet.title, ['emoji', 'links']) : lang('AccDescrStickerSet')} +
+ + {lang('StickersCopy')} + +
+ ); + } + return ( {renderingStickerSet?.stickers ? ( <> @@ -175,8 +216,6 @@ const StickerSetModal: FC = ({ size="smaller" fluid color={isAdded ? 'danger' : 'primary'} - isShiny={isButtonLocked} - withPremiumGradient={isButtonLocked} onClick={handleButtonClick} > {renderButtonText()} @@ -206,7 +245,6 @@ export default memo(withGlobal( : stickerSetShortName ? { shortName: stickerSetShortName } : undefined; const stickerSet = stickerSetInfo ? selectStickerSet(global, stickerSetInfo) : undefined; - const isSetPremium = stickerSet && selectIsSetPremium(stickerSet); return { canScheduleUntilOnline: Boolean(chatId) && selectCanScheduleUntilOnline(global, chatId), @@ -215,7 +253,6 @@ export default memo(withGlobal( shouldSchedule: selectShouldSchedule(global), stickerSet, isCurrentUserPremium: selectIsCurrentUserPremium(global), - isSetPremium, }; }, )(StickerSetModal)); diff --git a/src/components/main/WebAppModal.module.scss b/src/components/main/WebAppModal.module.scss index 5b69a36ad..92b7b0a68 100644 --- a/src/components/main/WebAppModal.module.scss +++ b/src/components/main/WebAppModal.module.scss @@ -3,6 +3,8 @@ .modal-header { border-bottom: 1px solid var(--color-dividers); padding: 0.5rem; + + transition: 0.25s ease-in-out background-color; } .modal-dialog { @@ -21,6 +23,8 @@ padding: 0; border-bottom-right-radius: var(--border-radius-default); border-bottom-left-radius: var(--border-radius-default); + + transition: 0.25s ease-in-out background-color; } @media (max-width: 600px) { @@ -84,7 +88,7 @@ left: 50%; transform: translate(-50%, -50%); - transition: opacity 0.2s ease-in-out; + transition: opacity 0.25s ease-in-out; } .hide { @@ -106,8 +110,12 @@ position: absolute; bottom: 0; border-radius: 0; + + z-index: 1; transform: translateY(100%); - transition: 0.25s ease-in-out transform; + transition-property: background-color, color, transform; + transition-duration: 0.25s; + transition-timing-function: ease-in-out; &.visible { transform: translateY(0); diff --git a/src/components/main/WebAppModal.tsx b/src/components/main/WebAppModal.tsx index 556c28db0..fdb99ecd0 100644 --- a/src/components/main/WebAppModal.tsx +++ b/src/components/main/WebAppModal.tsx @@ -341,7 +341,7 @@ const WebAppModal: FC = ({ isBackButtonVisible && styles.stateBack, ); - const header = useMemo(() => { + function renderHeader() { return (
); - }, [ - lang, handleBackClick, bot, MoreMenuButton, chat, openBotChat, handleRefreshClick, attachBot, - handleToggleClick, handleSettingsButtonClick, isBackButtonVisible, headerColor, backButtonClassName, - ]); + } const prevMainButtonColor = usePrevious(mainButton?.color, true); const prevMainButtonTextColor = usePrevious(mainButton?.textColor, true); @@ -430,8 +427,7 @@ const WebAppModal: FC = ({ className={styles.root} isOpen={isOpen} onClose={handleClose} - header={header} - hasCloseButton + header={renderHeader()} style={`background-color: ${backgroundColor}`} > diff --git a/src/components/ui/Menu.scss b/src/components/ui/Menu.scss index eaa25b2a8..35e383a9a 100644 --- a/src/components/ui/Menu.scss +++ b/src/components/ui/Menu.scss @@ -67,7 +67,7 @@ } } - body.has-open-dialog &:not(.CustomSendMenu):not(.web-app-more-menu):not(.attachment-modal-more-menu) .bubble { + body.has-open-dialog &:not(.CustomSendMenu):not(.web-app-more-menu):not(.attachment-modal-more-menu):not(.stickers-more-menu) .bubble { transition: none !important; } diff --git a/src/lib/teact/teact.ts b/src/lib/teact/teact.ts index de0e36546..3a5d18227 100644 --- a/src/lib/teact/teact.ts +++ b/src/lib/teact/teact.ts @@ -337,16 +337,16 @@ function scheduleUpdate(componentInstance: ComponentInstance) { export function renderComponent(componentInstance: ComponentInstance) { idsToExcludeFromUpdate.add(componentInstance.id); - renderingInstance = componentInstance; - componentInstance.hooks.state.cursor = 0; - componentInstance.hooks.effects.cursor = 0; - componentInstance.hooks.memos.cursor = 0; - componentInstance.hooks.refs.cursor = 0; - const { Component, props } = componentInstance; let newRenderedValue; try { + renderingInstance = componentInstance; + componentInstance.hooks.state.cursor = 0; + componentInstance.hooks.effects.cursor = 0; + componentInstance.hooks.memos.cursor = 0; + componentInstance.hooks.refs.cursor = 0; + // eslint-disable-next-line @typescript-eslint/naming-convention let DEBUG_startAt: number | undefined; if (DEBUG) { @@ -388,6 +388,8 @@ export function renderComponent(componentInstance: ComponentInstance) { } } } catch (err: any) { + // eslint-disable-next-line no-console + console.error(`[Teact] Error while rendering component ${componentInstance.name}`); handleError(err); newRenderedValue = componentInstance.renderedValue;