feat: Refactor DocumentsPanel to use a shared application shell context for state management

This commit is contained in:
2025-12-12 02:57:10 +01:00
parent 34e6c3037f
commit 6ba8084caa
7 changed files with 109 additions and 338 deletions
@@ -1,34 +1,28 @@
import { useMemo, useCallback, useRef, useEffect } from 'react';
import type { DocumentsPanelInnerProps } from './DocumentsPanel';
import { isPointerModifierEvent, isPrimaryPointerEvent } from '../features/selection/useEntryPointer';
import { useDocumentsFilter } from '../context/DocumentsFilterContext';
import { useWorkspaceSelectionContext } from '../../app/WorkspaceSelectionContext';
import type { Identifier } from '../../types/identifiers';
import { useStatusToast } from '../../lib/context/StatusToastContext';
import { useTagInteractions } from '../interactions/useTagInteractions';
import { subscribeToToast } from '../features/tagging/tagTransfer';
import { useAppShell } from '../../lib/context/AppShellContext';
const EntryType = {
folder: 'folder',
document: 'document',
};
export const useDocumentsContextValues = (props: DocumentsPanelInnerProps) => {
export const useDocumentsContextValues = () => {
const shell = useAppShell();
const {
ensureAssetUrl,
getDocumentAsset,
isSearchLoading,
searchQuery = '',
activeTagFilters = [],
activeCorrespondentFilters = [],
selectedFolder = null,
onDocumentDragStart,
onDocumentDragEnd,
onEntryPointer,
activeCorrespondentIds = [],
draggingDocumentIds = [],
tagLookupById,
} = props;
preview: { ensureAssetUrl, getDocumentAsset },
search: { isSearchLoading, searchQuery, activeTagFilters, activeCorrespondentFilters, searchResultIds, documents },
folderTree: { selectedFolder },
mutations: { handleDocumentDragStart, handleDocumentDragEnd, draggedDocumentIds: draggingDocumentIds, handleDocumentTagAttach, handleDocumentTagDetach },
selection: { handleEntryPointerCore: onEntryPointer },
correspondents: { activeCorrespondentIds, correspondentLookupById },
tags: { tagLookupById },
} = shell as any;
const {
setFocusedEntryKey,
@@ -53,8 +47,8 @@ export const useDocumentsContextValues = (props: DocumentsPanelInnerProps) => {
// Handlers
const tagHandlers = useTagInteractions({
onAssignTagToDocument: props.onDocumentTagAttach,
onRemoveTagFromDocument: props.onDocumentTagDetach,
onAssignTagToDocument: handleDocumentTagAttach,
onRemoveTagFromDocument: handleDocumentTagDetach,
onTagClick: toggleTagFilter,
});
@@ -67,14 +61,14 @@ export const useDocumentsContextValues = (props: DocumentsPanelInnerProps) => {
() => new Set(activeCorrespondentIds || []),
[activeCorrespondentIds],
);
const showingSearchResults = Array.isArray(props.searchResultIds);
const hasDocumentEntries = (props.documents || []).length > 0 || (showingSearchResults && (props.searchResultIds || []).length > 0);
const showingSearchResults = Array.isArray(searchResultIds);
const hasDocumentEntries = (documents || []).length > 0 || (showingSearchResults && (searchResultIds || []).length > 0);
const viewId = useMemo(() => {
if (showingSearchResults) {
const trimmedQuery = searchQuery.trim();
const tagsKey = [...activeTagFilters].sort().join(',');
const correspondentsKey = [...activeCorrespondentFilters].sort().join(',');
const trimmedQuery = (searchQuery || '').trim();
const tagsKey = [...(activeTagFilters || [])].sort().join(',');
const correspondentsKey = [...(activeCorrespondentFilters || [])].sort().join(',');
return `search:${trimmedQuery}|tags:${tagsKey}|corr:${correspondentsKey}`;
}
const folderKey = selectedFolder && selectedFolder !== '' ? selectedFolder : 'root';
@@ -115,19 +109,19 @@ export const useDocumentsContextValues = (props: DocumentsPanelInnerProps) => {
const handleDocumentDragStartLocal = useCallback(
(event: any, doc: any) => {
suppressDocumentClickRef.current = true;
onDocumentDragStart?.(event, doc);
handleDocumentDragStart?.(event, doc);
},
[onDocumentDragStart],
[handleDocumentDragStart],
);
const handleDocumentDragEndLocal = useCallback(
(event: any) => {
onDocumentDragEnd?.(event);
handleDocumentDragEnd?.(event);
requestAnimationFrame(() => {
suppressDocumentClickRef.current = false;
});
},
[onDocumentDragEnd],
[handleDocumentDragEnd],
);
// Context Values Construction
@@ -136,71 +130,61 @@ export const useDocumentsContextValues = (props: DocumentsPanelInnerProps) => {
getDocumentAsset,
}), [ensureAssetUrl, getDocumentAsset]);
const draggedFolderId = (shell as any).folderTree?.draggedFolderId;
const viewStateContextValue = useMemo(() => ({
viewId,
scrollRef,
tagLookupById,
correspondentLookupById: props.correspondentLookupById,
correspondentLookupById,
activeCorrespondentIdSet,
draggingDocumentIdsSet,
draggedFolderId: props.draggedFolderId,
draggedFolderId,
}), [
viewId,
scrollRef,
tagLookupById,
activeCorrespondentIdSet,
draggingDocumentIdsSet,
props.draggedFolderId,
props.correspondentLookupById,
draggedFolderId,
correspondentLookupById,
]);
// Use refs to stabilize handlers and avoid massive dependency arrays
const latestPropsRef = useRef(props);
const latestHandlersRef = useRef({
const commandContextValue = useMemo(() => {
const anyShell = shell as any;
return {
folder: {
onClick: handleFolderClick,
onSelect: anyShell.folderTree?.selectFolder,
onRename: anyShell.folderTree?.handleFolderRename,
onDrag: {
start: anyShell.folderTree?.handleFolderDragStart,
end: anyShell.folderTree?.handleFolderDragEnd,
over: anyShell.folderTree?.folderClickHandlers?.onDragOver,
leave: anyShell.folderTree?.folderClickHandlers?.onDragLeave,
drop: anyShell.folderTree?.folderClickHandlers?.onDrop,
},
},
document: {
onRename: anyShell.mutations?.handleDocumentTitleUpdate,
onDrag: {
start: handleDocumentDragStartLocal,
end: handleDocumentDragEndLocal,
},
},
correspondents: {
onClick: toggleCorrespondentFilter,
},
onEntryPointer,
}
}, [
handleFolderClick,
shell,
handleDocumentDragStartLocal,
handleDocumentDragEndLocal,
tagHandlers,
toggleTagFilter,
toggleCorrespondentFilter,
});
// Update refs on every render
latestPropsRef.current = props;
latestHandlersRef.current = {
handleFolderClick,
handleDocumentDragStartLocal,
handleDocumentDragEndLocal,
tagHandlers,
toggleTagFilter,
toggleCorrespondentFilter,
};
const commandContextValue = useMemo(() => ({
folder: {
onClick: (f: any, e: any) => latestHandlersRef.current.handleFolderClick(f, e),
onSelect: (id: Identifier) => latestPropsRef.current.onFolderSelect?.(id),
onRename: (id: Identifier, name: string) => latestPropsRef.current.onFolderRename?.(id, name),
onDrag: {
start: (e: any, f: any) => latestPropsRef.current.onFolderDragStart?.(e, f),
end: (e: any) => latestPropsRef.current.onFolderDragEnd?.(e),
over: (e: any, f: any) => latestPropsRef.current.onFolderDragOver?.(e, f),
leave: (e: any) => latestPropsRef.current.onFolderDragLeave?.(e),
drop: (e: any, f: any) => latestPropsRef.current.onFolderDrop?.(e, f),
},
},
document: {
onRename: (id: Identifier, name: string) => latestPropsRef.current.onDocumentRename?.(id, name),
onDrag: {
start: (e: any, d: any) => latestHandlersRef.current.handleDocumentDragStartLocal(e, d),
end: (e: any) => latestHandlersRef.current.handleDocumentDragEndLocal(e),
},
},
correspondents: {
onClick: (c: any) => latestHandlersRef.current.toggleCorrespondentFilter(c),
},
onEntryPointer: (entry: any, e: any) => latestPropsRef.current.onEntryPointer?.(entry, e),
}), []);
onEntryPointer,
]);
return {
assetContextValue,