summaryrefslogtreecommitdiff
path: root/web/source/settings/lib/form/check-list.tsx
diff options
context:
space:
mode:
authorLibravatar tobi <31960611+tsmethurst@users.noreply.github.com>2023-10-17 12:46:06 +0200
committerLibravatar GitHub <noreply@github.com>2023-10-17 12:46:06 +0200
commit637f188ebec71fe4b0b80bbab4592d4c269d7d93 (patch)
tree6e1136dee4d854af021e0a571a67038d32083e4b /web/source/settings/lib/form/check-list.tsx
parent[chore]: Bump github.com/microcosm-cc/bluemonday from 1.0.25 to 1.0.26 (#2266) (diff)
downloadgotosocial-637f188ebec71fe4b0b80bbab4592d4c269d7d93.tar.xz
[feature] Allow import/export/creation of domain allows via admin panel (#2264)v0.12.0-rc1
* it's happening! * aaa * fix silly whoopsie * it's working pa! it's working ma! * model report parameters * shuffle some more stuff around * getting there * oo hoo * finish tidying up for now * aaa * fix use form submit errors * peepee poo poo * aaaaa * ffff * they see me typin', they hatin' * boop * aaa * oooo * typing typing tappa tappa * almost done typing * weee * alright * push it push it real good doo doo doo doo doo doo * thingy no worky * almost done * mutation modifers not quite right * hmm * it works * view blocks + allows nicely * it works! * typia install * the old linterino * linter plz
Diffstat (limited to 'web/source/settings/lib/form/check-list.tsx')
-rw-r--r--web/source/settings/lib/form/check-list.tsx219
1 files changed, 219 insertions, 0 deletions
diff --git a/web/source/settings/lib/form/check-list.tsx b/web/source/settings/lib/form/check-list.tsx
new file mode 100644
index 000000000..c08e5022f
--- /dev/null
+++ b/web/source/settings/lib/form/check-list.tsx
@@ -0,0 +1,219 @@
+/*
+ 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 {
+ useReducer,
+ useRef,
+ useEffect,
+ useCallback,
+ useMemo,
+} from "react";
+
+import { PayloadAction, createSlice } from "@reduxjs/toolkit";
+
+import type {
+ Checkable,
+ ChecklistInputHook,
+ CreateHookNames,
+ HookOpts,
+} from "./types";
+
+// https://immerjs.github.io/immer/installation#pick-your-immer-version
+import { enableMapSet } from "immer";
+enableMapSet();
+
+interface ChecklistState {
+ entries: { [k: string]: Checkable },
+ selectedEntries: Set<string>,
+}
+
+const initialState: ChecklistState = {
+ entries: {},
+ selectedEntries: new Set(),
+};
+
+const { reducer, actions } = 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: Checkable}>) => {
+ if (value.checked !== undefined) {
+ if (value.checked === true) {
+ state.selectedEntries.add(key);
+ } else {
+ state.selectedEntries.delete(key);
+ }
+ }
+
+ state.entries[key] = {
+ ...state.entries[key],
+ ...value
+ };
+ },
+ updateMultiple: (state, { payload }: PayloadAction<Array<[key: string, value: Checkable]>>) => {
+ payload.forEach(([key, value]) => {
+ if (value.checked !== undefined) {
+ if (value.checked === true) {
+ state.selectedEntries.add(key);
+ } else {
+ state.selectedEntries.delete(key);
+ }
+ }
+
+ state.entries[key] = {
+ ...state.entries[key],
+ ...value
+ };
+ });
+ }
+ }
+});
+
+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 _default: { [k: string]: Checkable } = {};
+
+export default function useCheckListInput(
+ /* eslint-disable no-unused-vars */
+ { name, Name }: CreateHookNames,
+ {
+ entries = [],
+ uniqueKey = "key",
+ initialValue = false,
+ }: HookOpts<boolean>
+): ChecklistInputHook {
+ const [state, dispatch] = useReducer(
+ reducer,
+ initialState,
+ (_) => initialHookState({ entries, uniqueKey, initialValue }) // initial state
+ );
+
+ const toggleAllRef = useRef<any>(null);
+
+ useEffect(() => {
+ if (toggleAllRef.current != null) {
+ let some = state.selectedEntries.size > 0;
+ let all = false;
+ if (some) {
+ all = state.selectedEntries.size == Object.values(state.entries).length;
+ }
+ toggleAllRef.current.checked = all;
+ toggleAllRef.current.indeterminate = some && !all;
+ }
+ // only needs to update when state.selectedEntries changes, not state.entries
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [state.selectedEntries]);
+
+ const reset = useCallback(
+ () => dispatch(actions.updateAll(initialValue)),
+ [initialValue]
+ );
+
+ const onChange = useCallback(
+ (key, value) => dispatch(actions.update({ key, value })),
+ []
+ );
+
+ const updateMultiple = useCallback(
+ (entries) => dispatch(actions.updateMultiple(entries)),
+ []
+ );
+
+ return useMemo(() => {
+ function toggleAll(e) {
+ let checked = e.target.checked;
+ if (e.target.indeterminate) {
+ checked = false;
+ }
+ dispatch(actions.updateAll(checked));
+ }
+
+ function selectedValues() {
+ return Array.from((state.selectedEntries)).map((key) => ({
+ ...state.entries[key] // returned as new object, because reducer state is immutable
+ }));
+ }
+
+ return Object.assign([
+ state,
+ reset,
+ { name }
+ ], {
+ _default,
+ hasChanged: () => true,
+ name,
+ Name: "",
+ value: state.entries,
+ onChange,
+ selectedValues,
+ reset,
+ someSelected: state.selectedEntries.size > 0,
+ updateMultiple,
+ toggleAll: {
+ ref: toggleAllRef,
+ onChange: toggleAll
+ }
+ });
+ }, [state, reset, name, onChange, updateMultiple]);
+}