Signal-Desktop/ts/state/ducks/gifs.ts

114 lines
2.8 KiB
TypeScript

// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { ReadonlyDeep } from 'type-fest';
import type { ThunkAction } from 'redux-thunk';
import { take } from 'lodash';
import type { GifType } from '../../components/fun/panels/FunPanelGifs';
import { DataWriter } from '../../sql/Client';
import {
type BoundActionCreatorsMapObject,
useBoundActions,
} from '../../hooks/useBoundActions';
const { addRecentGif, removeRecentGif } = DataWriter;
export const MAX_RECENT_GIFS = 64;
type RecentGifs = ReadonlyDeep<Array<GifType>>;
// State
export type GifsStateType = ReadonlyDeep<{
recentGifs: RecentGifs;
}>;
// Actions
const GIFS_RECENT_GIFS_ADD = 'gifs/RECENT_GIFS_ADD';
const GIFS_RECENT_GIFS_REMOVE = 'gifs/RECENT_GIFS_REMOVE';
export type GifsRecentGifsAdd = ReadonlyDeep<{
type: typeof GIFS_RECENT_GIFS_ADD;
payload: GifType;
}>;
export type GifsRecentGifsRemove = ReadonlyDeep<{
type: typeof GIFS_RECENT_GIFS_REMOVE;
payload: Pick<GifType, 'id'>;
}>;
type GifsActionType = ReadonlyDeep<GifsRecentGifsAdd | GifsRecentGifsRemove>;
// Action Creators
export const actions = {
onAddRecentGif,
onRemoveRecentGif,
};
export const useGifsActions = (): BoundActionCreatorsMapObject<
typeof actions
> => useBoundActions(actions);
function onAddRecentGif(
payload: GifType
): ThunkAction<void, unknown, unknown, GifsRecentGifsAdd> {
return async dispatch => {
await addRecentGif(payload, Date.now(), MAX_RECENT_GIFS);
dispatch({ type: GIFS_RECENT_GIFS_ADD, payload });
};
}
function onRemoveRecentGif(
payload: Pick<GifType, 'id'>
): ThunkAction<void, unknown, unknown, GifsRecentGifsRemove> {
return async dispatch => {
await removeRecentGif(payload);
dispatch({ type: GIFS_RECENT_GIFS_REMOVE, payload });
};
}
function filterRecentGif(
prev: ReadonlyArray<GifType>,
gifId: GifType['id']
): ReadonlyArray<GifType> {
return prev.filter(gif => gif.id !== gifId);
}
function addOrMoveRecentGifToStart(prev: RecentGifs, gif: GifType): RecentGifs {
// Make sure there isn't a duplicate item in the array
const filtered = filterRecentGif(prev, gif.id);
// Make sure final array isn't too long
const limited = take(filtered, MAX_RECENT_GIFS - 1);
return [gif, ...limited];
}
// Reducer
export function getEmptyState(): GifsStateType {
return { recentGifs: [] };
}
export function reducer(
state: GifsStateType = getEmptyState(),
action: GifsActionType
): GifsStateType {
if (action.type === GIFS_RECENT_GIFS_ADD) {
const { payload } = action;
return {
...state,
recentGifs: addOrMoveRecentGifToStart(state.recentGifs, payload),
};
}
if (action.type === GIFS_RECENT_GIFS_REMOVE) {
const { payload } = action;
return {
...state,
recentGifs: filterRecentGif(state.recentGifs, payload.id),
};
}
return state;
}