// Copyright 2019-2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; import classNames from 'classnames'; import { useRestoreFocus } from '../../util/hooks'; import { StickerPackType, StickerType } from '../../state/ducks/stickers'; import { LocalizerType } from '../../types/Util'; export type OwnProps = { readonly i18n: LocalizerType; readonly onClose: () => unknown; readonly onClickAddPack: () => unknown; readonly onPickSticker: (packId: string, stickerId: number) => unknown; readonly packs: ReadonlyArray; readonly recentStickers: ReadonlyArray; readonly showPickerHint?: boolean; }; export type Props = OwnProps & Pick, 'style'>; function useTabs(tabs: ReadonlyArray, initialTab = tabs[0]) { const [tab, setTab] = React.useState(initialTab); const handlers = React.useMemo( () => tabs.map(t => () => { setTab(t); }), [tabs] ); return [tab, handlers] as [T, ReadonlyArray<() => void>]; } const PACKS_PAGE_SIZE = 7; const PACK_ICON_WIDTH = 32; const PACK_PAGE_WIDTH = PACKS_PAGE_SIZE * PACK_ICON_WIDTH; function getPacksPageOffset(page: number, packs: number): number { if (page === 0) { return 0; } if (isLastPacksPage(page, packs)) { return ( PACK_PAGE_WIDTH * (Math.floor(packs / PACKS_PAGE_SIZE) - 1) + ((packs % PACKS_PAGE_SIZE) - 1) * PACK_ICON_WIDTH ); } return page * PACK_ICON_WIDTH * PACKS_PAGE_SIZE; } function isLastPacksPage(page: number, packs: number): boolean { return page === Math.floor(packs / PACKS_PAGE_SIZE); } export const StickerPicker = React.memo( React.forwardRef( ( { i18n, packs, recentStickers, onClose, onClickAddPack, onPickSticker, showPickerHint, style, }: Props, ref ) => { const focusRef = React.useRef(null); const tabIds = React.useMemo( () => ['recents', ...packs.map(({ id }) => id)], [packs] ); const [currentTab, [recentsHandler, ...packsHandlers]] = useTabs( tabIds, // If there are no recent stickers, // default to the first sticker pack, // unless there are no sticker packs. tabIds[recentStickers.length > 0 ? 0 : Math.min(1, tabIds.length)] ); const selectedPack = packs.find(({ id }) => id === currentTab); const { stickers = recentStickers, title: packTitle = 'Recent Stickers', } = selectedPack || {}; const [isUsingKeyboard, setIsUsingKeyboard] = React.useState(false); const [packsPage, setPacksPage] = React.useState(0); const onClickPrevPackPage = React.useCallback(() => { setPacksPage(i => i - 1); }, [setPacksPage]); const onClickNextPackPage = React.useCallback(() => { setPacksPage(i => i + 1); }, [setPacksPage]); // Handle escape key React.useEffect(() => { const handler = (event: KeyboardEvent) => { if (event.key === 'Tab') { // We do NOT prevent default here to allow Tab to be used normally setIsUsingKeyboard(true); return; } if (event.key === 'Escape') { event.stopPropagation(); event.preventDefault(); onClose(); } }; document.addEventListener('keydown', handler); return () => { document.removeEventListener('keydown', handler); }; }, [onClose]); // Focus popup on after initial render, restore focus on teardown useRestoreFocus(focusRef); const isEmpty = stickers.length === 0; const addPackRef = isEmpty ? focusRef : undefined; const downloadError = selectedPack && selectedPack.status === 'error' && selectedPack.stickerCount !== selectedPack.stickers.length; const pendingCount = selectedPack && selectedPack.status === 'pending' ? selectedPack.stickerCount - stickers.length : 0; const hasPacks = packs.length > 0; const isRecents = hasPacks && currentTab === 'recents'; const showPendingText = pendingCount > 0; const showDownlaodErrorText = downloadError; const showEmptyText = !downloadError && isEmpty; const showText = showPendingText || showDownlaodErrorText || showEmptyText; const showLongText = showPickerHint; return (
{hasPacks ? ( ))}
{!isUsingKeyboard && packsPage > 0 ? (
{showPickerHint ? (
{i18n('stickers--StickerPicker--Hint')}
) : null} {!hasPacks ? (
{i18n('stickers--StickerPicker--NoPacks')}
) : null} {pendingCount > 0 ? (
{i18n('stickers--StickerPicker--DownloadPending')}
) : null} {downloadError ? (
{stickers.length > 0 ? i18n('stickers--StickerPicker--DownloadError') : i18n('stickers--StickerPicker--Empty')}
) : null} {hasPacks && showEmptyText ? (
{isRecents ? i18n('stickers--StickerPicker--NoRecents') : i18n('stickers--StickerPicker--Empty')}
) : null} {!isEmpty ? (
{stickers.map(({ packId, id, url }, index: number) => { const maybeFocusRef = index === 0 ? focusRef : undefined; return ( ); })} {Array(pendingCount) .fill(0) .map((_, i) => (
))}
) : null}
); } ) );