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