feat: Refactor DocumentsPanel to use a shared application shell context for state management
This commit is contained in:
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import type { useWorkspaceSelection } from './useWorkspaceSelection';
|
import type { useWorkspaceSelection } from './useWorkspaceSelection';
|
||||||
import { createSafeContext } from '../utils/createSafeContext';
|
import { createSafeContext } from '../utils/createSafeContext';
|
||||||
|
|
||||||
export type WorkspaceSelectionValue = ReturnType<typeof useWorkspaceSelection>;
|
type WorkspaceSelectionValue = ReturnType<typeof useWorkspaceSelection>;
|
||||||
|
|
||||||
const [WorkspaceSelectionContext, useWorkspaceSelectionContext] = createSafeContext<WorkspaceSelectionValue>('WorkspaceSelection');
|
const [WorkspaceSelectionContext, useWorkspaceSelectionContext] = createSafeContext<WorkspaceSelectionValue>('WorkspaceSelection');
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import type { DocumentsFilterValue } from '../documents/context/DocumentsFilterC
|
|||||||
import type { UseWorkspaceSurfaceArgs } from './useWorkspaceSurface';
|
import type { UseWorkspaceSurfaceArgs } from './useWorkspaceSurface';
|
||||||
import type { Identifier } from '../types/identifiers';
|
import type { Identifier } from '../types/identifiers';
|
||||||
|
|
||||||
import useDocumentsPanelProps from '../documents/logic/useDocumentsPanelProps';
|
|
||||||
|
|
||||||
type WorkspaceSurfaceConfig = Omit<UseWorkspaceSurfaceArgs, 'sidebarHidden' | 'onExpandSidebar'> & {
|
type WorkspaceSurfaceConfig = Omit<UseWorkspaceSurfaceArgs, 'sidebarHidden' | 'onExpandSidebar'> & {
|
||||||
openDetailPanel?: (documentId: Identifier) => void;
|
openDetailPanel?: (documentId: Identifier) => void;
|
||||||
closeDetailPanel?: () => void;
|
closeDetailPanel?: () => void;
|
||||||
@@ -21,11 +19,10 @@ interface DocumentsShellView {
|
|||||||
|
|
||||||
const useDocumentsShell = (): DocumentsShellView => {
|
const useDocumentsShell = (): DocumentsShellView => {
|
||||||
const shell = useAppShell() as any;
|
const shell = useAppShell() as any;
|
||||||
const documentsPanelProps = useDocumentsPanelProps(shell as any);
|
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const surfaceConfig: WorkspaceSurfaceConfig = {
|
const surfaceConfig: WorkspaceSurfaceConfig = {
|
||||||
documentsTableProps: documentsPanelProps,
|
documentsTableProps: {},
|
||||||
detailPanelProps: (shell.detailPanel?.detailPanelProps ?? null) as WorkspaceSurfaceConfig['detailPanelProps'],
|
detailPanelProps: (shell.detailPanel?.detailPanelProps ?? null) as WorkspaceSurfaceConfig['detailPanelProps'],
|
||||||
detailPanelOpen: Boolean(shell.detailPanel?.detailPanelOpen),
|
detailPanelOpen: Boolean(shell.detailPanel?.detailPanelOpen),
|
||||||
openDetailPanel: shell.detailPanel?.openDetailPanel as WorkspaceSurfaceConfig['openDetailPanel'],
|
openDetailPanel: shell.detailPanel?.openDetailPanel as WorkspaceSurfaceConfig['openDetailPanel'],
|
||||||
@@ -46,7 +43,7 @@ const useDocumentsShell = (): DocumentsShellView => {
|
|||||||
documentsManager: shell.managers?.documentsManager,
|
documentsManager: shell.managers?.documentsManager,
|
||||||
foldersManager: shell.folderTree?.foldersManager,
|
foldersManager: shell.folderTree?.foldersManager,
|
||||||
};
|
};
|
||||||
}, [shell, documentsPanelProps]);
|
}, [shell]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useDocumentsShell;
|
export default useDocumentsShell;
|
||||||
|
|||||||
@@ -150,7 +150,6 @@ export const useWorkspaceSurface = ({
|
|||||||
return {
|
return {
|
||||||
content: (
|
content: (
|
||||||
<DocumentsPanel
|
<DocumentsPanel
|
||||||
{...documentsTableProps}
|
|
||||||
headerLeading={sidebarToggle}
|
headerLeading={sidebarToggle}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,192 +0,0 @@
|
|||||||
import type { WorkspaceSelectionValue } from '../../app/WorkspaceSelectionContext';
|
|
||||||
import type { Identifier } from '../../types/identifiers';
|
|
||||||
|
|
||||||
interface Breadcrumb {
|
|
||||||
id?: Identifier;
|
|
||||||
name?: string;
|
|
||||||
label?: string;
|
|
||||||
title?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FolderClickHandlers {
|
|
||||||
onDrop?: (...args: unknown[]) => void;
|
|
||||||
onDragOver?: (...args: unknown[]) => void;
|
|
||||||
onDragLeave?: (...args: unknown[]) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UseDocumentsPanelPropsArgs {
|
|
||||||
currentFolderName?: string | null;
|
|
||||||
breadcrumbs?: Breadcrumb[];
|
|
||||||
refreshCurrentFolder?: () => void | Promise<void>;
|
|
||||||
currentSubfolders?: unknown[];
|
|
||||||
documents?: unknown[];
|
|
||||||
searchQuery?: string;
|
|
||||||
searchResultIds?: Identifier[] | null;
|
|
||||||
folderClickHandlers: FolderClickHandlers;
|
|
||||||
selectedFolder?: Identifier | 'root' | null;
|
|
||||||
selectFolder?: (...args: unknown[]) => void;
|
|
||||||
handleFolderDragStart?: (...args: unknown[]) => void;
|
|
||||||
handleFolderDragEnd?: (...args: unknown[]) => void;
|
|
||||||
draggedFolderId?: Identifier | null;
|
|
||||||
handleFolderRename?: (...args: unknown[]) => void;
|
|
||||||
handleDocumentTitleUpdate?: (...args: unknown[]) => void;
|
|
||||||
focusedRowKey?: Identifier | string | null;
|
|
||||||
draggedDocumentIds?: Identifier[];
|
|
||||||
handleDocumentDragStart?: (...args: unknown[]) => void;
|
|
||||||
handleDocumentDragEnd?: (...args: unknown[]) => void;
|
|
||||||
searchLoading?: boolean;
|
|
||||||
tagLookupById?: unknown;
|
|
||||||
activeTagFilters?: Identifier[];
|
|
||||||
activeCorrespondentFilters?: Identifier[];
|
|
||||||
ensureAssetUrl?: (...args: unknown[]) => void;
|
|
||||||
getDocumentAsset?: (...args: unknown[]) => unknown;
|
|
||||||
handleDocumentTagAttach?: (docId: Identifier, tagId: Identifier) => void;
|
|
||||||
handleDocumentTagDetach?: (docId: Identifier, tagId: Identifier) => void;
|
|
||||||
documentsViewMode?: string;
|
|
||||||
documentsSortField?: string;
|
|
||||||
documentsSortDirection?: string;
|
|
||||||
handleDocumentsSortFieldChange?: (field: string) => void;
|
|
||||||
handleDocumentsSortDirectionToggle?: () => void;
|
|
||||||
handleDocumentsViewModeChange?: (mode: string) => void;
|
|
||||||
clearDocumentSelection?: () => void;
|
|
||||||
handleDeleteSelection?: () => void;
|
|
||||||
handleEntryPointerCore?: (...args: unknown[]) => void;
|
|
||||||
tags?: unknown[];
|
|
||||||
correspondents?: unknown[];
|
|
||||||
correspondentLookupById?: unknown;
|
|
||||||
handleBulkTagAddFromDetail?: (...args: unknown[]) => void;
|
|
||||||
handleBulkTagRemoveFromDetail?: (...args: unknown[]) => void;
|
|
||||||
handleBulkCorrespondentAdd?: (...args: unknown[]) => void;
|
|
||||||
handleBulkCorrespondentRemove?: (...args: unknown[]) => void;
|
|
||||||
handleBulkSelectionReanalyze?: (...args: unknown[]) => void;
|
|
||||||
folderOptions?: unknown[];
|
|
||||||
moveDocumentsToFolder?: (...args: unknown[]) => void;
|
|
||||||
documentLookup?: unknown;
|
|
||||||
selectionValue: WorkspaceSelectionValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useDocumentsPanelProps = (props: UseDocumentsPanelPropsArgs) => {
|
|
||||||
const {
|
|
||||||
folderTree: {
|
|
||||||
currentFolderName,
|
|
||||||
breadcrumbs,
|
|
||||||
currentSubfolders,
|
|
||||||
folderClickHandlers,
|
|
||||||
selectedFolder,
|
|
||||||
selectFolder,
|
|
||||||
handleFolderDragStart,
|
|
||||||
handleFolderDragEnd,
|
|
||||||
draggedFolderId,
|
|
||||||
handleFolderRename,
|
|
||||||
folderOptions,
|
|
||||||
moveDocumentsToFolder,
|
|
||||||
} = {},
|
|
||||||
search: {
|
|
||||||
documents,
|
|
||||||
searchQuery,
|
|
||||||
searchResultIds,
|
|
||||||
searchLoading,
|
|
||||||
documentsViewMode,
|
|
||||||
documentsSortField,
|
|
||||||
documentsSortDirection,
|
|
||||||
handleDocumentsSortFieldChange,
|
|
||||||
handleDocumentsSortDirectionToggle,
|
|
||||||
handleDocumentsViewModeChange,
|
|
||||||
} = {},
|
|
||||||
ui: {
|
|
||||||
refreshCurrentFolder,
|
|
||||||
} = {},
|
|
||||||
mutations: {
|
|
||||||
handleDocumentTitleUpdate,
|
|
||||||
draggedDocumentIds,
|
|
||||||
handleDocumentDragStart,
|
|
||||||
handleDocumentDragEnd,
|
|
||||||
} = {},
|
|
||||||
selection: {
|
|
||||||
selectionValue,
|
|
||||||
handleDeleteSelection,
|
|
||||||
handleEntryPointerCore,
|
|
||||||
handleBulkSelectionReanalyze,
|
|
||||||
} = {},
|
|
||||||
tags: {
|
|
||||||
tagLookupById,
|
|
||||||
activeTagFilters,
|
|
||||||
tags,
|
|
||||||
handleDocumentTagAttach,
|
|
||||||
handleDocumentTagDetach,
|
|
||||||
handleBulkTagAddFromDetail,
|
|
||||||
handleBulkTagRemoveFromDetail,
|
|
||||||
} = {},
|
|
||||||
correspondents: {
|
|
||||||
activeCorrespondentFilters,
|
|
||||||
correspondents,
|
|
||||||
correspondentLookupById,
|
|
||||||
handleBulkCorrespondentAdd,
|
|
||||||
handleBulkCorrespondentRemove,
|
|
||||||
} = {},
|
|
||||||
preview: {
|
|
||||||
ensureAssetUrl,
|
|
||||||
getDocumentAsset,
|
|
||||||
} = {},
|
|
||||||
managers: {
|
|
||||||
documentLookup,
|
|
||||||
} = {},
|
|
||||||
} = props as any;
|
|
||||||
|
|
||||||
const focusedRowKey = selectionValue?.focusedEntryKey || null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
currentFolderName,
|
|
||||||
breadcrumbs,
|
|
||||||
onRefresh: refreshCurrentFolder,
|
|
||||||
subfolders: currentSubfolders,
|
|
||||||
documents,
|
|
||||||
searchQuery,
|
|
||||||
searchResultIds,
|
|
||||||
selectedFolder,
|
|
||||||
onFolderSelect: selectFolder,
|
|
||||||
onFolderDrop: folderClickHandlers.onDrop,
|
|
||||||
onFolderDragOver: folderClickHandlers.onDragOver,
|
|
||||||
onFolderDragLeave: folderClickHandlers.onDragLeave,
|
|
||||||
onFolderDragStart: handleFolderDragStart,
|
|
||||||
onFolderDragEnd: handleFolderDragEnd,
|
|
||||||
draggedFolderId,
|
|
||||||
onFolderRename: handleFolderRename,
|
|
||||||
onDocumentRename: handleDocumentTitleUpdate,
|
|
||||||
focusedRowKey,
|
|
||||||
draggingDocumentIds: draggedDocumentIds,
|
|
||||||
onDocumentDragStart: handleDocumentDragStart,
|
|
||||||
onDocumentDragEnd: handleDocumentDragEnd,
|
|
||||||
isSearchLoading: searchLoading,
|
|
||||||
tagLookupById,
|
|
||||||
activeTagFilters,
|
|
||||||
activeCorrespondentFilters,
|
|
||||||
activeCorrespondentIds: activeCorrespondentFilters,
|
|
||||||
ensureAssetUrl,
|
|
||||||
getDocumentAsset,
|
|
||||||
onDocumentTagAttach: handleDocumentTagAttach,
|
|
||||||
onDocumentTagDetach: handleDocumentTagDetach,
|
|
||||||
viewMode: documentsViewMode,
|
|
||||||
sortField: documentsSortField,
|
|
||||||
sortDirection: documentsSortDirection,
|
|
||||||
onSortFieldChange: handleDocumentsSortFieldChange,
|
|
||||||
onSortDirectionToggle: handleDocumentsSortDirectionToggle,
|
|
||||||
onViewModeChange: handleDocumentsViewModeChange,
|
|
||||||
onDeleteSelection: handleDeleteSelection,
|
|
||||||
onEntryPointer: handleEntryPointerCore,
|
|
||||||
tags,
|
|
||||||
correspondents,
|
|
||||||
correspondentLookupById,
|
|
||||||
documentLookup,
|
|
||||||
onBulkTagAdd: handleBulkTagAddFromDetail,
|
|
||||||
onBulkTagRemove: handleBulkTagRemoveFromDetail,
|
|
||||||
onBulkCorrespondentAdd: handleBulkCorrespondentAdd,
|
|
||||||
onBulkCorrespondentRemove: handleBulkCorrespondentRemove,
|
|
||||||
onBulkReanalyze: handleBulkSelectionReanalyze,
|
|
||||||
folderOptions,
|
|
||||||
onMoveDocumentsToFolder: moveDocumentsToFolder,
|
|
||||||
selectionValue,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useDocumentsPanelProps;
|
|
||||||
@@ -1,27 +1,29 @@
|
|||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
|
||||||
useMemo,
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
useRef,
|
useRef,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useCallback,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import TagRemovalZone from '../components/TagRemovalZone';
|
import TagRemovalZone from '../components/TagRemovalZone';
|
||||||
import { DocumentsList, DocumentsGrid } from '../DocumentsView';
|
import { DocumentsList, DocumentsGrid } from '../DocumentsView';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import type {
|
import type {
|
||||||
DocumentsListEntry,
|
DocumentsListEntry,
|
||||||
Correspondent,
|
|
||||||
} from '../../types/documents';
|
} from '../../types/documents';
|
||||||
import type { Identifier } from '../../types/identifiers';
|
|
||||||
|
const EntryType = {
|
||||||
|
folder: 'folder' as const,
|
||||||
|
document: 'document' as const,
|
||||||
|
};
|
||||||
|
|
||||||
import DesktopWorkspace from '../../desktop/components/DesktopWorkspace';
|
import DesktopWorkspace from '../../desktop/components/DesktopWorkspace';
|
||||||
import {
|
import {
|
||||||
WorkspaceSelectionProvider,
|
WorkspaceSelectionProvider,
|
||||||
useWorkspaceSelectionContext,
|
useWorkspaceSelectionContext,
|
||||||
type WorkspaceSelectionValue,
|
|
||||||
} from '../../app/WorkspaceSelectionContext';
|
} from '../../app/WorkspaceSelectionContext';
|
||||||
import DocumentsPanelHeader, {
|
import DocumentsPanelHeader, {
|
||||||
DocumentsPanelHeaderConfig,
|
DocumentsPanelHeaderConfig,
|
||||||
DocumentsHeaderBreadcrumb,
|
|
||||||
} from './DocumentsPanelHeader';
|
} from './DocumentsPanelHeader';
|
||||||
import { SelectionFloatingPanel } from '../features/selection/SelectionFloatingActions';
|
import { SelectionFloatingPanel } from '../features/selection/SelectionFloatingActions';
|
||||||
import { createDocumentsTableHeaderActions } from './DocumentsToolbar';
|
import { createDocumentsTableHeaderActions } from './DocumentsToolbar';
|
||||||
@@ -35,58 +37,31 @@ import { DocumentsAssetContext } from '../context/DocumentsAssetContext';
|
|||||||
import { DocumentsViewStateContext } from '../context/DocumentsViewStateContext';
|
import { DocumentsViewStateContext } from '../context/DocumentsViewStateContext';
|
||||||
import { DocumentsCommandContext } from '../context/DocumentsCommandContext';
|
import { DocumentsCommandContext } from '../context/DocumentsCommandContext';
|
||||||
import { useDocumentsContextValues } from './useDocumentsContextValues';
|
import { useDocumentsContextValues } from './useDocumentsContextValues';
|
||||||
|
import { useAppShell } from '../../lib/context/AppShellContext';
|
||||||
const EntryType = {
|
|
||||||
folder: 'folder' as const,
|
|
||||||
document: 'document' as const,
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface DocumentsPanelInnerProps {
|
|
||||||
headerLeading?: ReactNode;
|
|
||||||
onBreadcrumbNavigate?: (crumb: DocumentsHeaderBreadcrumb) => void;
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DocumentsPanelProps extends DocumentsPanelInnerProps {
|
|
||||||
selectionValue: WorkspaceSelectionValue;
|
|
||||||
correspondentLookupById?: Map<Identifier, Correspondent>;
|
|
||||||
}
|
|
||||||
|
|
||||||
import type { TagInteractionHandlers } from '../interactions/useTagInteractions';
|
import type { TagInteractionHandlers } from '../interactions/useTagInteractions';
|
||||||
|
|
||||||
export interface DocumentsViewProps {
|
export interface DocumentsViewProps {
|
||||||
entries: DocumentsListEntry[];
|
entries: DocumentsListEntry[];
|
||||||
tagHandlers?: TagInteractionHandlers;
|
tagHandlers?: TagInteractionHandlers;
|
||||||
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = (props) => {
|
interface DocumentsPanelProps {
|
||||||
|
headerLeading?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DocumentsPanelInner: React.FC<DocumentsPanelProps> = (props) => {
|
||||||
|
const { headerLeading } = props;
|
||||||
|
const shell = useAppShell();
|
||||||
const {
|
const {
|
||||||
headerLeading = null,
|
search: { documents, searchResultIds, documentsViewMode: viewMode, documentsSortField: sortField, documentsSortDirection: sortDirection, handleDocumentsViewModeChange: onViewModeChange, handleDocumentsSortFieldChange: onSortFieldChange, handleDocumentsSortDirectionToggle: onSortDirectionToggle },
|
||||||
onBreadcrumbNavigate,
|
folderTree: { currentFolderName, breadcrumbs, currentSubfolders: subfolders, folderOptions, moveDocumentsToFolder: onMoveDocumentsToFolder, refreshCurrentFolder: onRefresh, handleBreadcrumbNavigate: onBreadcrumbNavigate },
|
||||||
currentFolderName,
|
selection: { handleDeleteSelection: onDeleteSelection },
|
||||||
breadcrumbs,
|
managers: { documentLookup },
|
||||||
subfolders,
|
tags: { tags, handleBulkTagAddFromDetail: onBulkTagAdd, handleBulkTagRemoveFromDetail: onBulkTagRemove },
|
||||||
documents,
|
correspondents: { correspondents, handleBulkCorrespondentAdd: onBulkCorrespondentAdd, handleBulkCorrespondentRemove: onBulkCorrespondentRemove },
|
||||||
searchResultIds,
|
mutations: { handleBulkSelectionReanalyze: onBulkReanalyze },
|
||||||
onRefresh = () => { },
|
} = shell as any;
|
||||||
sortField,
|
|
||||||
sortDirection,
|
|
||||||
onSortFieldChange,
|
|
||||||
onSortDirectionToggle,
|
|
||||||
onDeleteSelection,
|
|
||||||
documentLookup,
|
|
||||||
tags,
|
|
||||||
correspondents,
|
|
||||||
onBulkTagAdd,
|
|
||||||
onBulkTagRemove,
|
|
||||||
onBulkCorrespondentAdd,
|
|
||||||
onBulkCorrespondentRemove,
|
|
||||||
onBulkReanalyze,
|
|
||||||
folderOptions,
|
|
||||||
onMoveDocumentsToFolder,
|
|
||||||
viewMode = 'list',
|
|
||||||
onViewModeChange,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
assetContextValue,
|
assetContextValue,
|
||||||
@@ -95,7 +70,7 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = (props) => {
|
|||||||
tagHandlers,
|
tagHandlers,
|
||||||
scrollRef,
|
scrollRef,
|
||||||
hasDocumentEntries,
|
hasDocumentEntries,
|
||||||
} = useDocumentsContextValues(props);
|
} = useDocumentsContextValues();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
clearSelection,
|
clearSelection,
|
||||||
@@ -119,7 +94,6 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = (props) => {
|
|||||||
const showingSearchResults = Array.isArray(searchResultIds);
|
const showingSearchResults = Array.isArray(searchResultIds);
|
||||||
const rows = showingSearchResults && searchDocuments ? searchDocuments : documents;
|
const rows = showingSearchResults && searchDocuments ? searchDocuments : documents;
|
||||||
|
|
||||||
|
|
||||||
const headerTitle = showingSearchResults
|
const headerTitle = showingSearchResults
|
||||||
? 'Search results'
|
? 'Search results'
|
||||||
: currentFolderName || 'Documents';
|
: currentFolderName || 'Documents';
|
||||||
@@ -150,13 +124,16 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = (props) => {
|
|||||||
toggleIncludeDescendants,
|
toggleIncludeDescendants,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { tagLookupById, correspondentLookupById } = (shell as any).tags;
|
||||||
|
|
||||||
const floatingActions = useMemo(() => (
|
const floatingActions = useMemo(() => (
|
||||||
<SelectionFloatingPanel
|
<SelectionFloatingPanel
|
||||||
documentLookup={documentLookup}
|
documentLookup={documentLookup}
|
||||||
tags={tags}
|
tags={tags}
|
||||||
tagLookupById={props.tagLookupById}
|
tagLookupById={tagLookupById}
|
||||||
correspondents={correspondents}
|
correspondents={correspondents}
|
||||||
correspondentLookupById={props.correspondentLookupById}
|
correspondentLookupById={correspondentLookupById}
|
||||||
onBulkTagAdd={onBulkTagAdd}
|
onBulkTagAdd={onBulkTagAdd}
|
||||||
onBulkTagRemove={onBulkTagRemove}
|
onBulkTagRemove={onBulkTagRemove}
|
||||||
onBulkCorrespondentAdd={onBulkCorrespondentAdd}
|
onBulkCorrespondentAdd={onBulkCorrespondentAdd}
|
||||||
@@ -170,9 +147,9 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = (props) => {
|
|||||||
), [
|
), [
|
||||||
documentLookup,
|
documentLookup,
|
||||||
tags,
|
tags,
|
||||||
props.tagLookupById,
|
tagLookupById,
|
||||||
correspondents,
|
correspondents,
|
||||||
props.correspondentLookupById,
|
correspondentLookupById,
|
||||||
onBulkTagAdd,
|
onBulkTagAdd,
|
||||||
onBulkTagRemove,
|
onBulkTagRemove,
|
||||||
onBulkCorrespondentAdd,
|
onBulkCorrespondentAdd,
|
||||||
@@ -183,6 +160,7 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = (props) => {
|
|||||||
onMoveDocumentsToFolder,
|
onMoveDocumentsToFolder,
|
||||||
clearSelection,
|
clearSelection,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const headerConfig: DocumentsPanelHeaderConfig = useMemo(() => ({
|
const headerConfig: DocumentsPanelHeaderConfig = useMemo(() => ({
|
||||||
title: headerTitle,
|
title: headerTitle,
|
||||||
subtitle: null,
|
subtitle: null,
|
||||||
@@ -230,14 +208,14 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = (props) => {
|
|||||||
const entries = useMemo(() => {
|
const entries = useMemo(() => {
|
||||||
const list: DocumentsListEntry[] = []; // Explicit type
|
const list: DocumentsListEntry[] = []; // Explicit type
|
||||||
if (!showingSearchResults) {
|
if (!showingSearchResults) {
|
||||||
subfolders.forEach((folder: any) => {
|
(subfolders || []).forEach((folder: any) => {
|
||||||
if (!folder || !folder.id) {
|
if (!folder || !folder.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
list.push({ type: EntryType.folder, id: folder.id, key: `folder:${folder.id}`, folder });
|
list.push({ type: EntryType.folder, id: folder.id, key: `folder:${folder.id}`, folder });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
rows.forEach((doc: any) => {
|
(rows || []).forEach((doc: any) => {
|
||||||
if (!doc || !doc.id) {
|
if (!doc || !doc.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -261,7 +239,7 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = (props) => {
|
|||||||
desk: DEFAULT_DESKTOP_CARD_SIZE,
|
desk: DEFAULT_DESKTOP_CARD_SIZE,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isSearchLoading = props.isSearchLoading || false;
|
const isSearchLoading = (shell as any).search?.isSearchLoading || false;
|
||||||
|
|
||||||
const renderBody = () => {
|
const renderBody = () => {
|
||||||
const hasEntries = entries.length > 0;
|
const hasEntries = entries.length > 0;
|
||||||
@@ -328,10 +306,15 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const DocumentsPanel: React.FC<DocumentsPanelProps> = ({ selectionValue, ...rest }) => (
|
const DocumentsPanel: React.FC<DocumentsPanelProps> = (props) => {
|
||||||
<WorkspaceSelectionProvider value={selectionValue}>
|
const shell = useAppShell();
|
||||||
<DocumentsPanelInner {...rest} />
|
const selectionValue = (shell as any).selection?.selectionValue;
|
||||||
</WorkspaceSelectionProvider>
|
|
||||||
);
|
return (
|
||||||
|
<WorkspaceSelectionProvider value={selectionValue}>
|
||||||
|
<DocumentsPanelInner {...props} />
|
||||||
|
</WorkspaceSelectionProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default DocumentsPanel;
|
export default DocumentsPanel;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import PanelHeader from '../../components/PanelHeader';
|
|||||||
import BreadcrumbTrail from '../../components/BreadcrumbTrail';
|
import BreadcrumbTrail from '../../components/BreadcrumbTrail';
|
||||||
import type { Identifier } from '../../types/identifiers';
|
import type { Identifier } from '../../types/identifiers';
|
||||||
|
|
||||||
export interface DocumentsHeaderBreadcrumb {
|
interface DocumentsHeaderBreadcrumb {
|
||||||
id?: Identifier;
|
id?: Identifier;
|
||||||
name?: string;
|
name?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
|
|||||||
@@ -1,34 +1,28 @@
|
|||||||
import { useMemo, useCallback, useRef, useEffect } from 'react';
|
import { useMemo, useCallback, useRef, useEffect } from 'react';
|
||||||
import type { DocumentsPanelInnerProps } from './DocumentsPanel';
|
|
||||||
import { isPointerModifierEvent, isPrimaryPointerEvent } from '../features/selection/useEntryPointer';
|
import { isPointerModifierEvent, isPrimaryPointerEvent } from '../features/selection/useEntryPointer';
|
||||||
import { useDocumentsFilter } from '../context/DocumentsFilterContext';
|
import { useDocumentsFilter } from '../context/DocumentsFilterContext';
|
||||||
import { useWorkspaceSelectionContext } from '../../app/WorkspaceSelectionContext';
|
import { useWorkspaceSelectionContext } from '../../app/WorkspaceSelectionContext';
|
||||||
import type { Identifier } from '../../types/identifiers';
|
|
||||||
import { useStatusToast } from '../../lib/context/StatusToastContext';
|
import { useStatusToast } from '../../lib/context/StatusToastContext';
|
||||||
import { useTagInteractions } from '../interactions/useTagInteractions';
|
import { useTagInteractions } from '../interactions/useTagInteractions';
|
||||||
import { subscribeToToast } from '../features/tagging/tagTransfer';
|
import { subscribeToToast } from '../features/tagging/tagTransfer';
|
||||||
|
import { useAppShell } from '../../lib/context/AppShellContext';
|
||||||
|
|
||||||
const EntryType = {
|
const EntryType = {
|
||||||
folder: 'folder',
|
folder: 'folder',
|
||||||
document: 'document',
|
document: 'document',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useDocumentsContextValues = (props: DocumentsPanelInnerProps) => {
|
export const useDocumentsContextValues = () => {
|
||||||
|
const shell = useAppShell();
|
||||||
const {
|
const {
|
||||||
ensureAssetUrl,
|
preview: { ensureAssetUrl, getDocumentAsset },
|
||||||
getDocumentAsset,
|
search: { isSearchLoading, searchQuery, activeTagFilters, activeCorrespondentFilters, searchResultIds, documents },
|
||||||
isSearchLoading,
|
folderTree: { selectedFolder },
|
||||||
searchQuery = '',
|
mutations: { handleDocumentDragStart, handleDocumentDragEnd, draggedDocumentIds: draggingDocumentIds, handleDocumentTagAttach, handleDocumentTagDetach },
|
||||||
activeTagFilters = [],
|
selection: { handleEntryPointerCore: onEntryPointer },
|
||||||
activeCorrespondentFilters = [],
|
correspondents: { activeCorrespondentIds, correspondentLookupById },
|
||||||
selectedFolder = null,
|
tags: { tagLookupById },
|
||||||
onDocumentDragStart,
|
} = shell as any;
|
||||||
onDocumentDragEnd,
|
|
||||||
onEntryPointer,
|
|
||||||
activeCorrespondentIds = [],
|
|
||||||
draggingDocumentIds = [],
|
|
||||||
tagLookupById,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
setFocusedEntryKey,
|
setFocusedEntryKey,
|
||||||
@@ -53,8 +47,8 @@ export const useDocumentsContextValues = (props: DocumentsPanelInnerProps) => {
|
|||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
const tagHandlers = useTagInteractions({
|
const tagHandlers = useTagInteractions({
|
||||||
onAssignTagToDocument: props.onDocumentTagAttach,
|
onAssignTagToDocument: handleDocumentTagAttach,
|
||||||
onRemoveTagFromDocument: props.onDocumentTagDetach,
|
onRemoveTagFromDocument: handleDocumentTagDetach,
|
||||||
onTagClick: toggleTagFilter,
|
onTagClick: toggleTagFilter,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -67,14 +61,14 @@ export const useDocumentsContextValues = (props: DocumentsPanelInnerProps) => {
|
|||||||
() => new Set(activeCorrespondentIds || []),
|
() => new Set(activeCorrespondentIds || []),
|
||||||
[activeCorrespondentIds],
|
[activeCorrespondentIds],
|
||||||
);
|
);
|
||||||
const showingSearchResults = Array.isArray(props.searchResultIds);
|
const showingSearchResults = Array.isArray(searchResultIds);
|
||||||
const hasDocumentEntries = (props.documents || []).length > 0 || (showingSearchResults && (props.searchResultIds || []).length > 0);
|
const hasDocumentEntries = (documents || []).length > 0 || (showingSearchResults && (searchResultIds || []).length > 0);
|
||||||
|
|
||||||
const viewId = useMemo(() => {
|
const viewId = useMemo(() => {
|
||||||
if (showingSearchResults) {
|
if (showingSearchResults) {
|
||||||
const trimmedQuery = searchQuery.trim();
|
const trimmedQuery = (searchQuery || '').trim();
|
||||||
const tagsKey = [...activeTagFilters].sort().join(',');
|
const tagsKey = [...(activeTagFilters || [])].sort().join(',');
|
||||||
const correspondentsKey = [...activeCorrespondentFilters].sort().join(',');
|
const correspondentsKey = [...(activeCorrespondentFilters || [])].sort().join(',');
|
||||||
return `search:${trimmedQuery}|tags:${tagsKey}|corr:${correspondentsKey}`;
|
return `search:${trimmedQuery}|tags:${tagsKey}|corr:${correspondentsKey}`;
|
||||||
}
|
}
|
||||||
const folderKey = selectedFolder && selectedFolder !== '' ? selectedFolder : 'root';
|
const folderKey = selectedFolder && selectedFolder !== '' ? selectedFolder : 'root';
|
||||||
@@ -115,19 +109,19 @@ export const useDocumentsContextValues = (props: DocumentsPanelInnerProps) => {
|
|||||||
const handleDocumentDragStartLocal = useCallback(
|
const handleDocumentDragStartLocal = useCallback(
|
||||||
(event: any, doc: any) => {
|
(event: any, doc: any) => {
|
||||||
suppressDocumentClickRef.current = true;
|
suppressDocumentClickRef.current = true;
|
||||||
onDocumentDragStart?.(event, doc);
|
handleDocumentDragStart?.(event, doc);
|
||||||
},
|
},
|
||||||
[onDocumentDragStart],
|
[handleDocumentDragStart],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDocumentDragEndLocal = useCallback(
|
const handleDocumentDragEndLocal = useCallback(
|
||||||
(event: any) => {
|
(event: any) => {
|
||||||
onDocumentDragEnd?.(event);
|
handleDocumentDragEnd?.(event);
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
suppressDocumentClickRef.current = false;
|
suppressDocumentClickRef.current = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[onDocumentDragEnd],
|
[handleDocumentDragEnd],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Context Values Construction
|
// Context Values Construction
|
||||||
@@ -136,71 +130,61 @@ export const useDocumentsContextValues = (props: DocumentsPanelInnerProps) => {
|
|||||||
getDocumentAsset,
|
getDocumentAsset,
|
||||||
}), [ensureAssetUrl, getDocumentAsset]);
|
}), [ensureAssetUrl, getDocumentAsset]);
|
||||||
|
|
||||||
|
const draggedFolderId = (shell as any).folderTree?.draggedFolderId;
|
||||||
|
|
||||||
const viewStateContextValue = useMemo(() => ({
|
const viewStateContextValue = useMemo(() => ({
|
||||||
viewId,
|
viewId,
|
||||||
scrollRef,
|
scrollRef,
|
||||||
tagLookupById,
|
tagLookupById,
|
||||||
correspondentLookupById: props.correspondentLookupById,
|
correspondentLookupById,
|
||||||
activeCorrespondentIdSet,
|
activeCorrespondentIdSet,
|
||||||
draggingDocumentIdsSet,
|
draggingDocumentIdsSet,
|
||||||
draggedFolderId: props.draggedFolderId,
|
draggedFolderId,
|
||||||
}), [
|
}), [
|
||||||
viewId,
|
viewId,
|
||||||
scrollRef,
|
scrollRef,
|
||||||
tagLookupById,
|
tagLookupById,
|
||||||
activeCorrespondentIdSet,
|
activeCorrespondentIdSet,
|
||||||
draggingDocumentIdsSet,
|
draggingDocumentIdsSet,
|
||||||
props.draggedFolderId,
|
draggedFolderId,
|
||||||
props.correspondentLookupById,
|
correspondentLookupById,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Use refs to stabilize handlers and avoid massive dependency arrays
|
const commandContextValue = useMemo(() => {
|
||||||
const latestPropsRef = useRef(props);
|
const anyShell = shell as any;
|
||||||
const latestHandlersRef = useRef({
|
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,
|
handleFolderClick,
|
||||||
|
shell,
|
||||||
handleDocumentDragStartLocal,
|
handleDocumentDragStartLocal,
|
||||||
handleDocumentDragEndLocal,
|
handleDocumentDragEndLocal,
|
||||||
tagHandlers,
|
|
||||||
toggleTagFilter,
|
|
||||||
toggleCorrespondentFilter,
|
toggleCorrespondentFilter,
|
||||||
});
|
onEntryPointer,
|
||||||
|
]);
|
||||||
// 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),
|
|
||||||
}), []);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
assetContextValue,
|
assetContextValue,
|
||||||
|
|||||||
Reference in New Issue
Block a user