mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-06 20:10:12 +02:00
add select all hotkey to lists
This commit is contained in:
@@ -77,6 +77,7 @@ export interface ItemListStateActions {
|
||||
clearDragging: () => void;
|
||||
clearExpanded: () => void;
|
||||
clearSelected: () => void;
|
||||
deselectAll: () => void;
|
||||
extractRowId: (item: unknown) => string | undefined;
|
||||
findItemIndex: (rowId: string) => number;
|
||||
getData: () => unknown[];
|
||||
@@ -91,9 +92,12 @@ export interface ItemListStateActions {
|
||||
hasDragging: () => boolean;
|
||||
hasExpanded: () => boolean;
|
||||
hasSelected: () => boolean;
|
||||
isAllSelected: () => boolean;
|
||||
isDragging: (rowId: string) => boolean;
|
||||
isExpanded: (rowId: string) => boolean;
|
||||
isSelected: (rowId: string) => boolean;
|
||||
isSomeSelected: () => boolean;
|
||||
selectAll: () => void;
|
||||
setDragging: (items: ItemListStateItemWithRequiredProperties[]) => void;
|
||||
setExpanded: (items: ItemListStateItemWithRequiredProperties[]) => void;
|
||||
setSelected: (items: ItemListStateItemWithRequiredProperties[]) => void;
|
||||
@@ -587,6 +591,29 @@ export const useItemListState = (
|
||||
[getDataFn, extractRowId],
|
||||
);
|
||||
|
||||
const selectAll = useCallback(() => {
|
||||
const data = getDataFn ? getDataFn() : [];
|
||||
const items = data
|
||||
.filter((d) => d && typeof d === 'object')
|
||||
.map((d) => d as ItemListStateItemWithRequiredProperties);
|
||||
store.dispatch({ extractRowId: extractRowIdFn, payload: items, type: 'SET_SELECTED' });
|
||||
}, [extractRowIdFn, getDataFn, store]);
|
||||
|
||||
const deselectAll = useCallback(() => {
|
||||
store.dispatch({ type: 'CLEAR_SELECTED' });
|
||||
}, [store]);
|
||||
|
||||
const isAllSelected = useCallback(() => {
|
||||
const state = getCurrentState();
|
||||
const data = getDataFn ? getDataFn() : [];
|
||||
return state.selected.size === data.filter((d) => d && typeof d === 'object').length;
|
||||
}, [getCurrentState, getDataFn]);
|
||||
|
||||
const isSomeSelected = useCallback(() => {
|
||||
const state = getCurrentState();
|
||||
return state.selected.size > 0;
|
||||
}, [getCurrentState]);
|
||||
|
||||
// Expose the store so components can subscribe if needed
|
||||
// Store it in the actions object for access
|
||||
const actions = useMemo(() => {
|
||||
@@ -597,6 +624,7 @@ export const useItemListState = (
|
||||
clearDragging,
|
||||
clearExpanded,
|
||||
clearSelected,
|
||||
deselectAll,
|
||||
extractRowId: extractRowIdFn,
|
||||
findItemIndex,
|
||||
getData,
|
||||
@@ -611,9 +639,12 @@ export const useItemListState = (
|
||||
hasDragging,
|
||||
hasExpanded,
|
||||
hasSelected,
|
||||
isAllSelected,
|
||||
isDragging,
|
||||
isExpanded,
|
||||
isSelected,
|
||||
isSomeSelected,
|
||||
selectAll,
|
||||
setDragging,
|
||||
setExpanded,
|
||||
setSelected,
|
||||
@@ -625,10 +656,15 @@ export const useItemListState = (
|
||||
};
|
||||
return actionsObj;
|
||||
}, [
|
||||
getCurrentState,
|
||||
store,
|
||||
clearAll,
|
||||
clearDragging,
|
||||
clearExpanded,
|
||||
clearSelected,
|
||||
isAllSelected,
|
||||
isSomeSelected,
|
||||
deselectAll,
|
||||
extractRowIdFn,
|
||||
findItemIndex,
|
||||
getData,
|
||||
@@ -646,13 +682,12 @@ export const useItemListState = (
|
||||
isDragging,
|
||||
isExpanded,
|
||||
isSelected,
|
||||
selectAll,
|
||||
setDragging,
|
||||
setExpanded,
|
||||
setSelected,
|
||||
toggleExpanded,
|
||||
toggleSelected,
|
||||
store,
|
||||
getCurrentState,
|
||||
]);
|
||||
|
||||
return actions;
|
||||
|
||||
@@ -44,6 +44,8 @@ import {
|
||||
import { ItemControls, ItemListHandle } from '/@/renderer/components/item-list/types';
|
||||
import { animationProps } from '/@/shared/components/animations/animation-props';
|
||||
import { useElementSize } from '/@/shared/hooks/use-element-size';
|
||||
import { useFocusWithin } from '/@/shared/hooks/use-focus-within';
|
||||
import { useHotkeys } from '/@/shared/hooks/use-hotkeys';
|
||||
import { useMergedRef } from '/@/shared/hooks/use-merged-ref';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
|
||||
@@ -288,7 +290,7 @@ const BaseItemGridList = ({
|
||||
const outerRef = useRef(null);
|
||||
const listRef = useRef<FixedSizeList<GridItemProps>>(null);
|
||||
const { ref: containerRef, width: containerWidth } = useElementSize();
|
||||
const containerFocusRef = useRef<HTMLDivElement | null>(null);
|
||||
const { focused, ref: containerFocusRef } = useFocusWithin();
|
||||
const handleRef = useRef<ItemListHandle | null>(null);
|
||||
const mergedContainerRef = useMergedRef(containerRef, rootRef, containerFocusRef);
|
||||
|
||||
@@ -654,6 +656,21 @@ const BaseItemGridList = ({
|
||||
|
||||
useImperativeHandle(ref, () => imperativeHandle, [imperativeHandle]);
|
||||
|
||||
useHotkeys([
|
||||
[
|
||||
'mod+a',
|
||||
() => {
|
||||
if (focused) {
|
||||
if (internalState.isAllSelected()) {
|
||||
internalState.deselectAll();
|
||||
} else {
|
||||
internalState.selectAll();
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className={styles.itemGridContainer}
|
||||
|
||||
@@ -41,6 +41,8 @@ import {
|
||||
} from '/@/renderer/components/item-list/types';
|
||||
import { PlayerContext, usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||
import { animationProps } from '/@/shared/components/animations/animation-props';
|
||||
import { useFocusWithin } from '/@/shared/hooks/use-focus-within';
|
||||
import { useHotkeys } from '/@/shared/hooks/use-hotkeys';
|
||||
import { useMergedRef } from '/@/shared/hooks/use-merged-ref';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
import { TableColumn } from '/@/shared/types/types';
|
||||
@@ -847,7 +849,9 @@ const BaseItemTableList = ({
|
||||
const [showRightShadow, setShowRightShadow] = useState(false);
|
||||
const [showTopShadow, setShowTopShadow] = useState(false);
|
||||
const handleRef = useRef<ItemListHandle | null>(null);
|
||||
const containerFocusRef = useRef<HTMLDivElement | null>(null);
|
||||
const { focused, ref: focusRef } = useFocusWithin();
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const mergedContainerRef = useMergedRef(containerRef, focusRef);
|
||||
|
||||
const stickyHeaderRef = useRef<HTMLDivElement | null>(null);
|
||||
const stickyGroupRowRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -856,7 +860,7 @@ const BaseItemTableList = ({
|
||||
const stickyHeaderRightRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const { shouldShowStickyHeader, stickyTop } = useStickyTableHeader({
|
||||
containerRef: containerFocusRef,
|
||||
containerRef: containerRef,
|
||||
enabled: enableHeader && enableStickyHeader,
|
||||
headerRef: pinnedRowRef,
|
||||
mainGridRef: rowRef,
|
||||
@@ -867,12 +871,12 @@ const BaseItemTableList = ({
|
||||
|
||||
// Update position and width of sticky header (scroll sync is handled in the hook)
|
||||
useEffect(() => {
|
||||
if (!shouldShowStickyHeader || !stickyHeaderRef.current || !containerFocusRef.current) {
|
||||
if (!shouldShowStickyHeader || !stickyHeaderRef.current || !containerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stickyHeader = stickyHeaderRef.current;
|
||||
const container = containerFocusRef.current;
|
||||
const container = containerRef.current;
|
||||
|
||||
const updatePosition = () => {
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
@@ -914,7 +918,7 @@ const BaseItemTableList = ({
|
||||
|
||||
// Track total container width for autoSizeColumns
|
||||
useEffect(() => {
|
||||
const el = containerFocusRef.current;
|
||||
const el = containerRef.current;
|
||||
if (!el || !autoFitColumns) return;
|
||||
|
||||
const updateWidth = () => {
|
||||
@@ -1495,7 +1499,7 @@ const BaseItemTableList = ({
|
||||
stickyGroupIndex,
|
||||
stickyTop: stickyGroupTop,
|
||||
} = useStickyTableGroupRows({
|
||||
containerRef: containerFocusRef,
|
||||
containerRef: containerRef,
|
||||
enabled: enableStickyGroupRows && !!groups && groups.length > 0,
|
||||
getRowHeight: getRowHeightWrapper,
|
||||
groups,
|
||||
@@ -1510,16 +1514,12 @@ const BaseItemTableList = ({
|
||||
|
||||
// Update position and width of sticky group row
|
||||
useEffect(() => {
|
||||
if (
|
||||
!shouldRenderStickyGroupRow ||
|
||||
!stickyGroupRowRef.current ||
|
||||
!containerFocusRef.current
|
||||
) {
|
||||
if (!shouldRenderStickyGroupRow || !stickyGroupRowRef.current || !containerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stickyGroupRow = stickyGroupRowRef.current;
|
||||
const container = containerFocusRef.current;
|
||||
const container = containerRef.current;
|
||||
|
||||
const updatePosition = () => {
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
@@ -2109,6 +2109,21 @@ const BaseItemTableList = ({
|
||||
stickyGroupTop,
|
||||
]);
|
||||
|
||||
useHotkeys([
|
||||
[
|
||||
'mod+a',
|
||||
() => {
|
||||
if (focused) {
|
||||
if (internalState.isAllSelected()) {
|
||||
internalState.deselectAll();
|
||||
} else {
|
||||
internalState.selectAll();
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className={styles.itemTableListContainer}
|
||||
@@ -2120,7 +2135,7 @@ const BaseItemTableList = ({
|
||||
element.focus({ preventScroll: true });
|
||||
}
|
||||
}}
|
||||
ref={containerFocusRef}
|
||||
ref={mergedContainerRef}
|
||||
tabIndex={0}
|
||||
{...animationProps.fadeIn}
|
||||
transition={{ duration: 1, ease: 'anticipate' }}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { useFocusWithin as useMantineFocusWithin } from '@mantine/hooks';
|
||||
|
||||
export const useFocusWithin = useMantineFocusWithin;
|
||||
Reference in New Issue
Block a user