refactor: Unify document selection and drag interactions by replacing stack-specific callbacks and simplifying drag state.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import { createDocumentEntryKey, createFolderEntryKey, isFolderEntry } from '../../app/entryKey';
|
||||
import type { DocumentId, FolderId } from '../../types/identifiers';
|
||||
|
||||
@@ -22,8 +23,8 @@ interface UseDocumentsSelectionOptions {
|
||||
showingSearchResults?: boolean;
|
||||
currentSubfolders?: FolderEntry[];
|
||||
visibleDocuments?: DocumentEntry[];
|
||||
configureSelectionEnvironment: (config: { visibleRowKeySet: Set<string>; navigableRowKeys: string[] }) => void;
|
||||
visibleRowKeySet: Set<string>;
|
||||
configureSelectionEnvironment: (config: { visibleEntryKeySet: Set<string>; navigableEntryKeys: string[] }) => void;
|
||||
visibleEntryKeySet: Set<string>;
|
||||
selectedEntries: string[];
|
||||
selectionAnchorRef: { current: string | null };
|
||||
promoteSelectionOrderRaw: (id: DocumentId) => void;
|
||||
@@ -31,8 +32,8 @@ interface UseDocumentsSelectionOptions {
|
||||
setActivePreviewId: (id: DocumentId | null) => void;
|
||||
clearSelection: () => void;
|
||||
focusedDocumentId: DocumentId | null;
|
||||
setFocusedRowKey: (value: string | null | ((current: string | null) => string | null)) => void;
|
||||
focusedRowKey: string | null;
|
||||
setFocusedEntryKey: Dispatch<SetStateAction<string | null>>;
|
||||
focusedEntryKey: string | null;
|
||||
}
|
||||
|
||||
const useDocumentsSelection = ({
|
||||
@@ -40,7 +41,7 @@ const useDocumentsSelection = ({
|
||||
currentSubfolders = [],
|
||||
visibleDocuments = [],
|
||||
configureSelectionEnvironment,
|
||||
visibleRowKeySet,
|
||||
visibleEntryKeySet,
|
||||
selectedEntries,
|
||||
selectionAnchorRef,
|
||||
promoteSelectionOrderRaw,
|
||||
@@ -48,8 +49,8 @@ const useDocumentsSelection = ({
|
||||
setActivePreviewId,
|
||||
clearSelection,
|
||||
focusedDocumentId,
|
||||
setFocusedRowKey,
|
||||
focusedRowKey,
|
||||
setFocusedEntryKey,
|
||||
focusedEntryKey,
|
||||
}: UseDocumentsSelectionOptions) => {
|
||||
const navigableRows = useMemo<NavigableRow[]>(() => {
|
||||
const entries: NavigableRow[] = [];
|
||||
@@ -77,10 +78,10 @@ const useDocumentsSelection = ({
|
||||
|
||||
useEffect(() => {
|
||||
configureSelectionEnvironment({
|
||||
visibleRowKeySet,
|
||||
navigableRowKeys,
|
||||
visibleEntryKeySet,
|
||||
navigableEntryKeys: navigableRowKeys,
|
||||
});
|
||||
}, [configureSelectionEnvironment, visibleRowKeySet, navigableRowKeys]);
|
||||
}, [configureSelectionEnvironment, visibleEntryKeySet, navigableRowKeys]);
|
||||
|
||||
const promoteSelectionOrder = useCallback(
|
||||
(docId: DocumentId | null) => {
|
||||
@@ -108,40 +109,40 @@ const useDocumentsSelection = ({
|
||||
}
|
||||
prevFocusedDocIdRef.current = focusedDocumentId;
|
||||
if (focusedDocumentId) {
|
||||
setFocusedRowKey(createDocumentEntryKey(focusedDocumentId));
|
||||
setFocusedEntryKey(createDocumentEntryKey(focusedDocumentId));
|
||||
} else {
|
||||
setFocusedRowKey((current) => (current && isFolderEntry(current) ? current : null));
|
||||
setFocusedEntryKey((current) => (current && isFolderEntry(current) ? current : null));
|
||||
}
|
||||
}, [focusedDocumentId, setFocusedRowKey]);
|
||||
}, [focusedDocumentId, setFocusedEntryKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!navigableRowKeys.length) {
|
||||
if (focusedRowKey) {
|
||||
setFocusedRowKey(null);
|
||||
if (focusedEntryKey) {
|
||||
setFocusedEntryKey(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (focusedRowKey && navigableRowKeys.includes(focusedRowKey)) {
|
||||
if (focusedEntryKey && navigableRowKeys.includes(focusedEntryKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const docKey = focusedDocumentId ? createDocumentEntryKey(focusedDocumentId) : null;
|
||||
if (docKey && navigableRowKeys.includes(docKey)) {
|
||||
setFocusedRowKey(docKey);
|
||||
setFocusedEntryKey(docKey);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedKey = selectedEntries.find((key) => navigableRowKeys.includes(key));
|
||||
if (selectedKey) {
|
||||
setFocusedRowKey(selectedKey);
|
||||
setFocusedEntryKey(selectedKey);
|
||||
return;
|
||||
}
|
||||
|
||||
if (focusedRowKey) {
|
||||
setFocusedRowKey(null);
|
||||
if (focusedEntryKey) {
|
||||
setFocusedEntryKey(null);
|
||||
}
|
||||
}, [focusedRowKey, focusedDocumentId, navigableRowKeys, selectedEntries, setFocusedRowKey]);
|
||||
}, [focusedEntryKey, focusedDocumentId, navigableRowKeys, selectedEntries, setFocusedEntryKey]);
|
||||
|
||||
return {
|
||||
navigableRows,
|
||||
|
||||
@@ -142,8 +142,8 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
||||
}): ReactNode => {
|
||||
const {
|
||||
selectedEntries,
|
||||
focusedRowKey,
|
||||
setFocusedRowKey,
|
||||
focusedEntryKey,
|
||||
setFocusedEntryKey,
|
||||
handleEntrySelection,
|
||||
clearSelection,
|
||||
selectionAnchorRef,
|
||||
@@ -177,28 +177,28 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const rowKeys = docIds
|
||||
const entryKeys = docIds
|
||||
.map((id) => createDocumentEntryKey(id as Identifier))
|
||||
.filter((value): value is string => typeof value === 'string');
|
||||
|
||||
if (!rowKeys.length) {
|
||||
if (!entryKeys.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextKeys = [...selectedEntries];
|
||||
rowKeys.forEach((key) => {
|
||||
entryKeys.forEach((key) => {
|
||||
if (!nextKeys.includes(key)) {
|
||||
nextKeys.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
const anchor = (rowKeys[0]
|
||||
const anchor = (entryKeys[0]
|
||||
|| selectionAnchorRef.current
|
||||
|| nextKeys[nextKeys.length - 1]) as string | null;
|
||||
|
||||
applySelection(nextKeys, {
|
||||
anchor,
|
||||
interactedKeys: rowKeys,
|
||||
interactedKeys: entryKeys,
|
||||
});
|
||||
},
|
||||
[applySelection, selectedEntries, selectionAnchorRef],
|
||||
@@ -482,24 +482,24 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
||||
() => entries.map((entry) => ({ key: entry.key, type: entry.type, id: entry.id })),
|
||||
[entries],
|
||||
);
|
||||
const navigableRowKeys = useMemo(() => navigableRows.map((row) => row.key), [navigableRows]);
|
||||
const navigableEntryKeys = useMemo(() => navigableRows.map((row) => row.key), [navigableRows]);
|
||||
|
||||
const getEntryByKey = useCallback(
|
||||
(rowKey) => entries.find((entry) => entry.key === rowKey) || null,
|
||||
(entryKey) => entries.find((entry) => entry.key === entryKey) || null,
|
||||
[entries],
|
||||
);
|
||||
|
||||
const handlePanelFocus = useCallback(() => {
|
||||
let resolvedKey = null;
|
||||
|
||||
if (focusedRowKey && navigableRowKeys.includes(focusedRowKey)) {
|
||||
resolvedKey = focusedRowKey;
|
||||
if (focusedEntryKey && navigableEntryKeys.includes(focusedEntryKey)) {
|
||||
resolvedKey = focusedEntryKey;
|
||||
}
|
||||
|
||||
if (!resolvedKey) {
|
||||
for (let index = selectedEntries.length - 1; index >= 0; index -= 1) {
|
||||
const candidate = selectedEntries[index];
|
||||
if (navigableRowKeys.includes(candidate)) {
|
||||
if (navigableEntryKeys.includes(candidate)) {
|
||||
resolvedKey = candidate;
|
||||
break;
|
||||
}
|
||||
@@ -519,12 +519,12 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
setFocusedRowKey(resolvedKey);
|
||||
setFocusedEntryKey(resolvedKey);
|
||||
}, [
|
||||
focusedRowKey,
|
||||
navigableRowKeys,
|
||||
focusedEntryKey,
|
||||
navigableEntryKeys,
|
||||
navigableRows,
|
||||
setFocusedRowKey,
|
||||
setFocusedEntryKey,
|
||||
selectedEntries,
|
||||
]);
|
||||
|
||||
@@ -543,15 +543,15 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
||||
event.preventDefault();
|
||||
|
||||
let activeKey =
|
||||
focusedRowKey && navigableRowKeys.includes(focusedRowKey)
|
||||
? focusedRowKey
|
||||
focusedEntryKey && navigableEntryKeys.includes(focusedEntryKey)
|
||||
? focusedEntryKey
|
||||
: null;
|
||||
|
||||
if (!activeKey) {
|
||||
if (selectedEntries.length) {
|
||||
for (let index = selectedEntries.length - 1; index >= 0; index -= 1) {
|
||||
const candidate = selectedEntries[index];
|
||||
if (navigableRowKeys.includes(candidate)) {
|
||||
if (navigableEntryKeys.includes(candidate)) {
|
||||
activeKey = candidate;
|
||||
break;
|
||||
}
|
||||
@@ -559,11 +559,11 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
||||
}
|
||||
|
||||
if (!activeKey) {
|
||||
activeKey = key === 'ArrowUp' ? navigableRowKeys[navigableRowKeys.length - 1] : navigableRowKeys[0];
|
||||
activeKey = key === 'ArrowUp' ? navigableEntryKeys[navigableEntryKeys.length - 1] : navigableEntryKeys[0];
|
||||
}
|
||||
}
|
||||
|
||||
const currentIndex = navigableRowKeys.indexOf(activeKey);
|
||||
const currentIndex = navigableEntryKeys.indexOf(activeKey);
|
||||
const activeRow = currentIndex === -1 ? null : navigableRows[currentIndex];
|
||||
|
||||
if (key === 'Enter' || key === ' ' || key === 'Space' || key === 'Spacebar') {
|
||||
@@ -601,22 +601,22 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
setFocusedRowKey(targetRow.key);
|
||||
setFocusedEntryKey(targetRow.key);
|
||||
handleEntrySelection(targetRow.key, {
|
||||
shiftKey,
|
||||
preventDefault: () => { },
|
||||
});
|
||||
},
|
||||
[
|
||||
focusedRowKey,
|
||||
focusedEntryKey,
|
||||
getEntryByKey,
|
||||
navigableRowKeys,
|
||||
navigableEntryKeys,
|
||||
navigableRows,
|
||||
onFolderSelect,
|
||||
selectedEntries,
|
||||
handleDocumentPreviewZoom,
|
||||
handleEntrySelection,
|
||||
setFocusedRowKey,
|
||||
setFocusedEntryKey,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -648,14 +648,14 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
||||
|
||||
const isTagDragEvent = useCallback((event) => isTagTransferEvent(event), []);
|
||||
const ensureFocusedRowVisible = useCallback(() => {
|
||||
if (!focusedRowKey) return;
|
||||
if (!focusedEntryKey) return;
|
||||
const container = scrollRef.current;
|
||||
if (!container) return;
|
||||
let selector = null;
|
||||
if (focusedRowKey.startsWith('document:')) {
|
||||
selector = `#document-row-${focusedRowKey.slice('document:'.length)}`;
|
||||
} else if (focusedRowKey.startsWith('folder:')) {
|
||||
selector = `#folder-row-${focusedRowKey.slice('folder:'.length)}`;
|
||||
if (focusedEntryKey.startsWith('document:')) {
|
||||
selector = `#document-row-${focusedEntryKey.slice('document:'.length)}`;
|
||||
} else if (focusedEntryKey.startsWith('folder:')) {
|
||||
selector = `#folder-row-${focusedEntryKey.slice('folder:'.length)}`;
|
||||
}
|
||||
if (!selector) {
|
||||
return;
|
||||
@@ -681,22 +681,22 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
||||
const nextScrollTop = rowBottom - container.clientHeight;
|
||||
container.scrollTop = Math.max(nextScrollTop, 0);
|
||||
}
|
||||
}, [focusedRowKey]);
|
||||
}, [focusedEntryKey]);
|
||||
|
||||
useEffect(() => {
|
||||
ensureFocusedRowVisible();
|
||||
}, [ensureFocusedRowVisible]);
|
||||
|
||||
const activeDescendantId = useMemo(() => {
|
||||
if (!focusedRowKey) return undefined;
|
||||
if (focusedRowKey.startsWith('document:')) {
|
||||
return `document-row-${focusedRowKey.slice('document:'.length)}`;
|
||||
if (!focusedEntryKey) return undefined;
|
||||
if (focusedEntryKey.startsWith('document:')) {
|
||||
return `document-row-${focusedEntryKey.slice('document:'.length)}`;
|
||||
}
|
||||
if (focusedRowKey.startsWith('folder:')) {
|
||||
return `folder-row-${focusedRowKey.slice('folder:'.length)}`;
|
||||
if (focusedEntryKey.startsWith('folder:')) {
|
||||
return `folder-row-${focusedEntryKey.slice('folder:'.length)}`;
|
||||
}
|
||||
return undefined;
|
||||
}, [focusedRowKey]);
|
||||
}, [focusedEntryKey]);
|
||||
|
||||
const handleDocumentTagDragOver = useCallback(
|
||||
(event) => {
|
||||
@@ -756,10 +756,10 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
||||
&& scrollRef.current
|
||||
) {
|
||||
scrollRef.current.focus({ preventScroll: true });
|
||||
setFocusedRowKey(`folder:${folder.id}`);
|
||||
setFocusedEntryKey(`folder:${folder.id}`);
|
||||
}
|
||||
},
|
||||
[onEntryPointer, setFocusedRowKey],
|
||||
[onEntryPointer, setFocusedEntryKey],
|
||||
);
|
||||
|
||||
const handleDocumentDragStartLocal = useCallback(
|
||||
|
||||
Reference in New Issue
Block a user