signal-desktop/ts/state/ducks/items.ts
2021-06-01 13:45:43 -07:00

217 lines
4.3 KiB
TypeScript

// 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 universalExpireTimer?: number;
readonly [key: string]: unknown;
readonly customColors?: {
readonly colors: Record<string, CustomColorType>;
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<void, RootStateType, unknown, ItemPutAction> {
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<void, RootStateType, unknown, ItemPutAction> {
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<void, RootStateType, unknown, ItemPutAction> {
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<ItemsStateType> = getEmptyState(),
action: Readonly<ItemsActionType>
): 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;
}