// Copyright 2019-2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { omit } from 'lodash'; import { v4 as getGuid } from 'uuid'; import { ThunkAction } from 'redux-thunk'; import { StateType as RootStateType } from '../reducer'; import * as storageShim from '../../shims/storage'; import { useBoundActions } from '../../util/hooks'; import { CustomColorType } from '../../types/Colors'; // State export type ItemsStateType = { readonly [key: string]: unknown; readonly customColors?: { readonly colors: Record; readonly version: number; }; }; // Actions type ItemPutAction = { type: 'items/PUT'; payload: null; }; type ItemPutExternalAction = { type: 'items/PUT_EXTERNAL'; payload: { key: string; value: unknown; }; }; type ItemRemoveAction = { type: 'items/REMOVE'; payload: null; }; type ItemRemoveExternalAction = { type: 'items/REMOVE_EXTERNAL'; payload: string; }; type ItemsResetAction = { type: 'items/RESET'; }; export type ItemsActionType = | ItemPutAction | ItemPutExternalAction | ItemRemoveAction | ItemRemoveExternalAction | ItemsResetAction; // Action Creators export const actions = { addCustomColor, editCustomColor, removeCustomColor, onSetSkinTone, putItem, putItemExternal, removeItem, removeItemExternal, resetItems, }; export const useActions = (): typeof actions => useBoundActions(actions); function putItem(key: string, value: unknown): ItemPutAction { storageShim.put(key, value); return { type: 'items/PUT', payload: null, }; } function onSetSkinTone(tone: number): ItemPutAction { return putItem('skinTone', tone); } function putItemExternal(key: string, value: unknown): ItemPutExternalAction { return { type: 'items/PUT_EXTERNAL', payload: { key, value, }, }; } function removeItem(key: string): ItemRemoveAction { storageShim.remove(key); return { type: 'items/REMOVE', payload: null, }; } function removeItemExternal(key: string): ItemRemoveExternalAction { return { type: 'items/REMOVE_EXTERNAL', payload: key, }; } function resetItems(): ItemsResetAction { return { type: 'items/RESET' }; } function getDefaultCustomColorData() { return { colors: {}, version: 1, }; } function addCustomColor( payload: CustomColorType ): ThunkAction { return (dispatch, getState) => { const { customColors = getDefaultCustomColorData() } = getState().items; let uuid = getGuid(); while (customColors.colors[uuid]) { uuid = getGuid(); } const nextCustomColors = { ...customColors, colors: { ...customColors.colors, [uuid]: payload, }, }; dispatch(putItem('customColors', nextCustomColors)); }; } function editCustomColor( colorId: string, color: CustomColorType ): ThunkAction { return (dispatch, getState) => { const { customColors = getDefaultCustomColorData() } = getState().items; if (!customColors.colors[colorId]) { return; } const nextCustomColors = { ...customColors, colors: { ...customColors.colors, [colorId]: color, }, }; dispatch(putItem('customColors', nextCustomColors)); }; } function removeCustomColor( payload: string ): ThunkAction { return (dispatch, getState) => { const { customColors = getDefaultCustomColorData() } = getState().items; const nextCustomColors = { ...customColors, colors: omit(customColors.colors, payload), }; dispatch(putItem('customColors', nextCustomColors)); }; } // Reducer function getEmptyState(): ItemsStateType { return {}; } export function reducer( state: Readonly = getEmptyState(), action: Readonly ): ItemsStateType { if (action.type === 'items/PUT_EXTERNAL') { const { payload } = action; return { ...state, [payload.key]: payload.value, }; } if (action.type === 'items/REMOVE_EXTERNAL') { const { payload } = action; return omit(state, payload); } if (action.type === 'items/RESET') { return getEmptyState(); } return state; }