gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

check-list.jsx (4534B)


      1 /*
      2 	GoToSocial
      3 	Copyright (C) GoToSocial Authors admin@gotosocial.org
      4 	SPDX-License-Identifier: AGPL-3.0-or-later
      5 
      6 	This program is free software: you can redistribute it and/or modify
      7 	it under the terms of the GNU Affero General Public License as published by
      8 	the Free Software Foundation, either version 3 of the License, or
      9 	(at your option) any later version.
     10 
     11 	This program is distributed in the hope that it will be useful,
     12 	but WITHOUT ANY WARRANTY; without even the implied warranty of
     13 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14 	GNU Affero General Public License for more details.
     15 
     16 	You should have received a copy of the GNU Affero General Public License
     17 	along with this program.  If not, see <http://www.gnu.org/licenses/>.
     18 */
     19 
     20 "use strict";
     21 
     22 const React = require("react");
     23 const syncpipe = require("syncpipe");
     24 const { createSlice } = require("@reduxjs/toolkit");
     25 const { enableMapSet } = require("immer");
     26 
     27 enableMapSet(); // for use in reducers
     28 
     29 const { reducer, actions } = createSlice({
     30 	name: "checklist",
     31 	initialState: {}, // not handled by slice itself
     32 	reducers: {
     33 		updateAll: (state, { payload: checked }) => {
     34 			const selectedEntries = new Set();
     35 			return {
     36 				entries: syncpipe(state.entries, [
     37 					(_) => Object.values(_),
     38 					(_) => _.map((entry) => {
     39 						if (checked) {
     40 							selectedEntries.add(entry.key);
     41 						}
     42 						return [entry.key, {
     43 							...entry,
     44 							checked
     45 						}];
     46 					}),
     47 					(_) => Object.fromEntries(_)
     48 				]),
     49 				selectedEntries
     50 			};
     51 		},
     52 		update: (state, { payload: { key, value } }) => {
     53 			if (value.checked !== undefined) {
     54 				if (value.checked === true) {
     55 					state.selectedEntries.add(key);
     56 				} else {
     57 					state.selectedEntries.delete(key);
     58 				}
     59 			}
     60 
     61 			state.entries[key] = {
     62 				...state.entries[key],
     63 				...value
     64 			};
     65 		},
     66 		updateMultiple: (state, { payload }) => {
     67 			payload.forEach(([key, value]) => {
     68 				if (value.checked !== undefined) {
     69 					if (value.checked === true) {
     70 						state.selectedEntries.add(key);
     71 					} else {
     72 						state.selectedEntries.delete(key);
     73 					}
     74 				}
     75 
     76 				state.entries[key] = {
     77 					...state.entries[key],
     78 					...value
     79 				};
     80 			});
     81 		}
     82 	}
     83 });
     84 
     85 function initialState({ entries, uniqueKey, initialValue }) {
     86 	const selectedEntries = new Set();
     87 	return {
     88 		entries: syncpipe(entries, [
     89 			(_) => _.map((entry) => {
     90 				let key = entry[uniqueKey];
     91 				let checked = entry.checked ?? initialValue;
     92 
     93 				if (checked) {
     94 					selectedEntries.add(key);
     95 				} else {
     96 					selectedEntries.delete(key);
     97 				}
     98 
     99 				return [
    100 					key,
    101 					{
    102 						...entry,
    103 						key,
    104 						checked
    105 					}
    106 				];
    107 			}),
    108 			(_) => Object.fromEntries(_)
    109 		]),
    110 		selectedEntries
    111 	};
    112 }
    113 
    114 module.exports = function useCheckListInput({ name }, { entries, uniqueKey = "key", initialValue = false }) {
    115 	const [state, dispatch] = React.useReducer(reducer, null,
    116 		() => initialState({ entries, uniqueKey, initialValue }) // initial state
    117 	);
    118 
    119 	const toggleAllRef = React.useRef(null);
    120 
    121 	React.useEffect(() => {
    122 		if (toggleAllRef.current != null) {
    123 			let some = state.selectedEntries.size > 0;
    124 			let all = false;
    125 			if (some) {
    126 				all = state.selectedEntries.size == Object.values(state.entries).length;
    127 			}
    128 			toggleAllRef.current.checked = all;
    129 			toggleAllRef.current.indeterminate = some && !all;
    130 		}
    131 		// only needs to update when state.selectedEntries changes, not state.entries
    132 		// eslint-disable-next-line react-hooks/exhaustive-deps
    133 	}, [state.selectedEntries]);
    134 
    135 	const reset = React.useCallback(
    136 		() => dispatch(actions.updateAll(initialValue)),
    137 		[initialValue]
    138 	);
    139 
    140 	const onChange = React.useCallback(
    141 		(key, value) => dispatch(actions.update({ key, value })),
    142 		[]
    143 	);
    144 
    145 	const updateMultiple = React.useCallback(
    146 		(entries) => dispatch(actions.updateMultiple(entries)),
    147 		[]
    148 	);
    149 
    150 	return React.useMemo(() => {
    151 		function toggleAll(e) {
    152 			let checked = e.target.checked;
    153 			if (e.target.indeterminate) {
    154 				checked = false;
    155 			}
    156 			dispatch(actions.updateAll(checked));
    157 		}
    158 
    159 		function selectedValues() {
    160 			return Array.from((state.selectedEntries)).map((key) => ({
    161 				...state.entries[key] // returned as new object, because reducer state is immutable
    162 			}));
    163 		}
    164 
    165 		return Object.assign([
    166 			state,
    167 			reset,
    168 			{ name }
    169 		], {
    170 			name,
    171 			value: state.entries,
    172 			onChange,
    173 			selectedValues,
    174 			reset,
    175 			someSelected: state.selectedEntries.size > 0,
    176 			updateMultiple,
    177 			toggleAll: {
    178 				ref: toggleAllRef,
    179 				onChange: toggleAll
    180 			}
    181 		});
    182 	}, [state, reset, name, onChange, updateMultiple]);
    183 };