refactor: Standardize document and folder identification with a new entryKey module.

This commit is contained in:
2025-11-24 23:13:11 +01:00
parent 096c377897
commit 22f0c040a2
12 changed files with 146 additions and 202 deletions
+24
View File
@@ -0,0 +1,24 @@
// Entry key utilities for workspace selection
// Entry keys are strings in the format "document:id" or "folder:id"
const ENTRY_KEY_SEPARATOR = ':';
// Create entry key strings
export const createDocumentEntryKey = (documentId: string | number): string =>
`document${ENTRY_KEY_SEPARATOR}${documentId}`;
export const createFolderEntryKey = (folderId: string | number): string =>
`folder${ENTRY_KEY_SEPARATOR}${folderId}`;
// Type guards for entry key strings
export const isDocumentEntry = (key: string): boolean =>
key.split(ENTRY_KEY_SEPARATOR, 1)[0] === 'document';
export const isFolderEntry = (key: string): boolean =>
key.split(ENTRY_KEY_SEPARATOR, 1)[0] === 'folder';
// Extract ID from entry key string
export const getEntryId = (key: string): string => {
const parts = key.split(ENTRY_KEY_SEPARATOR);
return parts.slice(1).join(ENTRY_KEY_SEPARATOR);
};
+42 -51
View File
@@ -1,6 +1,12 @@
import { useCallback, useRef, useState } from 'react'; import { useCallback, useRef, useState } from 'react';
import {
createDocumentEntryKey,
createFolderEntryKey,
isDocumentEntry,
isFolderEntry,
getEntryId,
} from './entryKey';
type RowKey = string;
type DocumentId = string | number; type DocumentId = string | number;
interface SelectionEventLike { interface SelectionEventLike {
@@ -11,46 +17,36 @@ interface SelectionEventLike {
} }
interface UseDocumentSelectionOptions { interface UseDocumentSelectionOptions {
resolveDocumentRowKey: (id: DocumentId | null) => RowKey | null; initialEntries?: string[];
resolveFolderRowKey: (id: DocumentId | null) => RowKey | null;
isDocumentRowKey: (key?: RowKey | null) => boolean;
isFolderRowKey: (key?: RowKey | null) => boolean;
getRowId: (key?: RowKey | null) => DocumentId | null;
initialEntries?: RowKey[];
} }
interface ApplySelectionOptions { interface ApplySelectionOptions {
anchor?: RowKey | null; anchor?: string;
interactedKeys?: RowKey[]; interactedKeys?: string[];
} }
const DEFAULT_INITIAL_ENTRIES: RowKey[] = []; const DEFAULT_INITIAL_ENTRIES: string[] = [];
export const useDocumentSelection = ({ export const useDocumentSelection = ({
resolveDocumentRowKey,
resolveFolderRowKey,
isDocumentRowKey,
isFolderRowKey,
getRowId,
initialEntries = DEFAULT_INITIAL_ENTRIES, initialEntries = DEFAULT_INITIAL_ENTRIES,
}: UseDocumentSelectionOptions) => { }: UseDocumentSelectionOptions = {}) => {
const [selectedEntries, setSelectedEntries] = useState<RowKey[]>(initialEntries); const [selectedEntries, setSelectedEntries] = useState<string[]>(initialEntries);
const [selectionOrder, setSelectionOrder] = useState<RowKey[]>(initialEntries); const [selectionOrder, setSelectionOrder] = useState<string[]>(initialEntries);
const selectionOrderRef = useRef<RowKey[]>(initialEntries); const selectionOrderRef = useRef<string[]>(initialEntries);
const selectionAnchorRef = useRef<RowKey | null>(null); const selectionAnchorRef = useRef<string | null>(null);
const selectionInitializedRef = useRef(false); const selectionInitializedRef = useRef(false);
const [focusedDocumentId, setFocusedDocumentId] = useState<DocumentId | null>(null); const [focusedDocumentId, setFocusedDocumentId] = useState<DocumentId | undefined>(undefined);
const [focusedRowKey, setFocusedRowKey] = useState<RowKey | null>(null); const [focusedRowKey, setFocusedRowKey] = useState<string | undefined>(undefined);
const visibleRowKeySetRef = useRef<Set<RowKey>>(new Set()); const visibleRowKeySetRef = useRef<Set<string>>(new Set());
const navigableRowKeysRef = useRef<RowKey[]>([]); const navigableRowKeysRef = useRef<string[]>([]);
const configureSelectionEnvironment = useCallback(({ const configureSelectionEnvironment = useCallback(({
visibleRowKeySet, visibleRowKeySet,
navigableRowKeys, navigableRowKeys,
}: { }: {
visibleRowKeySet?: Set<RowKey>; visibleRowKeySet?: Set<string>;
navigableRowKeys?: RowKey[]; navigableRowKeys?: string[];
}) => { }) => {
if (visibleRowKeySet) { if (visibleRowKeySet) {
visibleRowKeySetRef.current = visibleRowKeySet; visibleRowKeySetRef.current = visibleRowKeySet;
@@ -60,7 +56,7 @@ export const useDocumentSelection = ({
} }
}, []); }, []);
const updateSelectionOrder = useCallback((nextSelection: RowKey[], interactedKeys: RowKey[] = []) => { const updateSelectionOrder = useCallback((nextSelection: string[], interactedKeys: string[] = []) => {
const nextSet = new Set(nextSelection); const nextSet = new Set(nextSelection);
const previousOrder = selectionOrderRef.current.filter((id) => nextSet.has(id)); const previousOrder = selectionOrderRef.current.filter((id) => nextSet.has(id));
const interacted = (interactedKeys || []).filter((id, index, array) => array.indexOf(id) === index); const interacted = (interactedKeys || []).filter((id, index, array) => array.indexOf(id) === index);
@@ -93,23 +89,23 @@ export const useDocumentSelection = ({
const applySelection = useCallback( const applySelection = useCallback(
( (
rowKeys: Array<RowKey | null>, rowKeys: Array<string | null>,
{ anchor, interactedKeys = [] }: ApplySelectionOptions = {}, { anchor, interactedKeys = [] }: ApplySelectionOptions = {},
) => { ) => {
const visibleRowKeySet = visibleRowKeySetRef.current; const visibleRowKeySet = visibleRowKeySetRef.current;
const unique: RowKey[] = []; const unique: string[] = [];
(rowKeys || []).forEach((key) => { (rowKeys || []).forEach((key) => {
if (!key) return; if (!key) return;
let canonicalKey: RowKey | null = null; let canonicalKey: string | null = null;
if (visibleRowKeySet.has(key)) { if (visibleRowKeySet.has(key)) {
canonicalKey = key; canonicalKey = key;
} else if (isDocumentRowKey(key)) { } else if (isDocumentEntry(key)) {
const id = getRowId(key); const id = getEntryId(key);
canonicalKey = id ? resolveDocumentRowKey(id) : null; canonicalKey = id ? createDocumentEntryKey(id) : null;
} else if (isFolderRowKey(key)) { } else if (isFolderEntry(key)) {
const id = getRowId(key); const id = getEntryId(key);
canonicalKey = id ? resolveFolderRowKey(id) : null; canonicalKey = id ? createFolderEntryKey(id) : null;
} }
if (!canonicalKey || !visibleRowKeySet.has(canonicalKey)) { if (!canonicalKey || !visibleRowKeySet.has(canonicalKey)) {
@@ -131,18 +127,18 @@ export const useDocumentSelection = ({
const nextFocusedDocumentId: DocumentId | null = (() => { const nextFocusedDocumentId: DocumentId | null = (() => {
if (focusedDocumentId) { if (focusedDocumentId) {
const focusKey = resolveDocumentRowKey(focusedDocumentId); const focusKey = createDocumentEntryKey(focusedDocumentId);
if (focusKey && unique.includes(focusKey)) { if (focusKey && unique.includes(focusKey)) {
return focusedDocumentId; return focusedDocumentId;
} }
} }
if (resolvedAnchor && isDocumentRowKey(resolvedAnchor)) { if (resolvedAnchor && isDocumentEntry(resolvedAnchor)) {
return getRowId(resolvedAnchor) ?? null; return getEntryId(resolvedAnchor) ?? null;
} }
const lastDocKey = [...unique].reverse().find((key) => isDocumentRowKey(key)) ?? null; const lastDocKey = [...unique].reverse().find((key) => isDocumentEntry(key)) ?? null;
return lastDocKey ? getRowId(lastDocKey) ?? null : null; return lastDocKey ? getEntryId(lastDocKey) ?? null : null;
})(); })();
setFocusedDocumentId(nextFocusedDocumentId); setFocusedDocumentId(nextFocusedDocumentId);
@@ -159,11 +155,6 @@ export const useDocumentSelection = ({
}, },
[ [
focusedDocumentId, focusedDocumentId,
getRowId,
isDocumentRowKey,
isFolderRowKey,
resolveDocumentRowKey,
resolveFolderRowKey,
updateSelectionOrder, updateSelectionOrder,
], ],
); );
@@ -174,7 +165,7 @@ export const useDocumentSelection = ({
}, [applySelection]); }, [applySelection]);
const handleEntrySelection = useCallback( const handleEntrySelection = useCallback(
(rowKey: RowKey | null, event?: SelectionEventLike) => { (rowKey: string, event?: SelectionEventLike) => {
const visibleRowKeySet = visibleRowKeySetRef.current; const visibleRowKeySet = visibleRowKeySetRef.current;
const navigableRowKeys = navigableRowKeysRef.current; const navigableRowKeys = navigableRowKeysRef.current;
if (!rowKey || !visibleRowKeySet.has(rowKey)) { if (!rowKey || !visibleRowKeySet.has(rowKey)) {
@@ -200,8 +191,8 @@ export const useDocumentSelection = ({
anchorKey = rowKey; anchorKey = rowKey;
} }
let nextKeys: RowKey[] = []; let nextKeys: string[] = [];
let interactedKeys: RowKey[] = []; let interactedKeys: string[] = [];
if (shiftKey && anchorKey) { if (shiftKey && anchorKey) {
const anchorIndex = navigableRowKeys.indexOf(anchorKey); const anchorIndex = navigableRowKeys.indexOf(anchorKey);
@@ -245,7 +236,7 @@ export const useDocumentSelection = ({
const promoteSelectionOrder = useCallback( const promoteSelectionOrder = useCallback(
(docId?: DocumentId | null) => { (docId?: DocumentId | null) => {
if (!docId) return; if (!docId) return;
const rowKey = resolveDocumentRowKey(docId); const rowKey = createDocumentEntryKey(docId);
if (!rowKey) return; if (!rowKey) return;
if (!selectedEntries.includes(rowKey)) { if (!selectedEntries.includes(rowKey)) {
@@ -254,7 +245,7 @@ export const useDocumentSelection = ({
updateSelectionOrder(selectedEntries, [rowKey]); updateSelectionOrder(selectedEntries, [rowKey]);
}, },
[resolveDocumentRowKey, selectedEntries, updateSelectionOrder], [selectedEntries, updateSelectionOrder],
); );
return { return {
+16 -31
View File
@@ -1,19 +1,15 @@
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useDocumentSelection } from './useDocumentSelection'; import { useDocumentSelection } from './useDocumentSelection';
import { isDocumentEntry, isFolderEntry, getEntryId } from './entryKey';
type RowKey = string;
interface SelectionEntry { interface SelectionEntry {
rowKey?: RowKey; entryKey?: string;
// Legacy field for compatibility
rowKey?: string;
[key: string]: unknown; [key: string]: unknown;
} }
interface WorkspaceSelectionOptions { interface WorkspaceSelectionOptions {
resolveDocumentRowKey?: (id: string | number) => RowKey | null;
resolveFolderRowKey?: (id: string | number) => RowKey | null;
isDocumentRowKey?: (key: RowKey | SelectionEntry) => boolean;
isFolderRowKey?: (key: RowKey | SelectionEntry) => boolean;
getRowId?: (key: RowKey | SelectionEntry) => string | number | null;
onDocumentActivate?: (id: string | number) => void; onDocumentActivate?: (id: string | number) => void;
onInspectFolder?: (id: string | number) => void; onInspectFolder?: (id: string | number) => void;
} }
@@ -21,21 +17,10 @@ interface WorkspaceSelectionOptions {
const identity = <T,>(value: T) => value; const identity = <T,>(value: T) => value;
export const useWorkspaceSelection = ({ export const useWorkspaceSelection = ({
resolveDocumentRowKey,
resolveFolderRowKey,
isDocumentRowKey = () => false,
isFolderRowKey = () => false,
getRowId = () => null,
onDocumentActivate = identity, onDocumentActivate = identity,
onInspectFolder = identity, onInspectFolder = identity,
}: WorkspaceSelectionOptions = {}) => { }: WorkspaceSelectionOptions = {}) => {
const selection = useDocumentSelection({ const selection = useDocumentSelection();
resolveDocumentRowKey,
resolveFolderRowKey,
isDocumentRowKey,
isFolderRowKey,
getRowId,
});
const { const {
selectedEntries, selectedEntries,
@@ -59,26 +44,26 @@ export const useWorkspaceSelection = ({
const selectedDocumentIds = useMemo( const selectedDocumentIds = useMemo(
() => () =>
selectedEntries selectedEntries
.filter((entry) => isDocumentRowKey(entry)) .filter((entry) => isDocumentEntry(entry))
.map((entry) => getRowId(entry)) .map((entry) => getEntryId(entry))
.filter(Boolean), .filter(Boolean),
[selectedEntries, isDocumentRowKey, getRowId], [selectedEntries],
); );
const selectedFolderIds = useMemo( const selectedFolderIds = useMemo(
() => () =>
selectedEntries selectedEntries
.filter((entry) => isFolderRowKey(entry)) .filter((entry) => isFolderEntry(entry))
.map((entry) => getRowId(entry)) .map((entry) => getEntryId(entry))
.filter(Boolean), .filter(Boolean),
[selectedEntries, isFolderRowKey, getRowId], [selectedEntries],
); );
const selectEntry = useCallback( const selectEntry = useCallback(
(entry: SelectionEntry | string | null, event?: unknown) => { (entry: SelectionEntry | string, event?: unknown) => {
const rowKey = entry && Object(entry) === entry const rowKey = entry && Object(entry) === entry
? (entry as SelectionEntry).rowKey ?? null ? (entry as SelectionEntry).rowKey ?? undefined
: (entry as string | null); : (entry as string);
if (!rowKey) return; if (!rowKey) return;
handleEntrySelection(rowKey, event); handleEntrySelection(rowKey, event);
}, },
@@ -86,7 +71,7 @@ export const useWorkspaceSelection = ({
); );
const inspectDocument = useCallback( const inspectDocument = useCallback(
(documentId?: string | number | null) => { (documentId?: string | number) => {
if (!documentId) return; if (!documentId) return;
onDocumentActivate(documentId); onDocumentActivate(documentId);
}, },
@@ -94,7 +79,7 @@ export const useWorkspaceSelection = ({
); );
const inspectFolder = useCallback( const inspectFolder = useCallback(
(folderId?: string | number | null) => { (folderId?: string | number) => {
if (!folderId) return; if (!folderId) return;
onInspectFolder(folderId); onInspectFolder(folderId);
}, },
-23
View File
@@ -4,29 +4,6 @@ export const DEFAULT_SORT_DIRECTION = 'asc';
export const SORT_FIELD_VALUES = ['title', 'issued_at', 'created_at', 'updated_at']; export const SORT_FIELD_VALUES = ['title', 'issued_at', 'created_at', 'updated_at'];
export const TAG_FILTER_UNTAGGED = '__UNTAGGED__'; export const TAG_FILTER_UNTAGGED = '__UNTAGGED__';
const ROW_KEY_SEPARATOR = ':';
const DOCUMENT_ROW_PREFIX = 'document';
const FOLDER_ROW_PREFIX = 'folder';
const makeRowKey = (type, id) =>
id ? `${type}${ROW_KEY_SEPARATOR}${id}` : `${type}${ROW_KEY_SEPARATOR}`;
const getRowType = (key: string | null) => (key ?? '').split(ROW_KEY_SEPARATOR, 1)[0] ?? '';
export const getRowId = (key: string) => {
const parts = key.split(ROW_KEY_SEPARATOR);
return parts.slice(1).join(ROW_KEY_SEPARATOR);
};
export const isDocumentRowKey = (key) => getRowType(key) === DOCUMENT_ROW_PREFIX;
export const isFolderRowKey = (key) => getRowType(key) === FOLDER_ROW_PREFIX;
export const resolveDocumentRowKey = (documentId) =>
documentId ? makeRowKey(DOCUMENT_ROW_PREFIX, documentId) : null;
export const resolveFolderRowKey = (folderId) =>
folderId ? makeRowKey(FOLDER_ROW_PREFIX, folderId) : null;
export const hasFiles = (event) => export const hasFiles = (event) =>
Array.from(event.dataTransfer?.types || []).includes('Files'); Array.from(event.dataTransfer?.types || []).includes('Files');
+4 -7
View File
@@ -2,11 +2,8 @@ import { useCallback, useEffect, useMemo } from 'react';
import type { MutableRefObject } from 'react'; import type { MutableRefObject } from 'react';
import { resolveDocumentAssetUrl } from '../asset_manager'; import { resolveDocumentAssetUrl } from '../asset_manager';
import { useDetailPanel } from '../app/useDetailPanel'; import { useDetailPanel } from '../app/useDetailPanel';
import { import { DEFAULT_FOLDER_NAME } from '../app/workspaceUtils';
DEFAULT_FOLDER_NAME, import { getEntryId, isDocumentEntry } from '../app/entryKey';
getRowId,
isDocumentRowKey,
} from '../app/workspaceUtils';
import type { DocumentInfoPanelProps } from '../documents/DocumentInfoPanel'; import type { DocumentInfoPanelProps } from '../documents/DocumentInfoPanel';
import type { EnsureAssetUrl, GetDocumentAsset } from '../utils/ocr'; import type { EnsureAssetUrl, GetDocumentAsset } from '../utils/ocr';
@@ -101,10 +98,10 @@ const useDetailWorkspace = ({
}; };
selectionOrder.forEach((key) => { selectionOrder.forEach((key) => {
if (!isDocumentRowKey(key)) { if (!isDocumentEntry(key)) {
return; return;
} }
const docId = getRowId(key); const docId = getEntryId(key);
const doc = documentLookup.get(docId) || null; const doc = documentLookup.get(docId) || null;
pushDoc(doc); pushDoc(doc);
}); });
@@ -1,4 +1,5 @@
import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useCallback, useEffect, useMemo, useRef } from 'react';
import { createDocumentEntryKey, createFolderEntryKey, isFolderEntry } from '../../app/entryKey';
interface FolderEntry { interface FolderEntry {
id: string | number; id: string | number;
@@ -17,11 +18,9 @@ interface NavigableRow {
} }
interface UseDocumentsSelectionOptions { interface UseDocumentsSelectionOptions {
showingSearchResults: boolean; showingSearchResults?: boolean;
currentSubfolders: FolderEntry[]; currentSubfolders?: FolderEntry[];
visibleDocuments: DocumentEntry[]; visibleDocuments?: DocumentEntry[];
resolveFolderRowKey: (id: string | number) => string | null;
resolveDocumentRowKey: (id: string | number) => string | null;
configureSelectionEnvironment: (config: { visibleRowKeySet: Set<string>; navigableRowKeys: string[] }) => void; configureSelectionEnvironment: (config: { visibleRowKeySet: Set<string>; navigableRowKeys: string[] }) => void;
visibleRowKeySet: Set<string>; visibleRowKeySet: Set<string>;
selectedEntries: string[]; selectedEntries: string[];
@@ -33,15 +32,12 @@ interface UseDocumentsSelectionOptions {
focusedDocumentId: string | number | null; focusedDocumentId: string | number | null;
setFocusedRowKey: (value: string | null | ((current: string | null) => string | null)) => void; setFocusedRowKey: (value: string | null | ((current: string | null) => string | null)) => void;
focusedRowKey: string | null; focusedRowKey: string | null;
isFolderRowKey: (key: string | null) => boolean;
} }
const useDocumentsSelection = ({ const useDocumentsSelection = ({
showingSearchResults, showingSearchResults,
currentSubfolders, currentSubfolders,
visibleDocuments, visibleDocuments,
resolveFolderRowKey,
resolveDocumentRowKey,
configureSelectionEnvironment, configureSelectionEnvironment,
visibleRowKeySet, visibleRowKeySet,
selectedEntries, selectedEntries,
@@ -53,26 +49,25 @@ const useDocumentsSelection = ({
focusedDocumentId, focusedDocumentId,
setFocusedRowKey, setFocusedRowKey,
focusedRowKey, focusedRowKey,
isFolderRowKey,
}: UseDocumentsSelectionOptions) => { }: UseDocumentsSelectionOptions) => {
const navigableRows = useMemo<NavigableRow[]>(() => { const navigableRows = useMemo<NavigableRow[]>(() => {
const entries: NavigableRow[] = []; const entries: NavigableRow[] = [];
if (!showingSearchResults) { if (!showingSearchResults) {
currentSubfolders.forEach((folder) => { currentSubfolders.forEach((folder) => {
const key = resolveFolderRowKey(folder.id); const key = createFolderEntryKey(folder.id);
if (key) { if (key) {
entries.push({ key, type: 'folder', id: folder.id }); entries.push({ key, type: 'folder', id: folder.id });
} }
}); });
} }
visibleDocuments.forEach((doc) => { visibleDocuments.forEach((doc) => {
const key = resolveDocumentRowKey(doc.id); const key = createDocumentEntryKey(doc.id);
if (key) { if (key) {
entries.push({ key, type: 'document', id: doc.id }); entries.push({ key, type: 'document', id: doc.id });
} }
}); });
return entries; return entries;
}, [showingSearchResults, currentSubfolders, visibleDocuments, resolveFolderRowKey, resolveDocumentRowKey]); }, [showingSearchResults, currentSubfolders, visibleDocuments]);
const navigableRowKeys = useMemo( const navigableRowKeys = useMemo(
() => navigableRows.map((entry) => entry.key), () => navigableRows.map((entry) => entry.key),
@@ -90,14 +85,14 @@ const useDocumentsSelection = ({
(docId: string | number | null) => { (docId: string | number | null) => {
if (!docId) return; if (!docId) return;
promoteSelectionOrderRaw(docId); promoteSelectionOrderRaw(docId);
const rowKey = resolveDocumentRowKey(docId); const rowKey = createDocumentEntryKey(docId);
if (rowKey) { if (rowKey) {
selectionAnchorRef.current = rowKey; selectionAnchorRef.current = rowKey;
} }
setFocusedDocumentId(docId); setFocusedDocumentId(docId);
setActivePreviewId(docId); setActivePreviewId(docId);
}, },
[promoteSelectionOrderRaw, resolveDocumentRowKey, selectionAnchorRef, setFocusedDocumentId, setActivePreviewId], [promoteSelectionOrderRaw, selectionAnchorRef, setFocusedDocumentId, setActivePreviewId],
); );
const clearDocumentSelection = useCallback(() => { const clearDocumentSelection = useCallback(() => {
@@ -112,11 +107,11 @@ const useDocumentsSelection = ({
} }
prevFocusedDocIdRef.current = focusedDocumentId; prevFocusedDocIdRef.current = focusedDocumentId;
if (focusedDocumentId) { if (focusedDocumentId) {
setFocusedRowKey(resolveDocumentRowKey(focusedDocumentId)); setFocusedRowKey(createDocumentEntryKey(focusedDocumentId));
} else { } else {
setFocusedRowKey((current) => (isFolderRowKey(current) ? current : null)); setFocusedRowKey((current) => (isFolderEntry(current) ? current : null));
} }
}, [focusedDocumentId, resolveDocumentRowKey, setFocusedRowKey, isFolderRowKey]); }, [focusedDocumentId, setFocusedRowKey]);
useEffect(() => { useEffect(() => {
if (!navigableRowKeys.length) { if (!navigableRowKeys.length) {
@@ -130,7 +125,7 @@ const useDocumentsSelection = ({
return; return;
} }
const docKey = focusedDocumentId ? resolveDocumentRowKey(focusedDocumentId) : null; const docKey = focusedDocumentId ? createDocumentEntryKey(focusedDocumentId) : null;
if (docKey && navigableRowKeys.includes(docKey)) { if (docKey && navigableRowKeys.includes(docKey)) {
setFocusedRowKey(docKey); setFocusedRowKey(docKey);
return; return;
@@ -145,7 +140,7 @@ const useDocumentsSelection = ({
if (focusedRowKey) { if (focusedRowKey) {
setFocusedRowKey(null); setFocusedRowKey(null);
} }
}, [focusedRowKey, focusedDocumentId, navigableRowKeys, selectedEntries, setFocusedRowKey, resolveDocumentRowKey]); }, [focusedRowKey, focusedDocumentId, navigableRowKeys, selectedEntries, setFocusedRowKey]);
return { return {
navigableRows, navigableRows,
+3 -6
View File
@@ -1,4 +1,5 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { createDocumentEntryKey, createFolderEntryKey } from '../app/entryKey';
export type PointerEventLike = MouseEvent | PointerEvent; export type PointerEventLike = MouseEvent | PointerEvent;
@@ -26,8 +27,6 @@ export interface WorkspaceEntry {
} }
interface UseEntryPointerOptions { interface UseEntryPointerOptions {
resolveDocumentRowKey?: (id: string | number) => string | null;
resolveFolderRowKey?: (id: string | number) => string | null;
onSelectEntry?: (entry: WorkspaceEntry, event?: PointerEventLike | null, metadata?: EntryPointerMetadata) => void; onSelectEntry?: (entry: WorkspaceEntry, event?: PointerEventLike | null, metadata?: EntryPointerMetadata) => void;
onDocumentActivate?: (id: string | number, metadata?: EntryPointerMetadata) => void; onDocumentActivate?: (id: string | number, metadata?: EntryPointerMetadata) => void;
} }
@@ -41,8 +40,6 @@ export interface EntryPointerMetadata {
} }
export const useEntryPointer = ({ export const useEntryPointer = ({
resolveDocumentRowKey,
resolveFolderRowKey,
onSelectEntry, onSelectEntry,
onDocumentActivate, onDocumentActivate,
}: UseEntryPointerOptions) => }: UseEntryPointerOptions) =>
@@ -58,7 +55,7 @@ export const useEntryPointer = ({
} }
const rowKey = entry.key const rowKey = entry.key
|| (type === 'document' ? resolveDocumentRowKey?.(id) : resolveFolderRowKey?.(id)); || (type === 'document' ? createDocumentEntryKey(id) : createFolderEntryKey(id));
if (!rowKey) { if (!rowKey) {
return; return;
} }
@@ -73,7 +70,7 @@ export const useEntryPointer = ({
onDocumentActivate?.(id, metadata); onDocumentActivate?.(id, metadata);
} }
}, },
[resolveDocumentRowKey, resolveFolderRowKey, onSelectEntry, onDocumentActivate], [onSelectEntry, onDocumentActivate],
); );
export default useEntryPointer; export default useEntryPointer;
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useCallback, useEffect, useMemo, useRef } from 'react';
import type { DragEvent } from 'react'; import type { DragEvent } from 'react';
import { createDocumentEntryKey, createFolderEntryKey } from '../../app/entryKey';
type Identifier = string | number; type Identifier = string | number;
type FolderIdentifier = string | 'root'; type FolderIdentifier = string | 'root';
@@ -30,8 +31,6 @@ interface UseDocumentDragHandlersOptions {
documentLookup: Map<Identifier, DocumentLike>; documentLookup: Map<Identifier, DocumentLike>;
setDraggedDocumentIds: (ids: Identifier[] | []) => void; setDraggedDocumentIds: (ids: Identifier[] | []) => void;
setDraggedFolderId: (id: FolderIdentifier | null) => void; setDraggedFolderId: (id: FolderIdentifier | null) => void;
resolveDocumentRowKey: (id: Identifier) => string | null;
resolveFolderRowKey: (id: FolderIdentifier) => string | null;
documentsViewMode: string; documentsViewMode: string;
} }
@@ -44,8 +43,6 @@ const useDocumentDragHandlers = ({
documentLookup, documentLookup,
setDraggedDocumentIds, setDraggedDocumentIds,
setDraggedFolderId, setDraggedFolderId,
resolveDocumentRowKey,
resolveFolderRowKey,
documentsViewMode, documentsViewMode,
}: UseDocumentDragHandlersOptions) => { }: UseDocumentDragHandlersOptions) => {
const dragPreviewRef = useRef<HTMLDivElement | null>(null); const dragPreviewRef = useRef<HTMLDivElement | null>(null);
@@ -213,7 +210,7 @@ const useDocumentDragHandlers = ({
return; return;
} }
const documentKey = resolveDocumentRowKey(documentId); const documentKey = createDocumentEntryKey(documentId);
if (!documentKey) { if (!documentKey) {
return; return;
} }
@@ -279,7 +276,6 @@ const useDocumentDragHandlers = ({
setDraggedFolderId, setDraggedFolderId,
setDraggedDocumentIds, setDraggedDocumentIds,
documentsViewMode, documentsViewMode,
resolveDocumentRowKey,
], ],
); );
@@ -300,7 +296,7 @@ const useDocumentDragHandlers = ({
return; return;
} }
event.stopPropagation(); event.stopPropagation();
const folderKey = resolveFolderRowKey(normalizedFolderId); const folderKey = createFolderEntryKey(normalizedFolderId);
const isAlreadySelected = folderKey ? selectedEntries.includes(folderKey) : false; const isAlreadySelected = folderKey ? selectedEntries.includes(folderKey) : false;
let effectiveFolderSelection: FolderIdentifier[] = normalizedFolderIds; let effectiveFolderSelection: FolderIdentifier[] = normalizedFolderIds;
@@ -309,7 +305,7 @@ const useDocumentDragHandlers = ({
if (!isAlreadySelected && folderKey) { if (!isAlreadySelected && folderKey) {
effectiveFolderSelection = [normalizedFolderId]; effectiveFolderSelection = [normalizedFolderId];
effectiveDocumentSelection = []; effectiveDocumentSelection = [];
handleEntrySelection(folderKey, { preventDefault: () => {} }); handleEntrySelection(folderKey, { preventDefault: () => { } });
} }
const uniqueFolders = effectiveFolderSelection.length const uniqueFolders = effectiveFolderSelection.length
@@ -363,7 +359,6 @@ const useDocumentDragHandlers = ({
setDraggedDocumentIds, setDraggedDocumentIds,
documentLookup, documentLookup,
createDragPreview, createDragPreview,
resolveFolderRowKey,
], ],
); );
@@ -1,7 +1,7 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import type { Dispatch, MutableRefObject, SetStateAction } from 'react'; import type { Dispatch, MutableRefObject, SetStateAction } from 'react';
import { DEFAULT_FOLDER_NAME, getRowId, isDocumentRowKey } from '../../app/workspaceUtils'; import { DEFAULT_FOLDER_NAME } from '../../app/workspaceUtils';
import { getEntryId, isDocumentEntry } from '../../app/entryKey';
import { import {
addDocumentTags, addDocumentTags,
createTag, createTag,
@@ -275,10 +275,10 @@ const useDocumentMutations = ({
const pruneRow = (collection: string[]): string[] => const pruneRow = (collection: string[]): string[] =>
collection.filter((key) => { collection.filter((key) => {
if (!isDocumentRowKey(key)) { if (!isDocumentEntry(key)) {
return true; return true;
} }
const id = getRowId(key); const id = getEntryId(key);
return id ? !uniqueIdSet.has(id as DocumentId) : true; return id ? !uniqueIdSet.has(id as DocumentId) : true;
}); });
try { try {
@@ -348,8 +348,8 @@ const useDocumentMutations = ({
selectionOrderRef.current = nextSelectionOrder; selectionOrderRef.current = nextSelectionOrder;
if ( if (
selectionAnchorRef.current && selectionAnchorRef.current &&
isDocumentRowKey(selectionAnchorRef.current) && isDocumentEntry(selectionAnchorRef.current) &&
uniqueIdSet.has(getRowId(selectionAnchorRef.current) as DocumentId) uniqueIdSet.has(getEntryId(selectionAnchorRef.current) as DocumentId)
) { ) {
selectionAnchorRef.current = null; selectionAnchorRef.current = null;
} }
@@ -358,8 +358,8 @@ const useDocumentMutations = ({
} }
if ( if (
focusedRowKey && focusedRowKey &&
isDocumentRowKey(focusedRowKey) && isDocumentEntry(focusedRowKey) &&
uniqueIdSet.has(getRowId(focusedRowKey) as DocumentId) uniqueIdSet.has(getEntryId(focusedRowKey) as DocumentId)
) { ) {
setFocusedRowKey(null); setFocusedRowKey(null);
} }
@@ -617,7 +617,7 @@ const useDocumentMutations = ({
if (!source || source.id == null) { if (!source || source.id == null) {
return null; return null;
} }
const labelText = `${source.label ?? ''}`.trim(); const labelText = `${source.label ?? ''} `.trim();
if (!labelText) { if (!labelText) {
return null; return null;
} }
@@ -29,17 +29,12 @@ import useDocumentsPanelProps from '../../documents/hooks/useDocumentsPanelProps
import useDocumentPreview from '../../app/useDocumentPreview'; import useDocumentPreview from '../../app/useDocumentPreview';
import useSidebarProps from '../../sidebar/useSidebarProps'; import useSidebarProps from '../../sidebar/useSidebarProps';
import { import {
DEFAULT_SORT_DIRECTION, DEFAULT_SORT_DIRECTION,
DEFAULT_SORT_FIELD, DEFAULT_SORT_FIELD,
createRootNode, createRootNode,
getRowId,
isDocumentRowKey,
isFolderRowKey,
mergeAssetIntoDocument, mergeAssetIntoDocument,
resolveDocumentRowKey,
resolveFolderRowKey,
} from '../../app/workspaceUtils'; } from '../../app/workspaceUtils';
import { createDocumentEntryKey, createFolderEntryKey } from '../../app/entryKey';
import useDocumentsSearch from '../../app/useDocumentsSearch'; import useDocumentsSearch from '../../app/useDocumentsSearch';
import useDocumentsStore from './store/useDocumentsStore'; import useDocumentsStore from './store/useDocumentsStore';
import useAuthManager from './useAuthManager'; import useAuthManager from './useAuthManager';
@@ -252,13 +247,7 @@ const useDocumentsWorkspace = ({
} }
const tagManager = tagManagerRef.current; const tagManager = tagManagerRef.current;
const selection = useWorkspaceSelection({ const selection = useWorkspaceSelection();
resolveDocumentRowKey,
resolveFolderRowKey,
isDocumentRowKey,
isFolderRowKey,
getRowId,
});
const { const {
selectedEntries, selectedEntries,
@@ -415,7 +404,7 @@ const useDocumentsWorkspace = ({
); );
const visibleDocumentKeys = useMemo( const visibleDocumentKeys = useMemo(
() => visibleDocumentIds.map((id) => resolveDocumentRowKey(id)).filter(Boolean), () => visibleDocumentIds.map((id) => createDocumentEntryKey(id)).filter(Boolean),
[visibleDocumentIds], [visibleDocumentIds],
); );
@@ -424,7 +413,7 @@ const useDocumentsWorkspace = ({
showingSearchResults showingSearchResults
? [] ? []
: currentSubfolders : currentSubfolders
.map((folder) => resolveFolderRowKey(folder.id)) .map((folder) => createFolderEntryKey(folder.id))
.filter(Boolean), .filter(Boolean),
[showingSearchResults, currentSubfolders], [showingSearchResults, currentSubfolders],
); );
@@ -599,8 +588,6 @@ const useDocumentsWorkspace = ({
documentLookup, documentLookup,
setDraggedDocumentIds, setDraggedDocumentIds,
setDraggedFolderId, setDraggedFolderId,
resolveDocumentRowKey,
resolveFolderRowKey,
documentsViewMode, documentsViewMode,
}); });
@@ -827,8 +814,6 @@ const useDocumentsWorkspace = ({
showingSearchResults, showingSearchResults,
currentSubfolders, currentSubfolders,
visibleDocuments: viewDocuments, visibleDocuments: viewDocuments,
resolveFolderRowKey,
resolveDocumentRowKey,
configureSelectionEnvironment, configureSelectionEnvironment,
visibleRowKeySet, visibleRowKeySet,
selectedEntries, selectedEntries,
@@ -840,7 +825,6 @@ const useDocumentsWorkspace = ({
focusedDocumentId, focusedDocumentId,
setFocusedRowKey, setFocusedRowKey,
focusedRowKey, focusedRowKey,
isFolderRowKey,
}); });
const initializeAfterLogin = useCallback(async () => { const initializeAfterLogin = useCallback(async () => {
await Promise.all([refreshTags(), refreshCorrespondents()]); await Promise.all([refreshTags(), refreshCorrespondents()]);
@@ -1191,12 +1175,10 @@ const useDocumentsWorkspace = ({
); );
const handleEntryPointerCore = useEntryPointerCore({ const handleEntryPointerCore = useEntryPointerCore({
resolveDocumentRowKey,
resolveFolderRowKey,
onSelectEntry: (entry, event, { rowKey, modifierClick, primaryClick }) => { onSelectEntry: (entry, event, { rowKey, modifierClick, primaryClick }) => {
const { type, id } = entry; const { type, id } = entry;
const key = rowKey const key = rowKey
|| (type === EntryType.document ? resolveDocumentRowKey(id) : resolveFolderRowKey(id)); || (type === EntryType.document ? createDocumentEntryKey(id) : createFolderEntryKey(id));
if (key) { if (key) {
handleEntrySelection(key, event); handleEntrySelection(key, event);
} }
@@ -1238,7 +1220,6 @@ const useDocumentsWorkspace = ({
selectedEntries, selectedEntries,
selectionAnchorRef, selectionAnchorRef,
applySelection, applySelection,
resolveDocumentRowKey,
showingSearchResults, showingSearchResults,
searchQuery, searchQuery,
activeTagFilters, activeTagFilters,
+13 -14
View File
@@ -1,14 +1,13 @@
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import type { Dispatch, MutableRefObject, SetStateAction } from 'react'; import type { Dispatch, MutableRefObject, SetStateAction } from 'react';
import { createRootNode, DEFAULT_FOLDER_NAME } from '../../app/workspaceUtils';
import { import {
DEFAULT_FOLDER_NAME, getEntryId,
createRootNode, isDocumentEntry,
getRowId, isFolderEntry,
isDocumentRowKey, createDocumentEntryKey,
isFolderRowKey, createFolderEntryKey,
resolveDocumentRowKey, } from '../../app/entryKey';
resolveFolderRowKey,
} from '../../app/workspaceUtils';
type Identifier = string | number; type Identifier = string | number;
type FolderId = Identifier | 'root'; type FolderId = Identifier | 'root';
@@ -118,12 +117,12 @@ const useFolderTree = ({
setCurrentFolder(folderInfo); setCurrentFolder(folderInfo);
const availableDocKeys = docs const availableDocKeys = docs
.map((doc) => resolveDocumentRowKey(doc?.id as Identifier)) .map((doc) => createDocumentEntryKey(doc?.id as Identifier))
.filter(Boolean); .filter(Boolean);
const availableDocKeySet = new Set(availableDocKeys); const availableDocKeySet = new Set(availableDocKeys);
const availableFolderKeys = new Set( const availableFolderKeys = new Set(
subfolders subfolders
.map((folder) => resolveFolderRowKey(folder?.id as Identifier)) .map((folder) => createFolderEntryKey(folder?.id as Identifier))
.filter(Boolean), .filter(Boolean),
); );
@@ -132,22 +131,22 @@ const useFolderTree = ({
setSelectedEntries((previous) => { setSelectedEntries((previous) => {
const previousFolderKeys = previous const previousFolderKeys = previous
.filter(isFolderRowKey) .filter(isFolderEntry)
.filter((key) => availableFolderKeys.has(key)); .filter((key) => availableFolderKeys.has(key));
const previousDocKeys = previous.filter(isDocumentRowKey); const previousDocKeys = previous.filter(isDocumentEntry);
nextDocKeys = previousDocKeys.filter((key) => availableDocKeySet.has(key)); nextDocKeys = previousDocKeys.filter((key) => availableDocKeySet.has(key));
mergedSelection = [...previousFolderKeys, ...nextDocKeys]; mergedSelection = [...previousFolderKeys, ...nextDocKeys];
return mergedSelection; return mergedSelection;
}); });
const nextFocus = (() => { const nextFocus = (() => {
const currentFocusedKey = resolveDocumentRowKey(focusedDocumentId); const currentFocusedKey = createDocumentEntryKey(focusedDocumentId);
if (currentFocusedKey && availableDocKeySet.has(currentFocusedKey)) { if (currentFocusedKey && availableDocKeySet.has(currentFocusedKey)) {
return focusedDocumentId; return focusedDocumentId;
} }
if (nextDocKeys.length) { if (nextDocKeys.length) {
const lastDocKey = nextDocKeys[nextDocKeys.length - 1]; const lastDocKey = nextDocKeys[nextDocKeys.length - 1];
return getRowId(lastDocKey) || null; return getEntryId(lastDocKey) || null;
} }
return null; return null;
})(); })();
@@ -1,16 +1,20 @@
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import type { MutableRefObject } from 'react'; import type { MutableRefObject } from 'react';
import { createDocumentEntryKey } from '../../app/entryKey';
type Identifier = string | number; type Identifier = string | number;
interface ApplySelectionFn {
(keys: string[], options?: { anchor?: string | null; interactedKeys?: string[] }): unknown;
}
interface UseWorkspaceDeskPropsArgs { interface UseWorkspaceDeskPropsArgs {
viewDocuments: any[]; viewDocuments: unknown[];
inspectDocumentForDesk: (doc: any) => void; inspectDocumentForDesk?: (id: Identifier | null) => void;
handleEntryPointer: (params: { rowKey?: string | null; id?: Identifier | null; type?: string; event?: any }) => void; handleEntryPointer: (params: { rowKey?: string | null; id?: Identifier | null; type?: string; event?: any }) => void;
selectedEntries: Array<string | number>; selectedEntries: string[];
selectionAnchorRef: MutableRefObject<Identifier | string | null>; selectionAnchorRef: MutableRefObject<Identifier | string | null>;
applySelection: (rowKeys: Array<string | number>, options?: { anchor?: Identifier | string | null; interactedKeys?: Array<string | number> }) => void; applySelection: ApplySelectionFn;
resolveDocumentRowKey: (id?: Identifier | null) => string | null;
showingSearchResults: boolean; showingSearchResults: boolean;
searchQuery: string; searchQuery: string;
activeTagFilters: Array<string | number>; activeTagFilters: Array<string | number>;
@@ -32,7 +36,6 @@ const useWorkspaceDeskProps = ({
selectedEntries, selectedEntries,
selectionAnchorRef, selectionAnchorRef,
applySelection, applySelection,
resolveDocumentRowKey,
showingSearchResults, showingSearchResults,
searchQuery, searchQuery,
activeTagFilters, activeTagFilters,
@@ -53,7 +56,7 @@ const useWorkspaceDeskProps = ({
} }
const rowKeys = docIds const rowKeys = docIds
.map((id) => resolveDocumentRowKey(id as Identifier)) .map((id) => createDocumentEntryKey(id as Identifier))
.filter((value): value is string => typeof value === 'string'); .filter((value): value is string => typeof value === 'string');
if (!rowKeys.length) { if (!rowKeys.length) {
@@ -69,14 +72,14 @@ const useWorkspaceDeskProps = ({
const anchor = (rowKeys[0] const anchor = (rowKeys[0]
|| selectionAnchorRef.current || selectionAnchorRef.current
|| nextKeys[nextKeys.length - 1]) as Identifier | string | null; || nextKeys[nextKeys.length - 1]) as string | null;
applySelection(nextKeys, { applySelection(nextKeys, {
anchor, anchor,
interactedKeys: rowKeys, interactedKeys: rowKeys,
}); });
}, },
[applySelection, resolveDocumentRowKey, selectedEntries, selectionAnchorRef], [applySelection, selectedEntries, selectionAnchorRef],
); );
const deskViewId = useMemo(() => { const deskViewId = useMemo(() => {