From 74d2b4e0dc35c6f1d9689cb59d3932237ad8b460 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Fri, 24 Mar 2023 17:41:17 -0700 Subject: [PATCH] Extract useSelectState from SelectContext (cherry picked from commit 032d9a720c89286dc8c1931775144f0a65a6149e) --- frontend/src/App/SelectContext.tsx | 152 +++++------------- .../Index/Select/ArtistIndexPosterSelect.tsx | 4 +- .../Select/ArtistIndexSelectAllButton.tsx | 6 +- .../Select/ArtistIndexSelectAllMenuItem.tsx | 6 +- .../Index/Select/ArtistIndexSelectFooter.tsx | 4 +- .../Select/ArtistIndexSelectModeButton.tsx | 4 +- .../Select/ArtistIndexSelectModeMenuItem.tsx | 4 +- .../src/Artist/Index/Table/ArtistIndexRow.tsx | 4 +- .../Index/Table/ArtistIndexTableHeader.tsx | 4 +- 9 files changed, 52 insertions(+), 136 deletions(-) diff --git a/frontend/src/App/SelectContext.tsx b/frontend/src/App/SelectContext.tsx index 6980129c1..66be388ce 100644 --- a/frontend/src/App/SelectContext.tsx +++ b/frontend/src/App/SelectContext.tsx @@ -1,58 +1,28 @@ import { cloneDeep } from 'lodash'; -import React, { useEffect } from 'react'; -import areAllSelected from 'Utilities/Table/areAllSelected'; -import selectAll from 'Utilities/Table/selectAll'; -import toggleSelected from 'Utilities/Table/toggleSelected'; +import React, { useCallback, useEffect } from 'react'; +import useSelectState, { SelectState } from 'Helpers/Hooks/useSelectState'; import ModelBase from './ModelBase'; -export enum SelectActionType { - Reset, - SelectAll, - UnselectAll, - ToggleSelected, - RemoveItem, - UpdateItems, -} - -type SelectedState = Record; - -interface SelectState { - selectedState: SelectedState; - lastToggled: number | null; - allSelected: boolean; - allUnselected: boolean; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - items: any[]; -} - -type SelectAction = - | { type: SelectActionType.Reset } - | { type: SelectActionType.SelectAll } - | { type: SelectActionType.UnselectAll } +export type SelectContextAction = + | { type: 'reset' } + | { type: 'selectAll' } + | { type: 'unselectAll' } | { - type: SelectActionType.ToggleSelected; + type: 'toggleSelected'; id: number; isSelected: boolean; shiftKey: boolean; } | { - type: SelectActionType.RemoveItem; + type: 'removeItem'; id: number; } | { - type: SelectActionType.UpdateItems; + type: 'updateItems'; items: ModelBase[]; }; -type Dispatch = (action: SelectAction) => void; - -const initialState = { - selectedState: {}, - lastToggled: null, - allSelected: false, - allUnselected: true, - items: [], -}; +export type SelectDispatch = (action: SelectContextAction) => void; interface SelectProviderOptions { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -60,90 +30,40 @@ interface SelectProviderOptions { items: Array; } -function getSelectedState(items: ModelBase[], existingState: SelectedState) { - return items.reduce((acc: SelectedState, item) => { - const id = item.id; - - acc[id] = existingState[id] ?? false; - - return acc; - }, {}); -} - -// TODO: Can this be reused? - -const SelectContext = React.createContext<[SelectState, Dispatch] | undefined>( - cloneDeep(undefined) -); - -function selectReducer(state: SelectState, action: SelectAction): SelectState { - const { items, selectedState } = state; - - switch (action.type) { - case SelectActionType.Reset: { - return cloneDeep(initialState); - } - case SelectActionType.SelectAll: { - return { - items, - ...selectAll(selectedState, true), - }; - } - case SelectActionType.UnselectAll: { - return { - items, - ...selectAll(selectedState, false), - }; - } - case SelectActionType.ToggleSelected: { - const result = { - items, - ...toggleSelected( - state, - items, - action.id, - action.isSelected, - action.shiftKey - ), - }; - - return result; - } - case SelectActionType.UpdateItems: { - const nextSelectedState = getSelectedState(action.items, selectedState); - - return { - ...state, - ...areAllSelected(nextSelectedState), - selectedState: nextSelectedState, - items: action.items, - }; - } - default: { - throw new Error(`Unhandled action type: ${action.type}`); - } - } -} +const SelectContext = React.createContext< + [SelectState, SelectDispatch] | undefined +>(cloneDeep(undefined)); export function SelectProvider( props: SelectProviderOptions ) { const { items } = props; - const selectedState = getSelectedState(items, {}); - - const [state, dispatch] = React.useReducer(selectReducer, { - selectedState, - lastToggled: null, - allSelected: false, - allUnselected: true, - items, - }); + const [state, dispatch] = useSelectState(); + + const dispatchWrapper = useCallback( + (action: SelectContextAction) => { + switch (action.type) { + case 'reset': + case 'removeItem': + dispatch(action); + break; + + default: + dispatch({ + ...action, + items, + }); + break; + } + }, + [items, dispatch] + ); - const value: [SelectState, Dispatch] = [state, dispatch]; + const value: [SelectState, SelectDispatch] = [state, dispatchWrapper]; useEffect(() => { - dispatch({ type: SelectActionType.UpdateItems, items }); - }, [items]); + dispatch({ type: 'updateItems', items }); + }, [items, dispatch]); return ( diff --git a/frontend/src/Artist/Index/Select/ArtistIndexPosterSelect.tsx b/frontend/src/Artist/Index/Select/ArtistIndexPosterSelect.tsx index a97a00a85..804cb7ac7 100644 --- a/frontend/src/Artist/Index/Select/ArtistIndexPosterSelect.tsx +++ b/frontend/src/Artist/Index/Select/ArtistIndexPosterSelect.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import { SelectActionType, useSelect } from 'App/SelectContext'; +import { useSelect } from 'App/SelectContext'; import IconButton from 'Components/Link/IconButton'; import { icons } from 'Helpers/Props'; import styles from './ArtistIndexPosterSelect.css'; @@ -18,7 +18,7 @@ function ArtistIndexPosterSelect(props: ArtistIndexPosterSelectProps) { const shiftKey = event.nativeEvent.shiftKey; selectDispatch({ - type: SelectActionType.ToggleSelected, + type: 'toggleSelected', id: artistId, isSelected: !isSelected, shiftKey, diff --git a/frontend/src/Artist/Index/Select/ArtistIndexSelectAllButton.tsx b/frontend/src/Artist/Index/Select/ArtistIndexSelectAllButton.tsx index b6055cf02..7229dbdc0 100644 --- a/frontend/src/Artist/Index/Select/ArtistIndexSelectAllButton.tsx +++ b/frontend/src/Artist/Index/Select/ArtistIndexSelectAllButton.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import { SelectActionType, useSelect } from 'App/SelectContext'; +import { useSelect } from 'App/SelectContext'; import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; import { icons } from 'Helpers/Props'; @@ -24,9 +24,7 @@ function ArtistIndexSelectAllButton(props: ArtistIndexSelectAllButtonProps) { const onPress = useCallback(() => { selectDispatch({ - type: allSelected - ? SelectActionType.UnselectAll - : SelectActionType.SelectAll, + type: allSelected ? 'unselectAll' : 'selectAll', }); }, [allSelected, selectDispatch]); diff --git a/frontend/src/Artist/Index/Select/ArtistIndexSelectAllMenuItem.tsx b/frontend/src/Artist/Index/Select/ArtistIndexSelectAllMenuItem.tsx index 332ac0f3e..2340b65b6 100644 --- a/frontend/src/Artist/Index/Select/ArtistIndexSelectAllMenuItem.tsx +++ b/frontend/src/Artist/Index/Select/ArtistIndexSelectAllMenuItem.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import { SelectActionType, useSelect } from 'App/SelectContext'; +import { useSelect } from 'App/SelectContext'; import PageToolbarOverflowMenuItem from 'Components/Page/Toolbar/PageToolbarOverflowMenuItem'; import { icons } from 'Helpers/Props'; @@ -25,9 +25,7 @@ function ArtistIndexSelectAllMenuItem( const onPressWrapper = useCallback(() => { selectDispatch({ - type: allSelected - ? SelectActionType.UnselectAll - : SelectActionType.SelectAll, + type: allSelected ? 'unselectAll' : 'selectAll', }); }, [allSelected, selectDispatch]); diff --git a/frontend/src/Artist/Index/Select/ArtistIndexSelectFooter.tsx b/frontend/src/Artist/Index/Select/ArtistIndexSelectFooter.tsx index c1500c7ac..85a957bfd 100644 --- a/frontend/src/Artist/Index/Select/ArtistIndexSelectFooter.tsx +++ b/frontend/src/Artist/Index/Select/ArtistIndexSelectFooter.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { createSelector } from 'reselect'; -import { SelectActionType, useSelect } from 'App/SelectContext'; +import { useSelect } from 'App/SelectContext'; import AppState from 'App/State/AppState'; import { RENAME_ARTIST, RETAG_ARTIST } from 'Commands/commandNames'; import SpinnerButton from 'Components/Link/SpinnerButton'; @@ -172,7 +172,7 @@ function ArtistIndexSelectFooter() { useEffect(() => { if (!isDeleting && !deleteError) { - selectDispatch({ type: SelectActionType.UnselectAll }); + selectDispatch({ type: 'unselectAll' }); } }, [isDeleting, deleteError, selectDispatch]); diff --git a/frontend/src/Artist/Index/Select/ArtistIndexSelectModeButton.tsx b/frontend/src/Artist/Index/Select/ArtistIndexSelectModeButton.tsx index 8fa313f34..45fe78536 100644 --- a/frontend/src/Artist/Index/Select/ArtistIndexSelectModeButton.tsx +++ b/frontend/src/Artist/Index/Select/ArtistIndexSelectModeButton.tsx @@ -1,6 +1,6 @@ import { IconDefinition } from '@fortawesome/fontawesome-common-types'; import React, { useCallback } from 'react'; -import { SelectActionType, useSelect } from 'App/SelectContext'; +import { useSelect } from 'App/SelectContext'; import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; interface ArtistIndexSelectModeButtonProps { @@ -18,7 +18,7 @@ function ArtistIndexSelectModeButton(props: ArtistIndexSelectModeButtonProps) { const onPressWrapper = useCallback(() => { if (isSelectMode) { selectDispatch({ - type: SelectActionType.Reset, + type: 'reset', }); } diff --git a/frontend/src/Artist/Index/Select/ArtistIndexSelectModeMenuItem.tsx b/frontend/src/Artist/Index/Select/ArtistIndexSelectModeMenuItem.tsx index df7992697..b5a7a6de4 100644 --- a/frontend/src/Artist/Index/Select/ArtistIndexSelectModeMenuItem.tsx +++ b/frontend/src/Artist/Index/Select/ArtistIndexSelectModeMenuItem.tsx @@ -1,6 +1,6 @@ import { IconDefinition } from '@fortawesome/fontawesome-common-types'; import React, { useCallback } from 'react'; -import { SelectActionType, useSelect } from 'App/SelectContext'; +import { useSelect } from 'App/SelectContext'; import PageToolbarOverflowMenuItem from 'Components/Page/Toolbar/PageToolbarOverflowMenuItem'; interface ArtistIndexSelectModeMenuItemProps { @@ -19,7 +19,7 @@ function ArtistIndexSelectModeMenuItem( const onPressWrapper = useCallback(() => { if (isSelectMode) { selectDispatch({ - type: SelectActionType.Reset, + type: 'reset', }); } diff --git a/frontend/src/Artist/Index/Table/ArtistIndexRow.tsx b/frontend/src/Artist/Index/Table/ArtistIndexRow.tsx index 3f5b5385d..da277a418 100644 --- a/frontend/src/Artist/Index/Table/ArtistIndexRow.tsx +++ b/frontend/src/Artist/Index/Table/ArtistIndexRow.tsx @@ -2,7 +2,7 @@ import classNames from 'classnames'; import React, { useCallback, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import AlbumTitleLink from 'Album/AlbumTitleLink'; -import { SelectActionType, useSelect } from 'App/SelectContext'; +import { useSelect } from 'App/SelectContext'; import { Statistics } from 'Artist/Artist'; import ArtistBanner from 'Artist/ArtistBanner'; import ArtistNameLink from 'Artist/ArtistNameLink'; @@ -129,7 +129,7 @@ function ArtistIndexRow(props: ArtistIndexRowProps) { const onSelectedChange = useCallback( ({ id, value, shiftKey }) => { selectDispatch({ - type: SelectActionType.ToggleSelected, + type: 'toggleSelected', id, isSelected: value, shiftKey, diff --git a/frontend/src/Artist/Index/Table/ArtistIndexTableHeader.tsx b/frontend/src/Artist/Index/Table/ArtistIndexTableHeader.tsx index 095984be1..2e903574d 100644 --- a/frontend/src/Artist/Index/Table/ArtistIndexTableHeader.tsx +++ b/frontend/src/Artist/Index/Table/ArtistIndexTableHeader.tsx @@ -1,7 +1,7 @@ import classNames from 'classnames'; import React, { useCallback } from 'react'; import { useDispatch } from 'react-redux'; -import { SelectActionType, useSelect } from 'App/SelectContext'; +import { useSelect } from 'App/SelectContext'; import ArtistIndexTableOptions from 'Artist/Index/Table/ArtistIndexTableOptions'; import IconButton from 'Components/Link/IconButton'; import Column from 'Components/Table/Column'; @@ -48,7 +48,7 @@ function ArtistIndexTableHeader(props: ArtistIndexTableHeaderProps) { const onSelectAllChange = useCallback( ({ value }) => { selectDispatch({ - type: value ? SelectActionType.SelectAll : SelectActionType.UnselectAll, + type: value ? 'selectAll' : 'unselectAll', }); }, [selectDispatch]