// Copyright 2019 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { join } from 'path'; import type { Dictionary } from 'lodash'; import { compact, filter, map, orderBy, reject, sortBy, values } from 'lodash'; import { createSelector } from 'reselect'; import type { RecentStickerType } from '../../types/Stickers'; import type { StickerType as StickerDBType, StickerPackType as StickerPackDBType, } from '../../sql/Interface'; import type { StateType } from '../reducer'; import type { StickersStateType, StickerPackType, StickerType, } from '../ducks/stickers'; import { getStickersPath, getTempPath } from './user'; const getSticker = ( packs: Dictionary, packId: string, stickerId: number, stickerPath: string, tempPath: string ): StickerType | undefined => { const pack = packs[packId]; if (!pack) { return undefined; } const sticker = pack.stickers[stickerId]; if (!sticker) { return undefined; } const isEphemeral = pack.status === 'ephemeral'; return translateStickerFromDB(sticker, stickerPath, tempPath, isEphemeral); }; const translateStickerFromDB = ( sticker: StickerDBType, stickerPath: string, tempPath: string, isEphemeral: boolean ): StickerType => { const { id, packId, emoji, path } = sticker; const prefix = isEphemeral ? tempPath : stickerPath; return { id, packId, emoji, url: join(prefix, path), }; }; export const translatePackFromDB = ( pack: StickerPackDBType, packs: Dictionary, blessedPacks: Dictionary, stickersPath: string, tempPath: string ): StickerPackType => { const { id, stickers, status, coverStickerId } = pack; const isEphemeral = status === 'ephemeral'; // Sometimes sticker packs have a cover which isn't included in their set of stickers. // We don't want to show cover-only images when previewing or picking from a pack. const filteredStickers = reject( values(stickers), sticker => sticker.isCoverOnly ); const translatedStickers = map(filteredStickers, sticker => translateStickerFromDB(sticker, stickersPath, tempPath, isEphemeral) ); return { ...pack, isBlessed: Boolean(blessedPacks[id]), cover: getSticker(packs, id, coverStickerId, stickersPath, tempPath), stickers: sortBy(translatedStickers, sticker => sticker.id), }; }; const filterAndTransformPacks = ( packs: Dictionary, packFilter: (sticker: StickerPackDBType) => boolean, packSort: (sticker: StickerPackDBType) => number | undefined, blessedPacks: Dictionary, stickersPath: string, tempPath: string ): Array => { const list = filter(packs, packFilter); const sorted = orderBy(list, packSort, ['desc']); return sorted.map(pack => translatePackFromDB(pack, packs, blessedPacks, stickersPath, tempPath) ); }; const getStickers = (state: StateType) => state.stickers; export const getPacks = createSelector( getStickers, (stickers: StickersStateType) => stickers.packs ); const getRecents = createSelector( getStickers, (stickers: StickersStateType) => stickers.recentStickers ); export const getBlessedPacks = createSelector( getStickers, (stickers: StickersStateType) => stickers.blessedPacks ); export const getRecentStickers = createSelector( getRecents, getPacks, getStickersPath, getTempPath, ( recents: ReadonlyArray, packs: Dictionary, stickersPath: string, tempPath: string ) => { return compact( recents.map(({ packId, stickerId }) => { return getSticker(packs, packId, stickerId, stickersPath, tempPath); }) ); } ); export const getInstalledStickerPacks = createSelector( getPacks, getBlessedPacks, getStickersPath, getTempPath, ( packs: Dictionary, blessedPacks: Dictionary, stickersPath: string, tempPath: string ): Array => { return filterAndTransformPacks( packs, pack => pack.status === 'installed', pack => pack.installedAt, blessedPacks, stickersPath, tempPath ); } ); export const getRecentlyInstalledStickerPack = createSelector( getInstalledStickerPacks, getStickers, (packs, { installedPack: packId }) => { if (!packId) { return null; } return packs.find(({ id }) => id === packId) || null; } ); export const getReceivedStickerPacks = createSelector( getPacks, getBlessedPacks, getStickersPath, getTempPath, ( packs: Dictionary, blessedPacks: Dictionary, stickersPath: string, tempPath: string ): Array => { return filterAndTransformPacks( packs, pack => (pack.status === 'downloaded' || pack.status === 'pending') && !blessedPacks[pack.id], pack => pack.createdAt, blessedPacks, stickersPath, tempPath ); } ); export const getBlessedStickerPacks = createSelector( getPacks, getBlessedPacks, getStickersPath, getTempPath, ( packs: Dictionary, blessedPacks: Dictionary, stickersPath: string, tempPath: string ): Array => { return filterAndTransformPacks( packs, pack => blessedPacks[pack.id] && pack.status !== 'installed', pack => pack.createdAt, blessedPacks, stickersPath, tempPath ); } ); export const getKnownStickerPacks = createSelector( getPacks, getBlessedPacks, getStickersPath, getTempPath, ( packs: Dictionary, blessedPacks: Dictionary, stickersPath: string, tempPath: string ): Array => { return filterAndTransformPacks( packs, pack => !blessedPacks[pack.id] && pack.status === 'known', pack => pack.createdAt, blessedPacks, stickersPath, tempPath ); } );