mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 07:02:27 -05:00 
			
		
		
		
	
		
			
	
	
		
			159 lines
		
	
	
	
		
			4.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			159 lines
		
	
	
	
		
			4.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | /* | ||
|  | 	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 }) | ||
|  | 	); | ||
|  | }; |