summaryrefslogtreecommitdiff
path: root/web/source/settings/redux/checklist.ts
diff options
context:
space:
mode:
Diffstat (limited to 'web/source/settings/redux/checklist.ts')
-rw-r--r--web/source/settings/redux/checklist.ts158
1 files changed, 158 insertions, 0 deletions
diff --git a/web/source/settings/redux/checklist.ts b/web/source/settings/redux/checklist.ts
new file mode 100644
index 000000000..d99f749ba
--- /dev/null
+++ b/web/source/settings/redux/checklist.ts
@@ -0,0 +1,158 @@
+/*
+ GoToSocial
+ Copyright (C) GoToSocial Authors admin@gotosocial.org
+ SPDX-License-Identifier: AGPL-3.0-or-later
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import { PayloadAction, createSlice } from "@reduxjs/toolkit";
+import type { Checkable } from "../lib/form/types";
+import { useReducer } from "react";
+
+// https://immerjs.github.io/immer/installation#pick-your-immer-version
+import { enableMapSet } from "immer";
+enableMapSet();
+
+export interface ChecklistState {
+ entries: { [k: string]: Checkable },
+ selectedEntries: Set<string>,
+}
+
+const initialState: ChecklistState = {
+ entries: {},
+ selectedEntries: new Set(),
+};
+
+function initialHookState({
+ entries,
+ uniqueKey,
+ initialValue,
+}: {
+ entries: Checkable[],
+ uniqueKey: string,
+ initialValue: boolean,
+}): ChecklistState {
+ const selectedEntries = new Set<string>();
+ const mappedEntries = Object.fromEntries(
+ entries.map((entry) => {
+ const key = entry[uniqueKey];
+ const checked = entry.checked ?? initialValue;
+
+ if (checked) {
+ selectedEntries.add(key);
+ } else {
+ selectedEntries.delete(key);
+ }
+
+ return [ key, { ...entry, key, checked } ];
+ })
+ );
+
+ return {
+ entries: mappedEntries,
+ selectedEntries
+ };
+}
+
+const checklistSlice = createSlice({
+ name: "checklist",
+ initialState, // not handled by slice itself
+ reducers: {
+ updateAll: (state, { payload: checked }: PayloadAction<boolean>) => {
+ const selectedEntries = new Set<string>();
+ const entries = Object.fromEntries(
+ Object.values(state.entries).map((entry) => {
+ if (checked) {
+ // Cheekily add this to selected
+ // entries while we're here.
+ selectedEntries.add(entry.key);
+ }
+
+ return [entry.key, { ...entry, checked } ];
+ })
+ );
+
+ return { entries, selectedEntries };
+ },
+ update: (state, { payload: { key, value } }: PayloadAction<{key: string, value: Partial<Checkable>}>) => {
+ if (value.checked !== undefined) {
+ if (value.checked) {
+ state.selectedEntries.add(key);
+ } else {
+ state.selectedEntries.delete(key);
+ }
+ }
+
+ state.entries[key] = {
+ ...state.entries[key],
+ ...value
+ };
+ },
+ updateMultiple: (state, { payload }: PayloadAction<Array<[key: string, value: Partial<Checkable>]>>) => {
+ payload.forEach(([ key, value ]) => {
+ if (value.checked !== undefined) {
+ if (value.checked) {
+ state.selectedEntries.add(key);
+ } else {
+ state.selectedEntries.delete(key);
+ }
+ }
+
+ state.entries[key] = {
+ ...state.entries[key],
+ ...value
+ };
+ });
+ }
+ }
+});
+
+export const actions = checklistSlice.actions;
+
+/**
+ * useChecklistReducer wraps the react 'useReducer'
+ * hook with logic specific to the checklist reducer.
+ *
+ * Use it in components where you need to keep track
+ * of checklist state.
+ *
+ * To update it, use dispatch with the actions
+ * exported from this module.
+ *
+ * @example
+ *
+ * ```javascript
+ * // Start with one entry with "checked" set to "false".
+ * const initialEntries = [{ key: "some_key", id: "some_id", value: "some_value", checked: false }];
+ * const [state, dispatch] = useChecklistReducer(initialEntries, "id", false);
+ *
+ * // Dispatch an action to set "checked" of all entries to "true".
+ * let checked = true;
+ * dispatch(actions.updateAll(checked));
+ *
+ * // Will log `["some_id"]`
+ * console.log(state.selectedEntries)
+ *
+ * // Will log `{ key: "some_key", id: "some_id", value: "some_value", checked: true }`
+ * console.log(state.entries["some_id"])
+ * ```
+ */
+export const useChecklistReducer = (entries: Checkable[], uniqueKey: string, initialValue: boolean) => {
+ return useReducer(
+ checklistSlice.reducer,
+ initialState,
+ (_) => initialHookState({ entries, uniqueKey, initialValue })
+ );
+};