mirror of
1
Fork 0
gotosocial/web/source/settings/redux/checklist.ts

159 lines
4.2 KiB
TypeScript
Raw Normal View History

/*
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 })
);
};