signal-desktop/ts/state/ducks/items.ts

344 lines
7.5 KiB
TypeScript
Raw Normal View History

2023-01-03 19:55:46 +00:00
// Copyright 2019 Signal Messenger, LLC
2020-10-30 20:34:04 +00:00
// SPDX-License-Identifier: AGPL-3.0-only
import { omit } from 'lodash';
2021-05-28 16:15:17 +00:00
import { v4 as getGuid } from 'uuid';
import type { ThunkAction } from 'redux-thunk';
import type { ReadonlyDeep } from 'type-fest';
import type { StateType as RootStateType } from '../reducer';
import * as storageShim from '../../shims/storage';
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
2021-09-17 22:24:21 +00:00
import { useBoundActions } from '../../hooks/useBoundActions';
import { drop } from '../../util/drop';
import type {
2021-06-02 21:05:09 +00:00
ConversationColorType,
CustomColorType,
} from '../../types/Colors';
import { ConversationColors } from '../../types/Colors';
2021-06-02 21:05:09 +00:00
import { reloadSelectedConversation } from '../../shims/reloadSelectedConversation';
import type { StorageAccessType } from '../../types/Storage.d';
2021-08-18 20:08:14 +00:00
import { actions as conversationActions } from './conversations';
import type { ConfigMapType as RemoteConfigType } from '../../RemoteConfig';
// State
2023-07-20 03:14:08 +00:00
export type ItemsStateType = ReadonlyDeep<
{
[key: string]: unknown;
remoteConfig?: RemoteConfigType;
serverTimeSkew?: number;
} & Partial<
Pick<
StorageAccessType,
| 'universalExpireTimer'
| 'defaultConversationColor'
| 'customColors'
| 'preferredLeftPaneWidth'
2023-08-09 00:53:06 +00:00
| 'navTabsCollapsed'
2023-07-20 03:14:08 +00:00
| 'preferredReactionEmoji'
| 'areWeASubscriber'
| 'usernameLinkColor'
| 'usernameLink'
>
>
>;
// Actions
type ItemPutAction = ReadonlyDeep<{
type: 'items/PUT';
payload: null;
}>;
type ItemPutExternalAction = ReadonlyDeep<{
type: 'items/PUT_EXTERNAL';
payload: {
key: string;
value: unknown;
};
}>;
type ItemRemoveAction = ReadonlyDeep<{
type: 'items/REMOVE';
payload: null;
}>;
type ItemRemoveExternalAction = ReadonlyDeep<{
type: 'items/REMOVE_EXTERNAL';
payload: string;
}>;
type ItemsResetAction = ReadonlyDeep<{
type: 'items/RESET';
}>;
export type ItemsActionType = ReadonlyDeep<
| ItemPutAction
| ItemPutExternalAction
| ItemRemoveAction
| ItemRemoveExternalAction
| ItemsResetAction
>;
// Action Creators
export const actions = {
2021-05-28 16:15:17 +00:00
addCustomColor,
editCustomColor,
markHasCompletedSafetyNumberOnboarding,
2021-05-28 16:15:17 +00:00
removeCustomColor,
2021-06-02 21:05:09 +00:00
resetDefaultChatColor,
savePreferredLeftPaneWidth,
2021-06-02 21:05:09 +00:00
setGlobalDefaultConversationColor,
2023-08-09 00:53:06 +00:00
toggleNavTabsCollapse,
2021-04-27 22:35:35 +00:00
onSetSkinTone,
putItem,
putItemExternal,
removeItem,
removeItemExternal,
resetItems,
};
2023-08-09 00:53:06 +00:00
export const useItemsActions = (): BoundActionCreatorsMapObject<
typeof actions
> => useBoundActions(actions);
function putItem<K extends keyof StorageAccessType>(
key: K,
value: StorageAccessType[K]
): ThunkAction<void, RootStateType, unknown, ItemPutAction> {
return async dispatch => {
dispatch({
type: 'items/PUT',
payload: null,
});
await storageShim.put(key, value);
};
}
function onSetSkinTone(
tone: number
): ThunkAction<void, RootStateType, unknown, ItemPutAction> {
2021-04-27 22:35:35 +00:00
return putItem('skinTone', tone);
}
function putItemExternal(key: string, value: unknown): ItemPutExternalAction {
return {
type: 'items/PUT_EXTERNAL',
payload: {
key,
value,
},
};
}
function removeItem(key: keyof StorageAccessType): ItemRemoveAction {
drop(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' };
}
2021-05-28 16:15:17 +00:00
function getDefaultCustomColorData() {
return {
colors: {} as Record<string, CustomColorType>,
2021-05-28 16:15:17 +00:00
version: 1,
};
}
function addCustomColor(
2021-06-03 21:34:36 +00:00
customColor: CustomColorType,
2021-08-18 20:08:14 +00:00
conversationId?: string
2021-05-28 16:15:17 +00:00
): 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,
2021-06-03 21:34:36 +00:00
[uuid]: customColor,
2021-05-28 16:15:17 +00:00
},
};
dispatch(putItem('customColors', nextCustomColors));
2021-08-18 20:08:14 +00:00
const customColorData = {
id: uuid,
value: customColor,
};
if (conversationId) {
conversationActions.colorSelected({
conversationId,
conversationColor: 'custom',
customColorData,
})(dispatch, getState, null);
} else {
setGlobalDefaultConversationColor('custom', customColorData)(
dispatch,
getState,
null
);
}
2021-05-28 16:15:17 +00:00
};
}
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));
resetDefaultChatColor()(dispatch, getState, null);
2021-05-28 16:15:17 +00:00
};
}
2021-06-02 21:05:09 +00:00
function resetDefaultChatColor(): ThunkAction<
void,
RootStateType,
unknown,
ItemPutAction
> {
return dispatch => {
dispatch(
putItem('defaultConversationColor', {
color: ConversationColors[0],
})
);
reloadSelectedConversation();
};
}
function setGlobalDefaultConversationColor(
color: ConversationColorType,
customColorData?: {
id: string;
value: CustomColorType;
}
): ThunkAction<void, RootStateType, unknown, ItemPutAction> {
return dispatch => {
dispatch(
putItem('defaultConversationColor', {
color,
customColorData,
})
);
reloadSelectedConversation();
};
}
function savePreferredLeftPaneWidth(
preferredWidth: number
): ThunkAction<void, RootStateType, unknown, ItemPutAction> {
return dispatch => {
dispatch(putItem('preferredLeftPaneWidth', preferredWidth));
};
}
function markHasCompletedSafetyNumberOnboarding(): ThunkAction<
void,
RootStateType,
unknown,
ItemPutAction
> {
return dispatch => {
dispatch(putItem('hasCompletedSafetyNumberOnboarding', true));
};
}
2023-08-09 00:53:06 +00:00
function toggleNavTabsCollapse(
navTabsCollapsed: boolean
): ThunkAction<void, RootStateType, unknown, ItemPutAction> {
return dispatch => {
dispatch(putItem('navTabsCollapsed', navTabsCollapsed));
};
}
// Reducer
export function getEmptyState(): ItemsStateType {
2021-06-02 21:05:09 +00:00
return {
defaultConversationColor: {
color: ConversationColors[0],
},
};
}
export function reducer(
state: Readonly<ItemsStateType> = getEmptyState(),
action: Readonly<ItemsActionType>
): ItemsStateType {
if (action.type === 'items/PUT_EXTERNAL') {
const { payload } = action;
2022-12-19 22:33:55 +00:00
if (state[payload.key] === payload.value) {
return state;
}
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;
}