refactor: Move DocumentsListProps to DocumentsPanel and implement desk view selection and ID generation.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import type { DragEvent, MouseEvent, RefObject } from 'react';
|
import type { MouseEvent } from 'react';
|
||||||
import { FolderIcon, CheckIcon, CloseIcon } from '../ui/icons';
|
import { FolderIcon, CheckIcon, CloseIcon } from '../ui/icons';
|
||||||
import { getTagColorStyle } from '../utils/colors';
|
import { getTagColorStyle } from '../utils/colors';
|
||||||
import { formatDate } from '../utils/date';
|
import { formatDate } from '../utils/date';
|
||||||
@@ -10,6 +10,7 @@ import { writeTagTransferData } from './tagTransfer';
|
|||||||
import useInlineRename from './useInlineRename';
|
import useInlineRename from './useInlineRename';
|
||||||
import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext';
|
import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext';
|
||||||
import type { Identifier } from '../types/identifiers';
|
import type { Identifier } from '../types/identifiers';
|
||||||
|
import type { DocumentsListProps } from './panel/DocumentsPanel';
|
||||||
|
|
||||||
export interface FolderLike {
|
export interface FolderLike {
|
||||||
id?: Identifier | 'root';
|
id?: Identifier | 'root';
|
||||||
@@ -57,36 +58,6 @@ export type DocumentsListEntry = FolderEntry | DocumentEntry;
|
|||||||
export type FolderEventHandler = (folder: FolderLike, event: MouseEvent<HTMLTableRowElement>) => void;
|
export type FolderEventHandler = (folder: FolderLike, event: MouseEvent<HTMLTableRowElement>) => void;
|
||||||
export type DocumentEventHandler = (document: DocumentLike, event: MouseEvent<HTMLTableRowElement>) => void;
|
export type DocumentEventHandler = (document: DocumentLike, event: MouseEvent<HTMLTableRowElement>) => void;
|
||||||
|
|
||||||
export interface DocumentsListProps {
|
|
||||||
entries: DocumentsListEntry[];
|
|
||||||
draggingDocumentIdsSet?: Set<Identifier> | null;
|
|
||||||
draggedFolderId?: Identifier | 'root' | null;
|
|
||||||
ensureAssetUrl?: (...args: any[]) => unknown;
|
|
||||||
getDocumentAsset?: (...args: any[]) => unknown;
|
|
||||||
onFolderClick?: FolderEventHandler;
|
|
||||||
onFolderSelect?: (folderId: Identifier | 'root') => void;
|
|
||||||
onFolderDragOver?: (event: DragEvent<HTMLTableRowElement>, folderId: Identifier | 'root') => void;
|
|
||||||
onFolderDragLeave?: (event: DragEvent<HTMLTableRowElement>) => void;
|
|
||||||
onFolderDrop?: (event: DragEvent<HTMLTableRowElement>, folderId: Identifier | 'root') => void;
|
|
||||||
onFolderDragStart?: (event: DragEvent<HTMLTableRowElement>, folderId: Identifier | 'root') => void;
|
|
||||||
onFolderDragEnd?: (event: DragEvent<HTMLTableRowElement>) => void;
|
|
||||||
onFolderRename?: (folderId: Identifier | 'root', nextName: string) => Promise<boolean> | boolean;
|
|
||||||
onDocumentClick?: DocumentEventHandler;
|
|
||||||
onDocumentActivate?: DocumentEventHandler;
|
|
||||||
onDocumentDragStart?: (event: DragEvent<HTMLTableRowElement>, document: DocumentLike) => void;
|
|
||||||
onDocumentDragEnd?: (event: DragEvent<HTMLTableRowElement>) => void;
|
|
||||||
onDocumentTagDragOver?: (event: DragEvent<HTMLTableRowElement>) => void;
|
|
||||||
onDocumentTagDragLeave?: (event: DragEvent<HTMLTableRowElement>) => void;
|
|
||||||
onDocumentTagDrop?: (event: DragEvent<HTMLTableRowElement>, documentId: Identifier) => void;
|
|
||||||
onDocumentRename?: (documentId: Identifier, nextTitle: string) => Promise<boolean> | boolean;
|
|
||||||
tagLookupById?: Map<Identifier, DocumentTag> | null;
|
|
||||||
onTagClick?: (tagId: Identifier) => void;
|
|
||||||
onCorrespondentClick?: (correspondentId: Identifier) => void;
|
|
||||||
activeCorrespondentIdSet?: Set<Identifier> | null;
|
|
||||||
scrollRef?: RefObject<HTMLElement | null>;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const DocumentsList: React.FC<DocumentsListProps> = ({
|
const DocumentsList: React.FC<DocumentsListProps> = ({
|
||||||
entries,
|
entries,
|
||||||
draggingDocumentIdsSet,
|
draggingDocumentIdsSet,
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import type { ReactNode } from 'react';
|
|
||||||
import DocumentsGrid from '../DocumentsGrid';
|
import DocumentsGrid from '../DocumentsGrid';
|
||||||
import DocumentsList from '../DocumentsList';
|
import DocumentsList from '../DocumentsList';
|
||||||
|
import type { DragEvent, ReactNode, RefObject } from 'react';
|
||||||
|
import type {
|
||||||
|
DocumentsListEntry,
|
||||||
|
FolderEventHandler,
|
||||||
|
DocumentEventHandler,
|
||||||
|
DocumentLike,
|
||||||
|
DocumentTag,
|
||||||
|
} from '../DocumentsList';
|
||||||
import DesktopWorkspace from '../../desktop/DesktopWorkspace';
|
import DesktopWorkspace from '../../desktop/DesktopWorkspace';
|
||||||
import { isTagTransferEvent } from '../tagTransfer';
|
import { isTagTransferEvent, parseTagTransferPayload } from '../tagTransfer';
|
||||||
import PreviewZoomOverlay from '../../detail/PreviewZoomOverlay';
|
import PreviewZoomOverlay from '../../detail/PreviewZoomOverlay';
|
||||||
import { isPointerModifierEvent, isPrimaryPointerEvent } from '../useEntryPointer';
|
import { isPointerModifierEvent, isPrimaryPointerEvent } from '../useEntryPointer';
|
||||||
import {
|
import {
|
||||||
@@ -16,6 +23,7 @@ import DocumentsPanelHeader, {
|
|||||||
DocumentsHeaderBreadcrumb,
|
DocumentsHeaderBreadcrumb,
|
||||||
} from './DocumentsPanelHeader';
|
} from './DocumentsPanelHeader';
|
||||||
import { SelectionFloatingPanel } from '../SelectionFloatingActions';
|
import { SelectionFloatingPanel } from '../SelectionFloatingActions';
|
||||||
|
import { createDocumentEntryKey } from '../../app/entryKey';
|
||||||
import { createDocumentsTableHeaderActions } from './DocumentsToolbar';
|
import { createDocumentsTableHeaderActions } from './DocumentsToolbar';
|
||||||
import { useDocumentsFilter } from '../context/DocumentsFilterContext';
|
import { useDocumentsFilter } from '../context/DocumentsFilterContext';
|
||||||
import { DEFAULT_GRID_ICON_SIZE } from '../../constants/documents';
|
import { DEFAULT_GRID_ICON_SIZE } from '../../constants/documents';
|
||||||
@@ -41,34 +49,32 @@ const defaultGetDocumentAsset = (_doc?: unknown, _type?: string) => null;
|
|||||||
export type DocumentLinkLike = { url?: string | null; mimeType?: string | null };
|
export type DocumentLinkLike = { url?: string | null; mimeType?: string | null };
|
||||||
|
|
||||||
export interface DocumentsListProps {
|
export interface DocumentsListProps {
|
||||||
entries: Array<{ type: string; id: Identifier; key: string; folder?: any; document?: any }>;
|
entries: DocumentsListEntry[];
|
||||||
draggingDocumentIdsSet: Set<Identifier>;
|
draggingDocumentIdsSet?: Set<Identifier> | null;
|
||||||
draggedFolderId: Identifier | 'root' | null;
|
draggedFolderId?: Identifier | 'root' | null;
|
||||||
onFolderClick: (folder: any, event: React.MouseEvent) => void;
|
ensureAssetUrl?: (...args: any[]) => unknown;
|
||||||
onFolderSelect?: (id: Identifier) => void;
|
getDocumentAsset?: (...args: any[]) => unknown;
|
||||||
onFolderDragOver?: (event: React.DragEvent, folder: any) => void;
|
onFolderClick?: FolderEventHandler;
|
||||||
onFolderDragLeave?: (event: React.DragEvent) => void;
|
onFolderSelect?: (folderId: Identifier | 'root') => void;
|
||||||
onFolderDrop?: (event: React.DragEvent, folder: any) => void;
|
onFolderDragOver?: (event: DragEvent<HTMLTableRowElement>, folderId: Identifier | 'root') => void;
|
||||||
onFolderDragStart?: (event: React.DragEvent, folder: any) => void;
|
onFolderDragLeave?: (event: DragEvent<HTMLTableRowElement>) => void;
|
||||||
onFolderDragEnd?: (event: React.DragEvent) => void;
|
onFolderDrop?: (event: DragEvent<HTMLTableRowElement>, folderId: Identifier | 'root') => void;
|
||||||
onDocumentClick: (doc: any, event: React.MouseEvent) => void;
|
onFolderDragStart?: (event: DragEvent<HTMLTableRowElement>, folderId: Identifier | 'root') => void;
|
||||||
onDocumentActivate: (doc: any, event?: React.MouseEvent | KeyboardEvent) => void;
|
onFolderDragEnd?: (event: DragEvent<HTMLTableRowElement>) => void;
|
||||||
onDocumentDragStart?: (event: React.DragEvent, doc: any) => void;
|
onFolderRename?: (folderId: Identifier | 'root', nextName: string) => Promise<boolean> | boolean;
|
||||||
onDocumentDragEnd?: (event: React.DragEvent) => void;
|
onDocumentClick?: DocumentEventHandler;
|
||||||
onDocumentTagDragOver?: (event: React.DragEvent) => void;
|
onDocumentActivate?: DocumentEventHandler;
|
||||||
onDocumentTagDragLeave?: (event: React.DragEvent) => void;
|
onDocumentDragStart?: (event: DragEvent<HTMLTableRowElement>, document: DocumentLike) => void;
|
||||||
onDocumentTagDrop?: (event: React.DragEvent, docId: Identifier) => void;
|
onDocumentDragEnd?: (event: DragEvent<HTMLTableRowElement>) => void;
|
||||||
ensureAssetUrl?: any;
|
onDocumentTagDragOver?: (event: DragEvent<HTMLTableRowElement>) => void;
|
||||||
getDocumentAsset?: any;
|
onDocumentTagDragLeave?: (event: DragEvent<HTMLTableRowElement>) => void;
|
||||||
tagLookupById?: any;
|
onDocumentTagDrop?: (event: DragEvent<HTMLTableRowElement>, documentId: Identifier) => void;
|
||||||
|
onDocumentRename?: (documentId: Identifier, nextTitle: string) => Promise<boolean> | boolean;
|
||||||
|
tagLookupById?: Map<Identifier, DocumentTag> | null;
|
||||||
onTagClick?: (tagId: Identifier) => void;
|
onTagClick?: (tagId: Identifier) => void;
|
||||||
scrollRef?: React.RefObject<HTMLElement>;
|
|
||||||
onCorrespondentClick?: (correspondentId: Identifier) => void;
|
onCorrespondentClick?: (correspondentId: Identifier) => void;
|
||||||
activeCorrespondentIdSet?: Set<Identifier>;
|
activeCorrespondentIdSet?: Set<Identifier> | null;
|
||||||
onDocumentRename?: (id: Identifier, name: string) => void;
|
scrollRef?: RefObject<HTMLElement | null>;
|
||||||
onFolderRename?: (id: Identifier, name: string) => void;
|
|
||||||
focusedRowKey?: string | null;
|
|
||||||
gridIconSize?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
||||||
@@ -98,12 +104,10 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
|||||||
ensureAssetUrl = null,
|
ensureAssetUrl = null,
|
||||||
getDocumentAsset = defaultGetDocumentAsset,
|
getDocumentAsset = defaultGetDocumentAsset,
|
||||||
isSearchLoading = false,
|
isSearchLoading = false,
|
||||||
onDocumentTagDrop,
|
|
||||||
viewMode = 'list',
|
viewMode = 'list',
|
||||||
onViewModeChange,
|
onViewModeChange,
|
||||||
documentLinks,
|
documentLinks,
|
||||||
ensureDownloadUrl,
|
ensureDownloadUrl,
|
||||||
deskWorkspaceProps = null,
|
|
||||||
onRefresh = () => { },
|
onRefresh = () => { },
|
||||||
sortField,
|
sortField,
|
||||||
sortDirection,
|
sortDirection,
|
||||||
@@ -120,6 +124,13 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
|||||||
onBulkReanalyze,
|
onBulkReanalyze,
|
||||||
folderOptions,
|
folderOptions,
|
||||||
onMoveDocumentsToFolder,
|
onMoveDocumentsToFolder,
|
||||||
|
searchQuery,
|
||||||
|
activeTagFilters,
|
||||||
|
activeCorrespondentFilters,
|
||||||
|
selectedFolder,
|
||||||
|
promoteSelectionOrder,
|
||||||
|
onDocumentTagDrop,
|
||||||
|
currentTenantId,
|
||||||
}): ReactNode => {
|
}): ReactNode => {
|
||||||
const {
|
const {
|
||||||
selectedEntries,
|
selectedEntries,
|
||||||
@@ -127,6 +138,8 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
|||||||
setFocusedRowKey,
|
setFocusedRowKey,
|
||||||
handleEntrySelection,
|
handleEntrySelection,
|
||||||
clearSelection,
|
clearSelection,
|
||||||
|
selectionAnchorRef,
|
||||||
|
applySelection,
|
||||||
} = useWorkspaceSelectionContext();
|
} = useWorkspaceSelectionContext();
|
||||||
const {
|
const {
|
||||||
isActive: isFilterActive,
|
isActive: isFilterActive,
|
||||||
@@ -144,10 +157,63 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
|||||||
: null,
|
: null,
|
||||||
[searchResultIds, documentLookup],
|
[searchResultIds, documentLookup],
|
||||||
);
|
);
|
||||||
|
|
||||||
const showingSearchResults = Array.isArray(searchResultIds);
|
const showingSearchResults = Array.isArray(searchResultIds);
|
||||||
const rows = showingSearchResults && searchDocuments ? searchDocuments : documents;
|
const rows = showingSearchResults && searchDocuments ? searchDocuments : documents;
|
||||||
const documentLinkMap = documentLinks instanceof Map ? documentLinks : null;
|
const documentLinkMap = documentLinks instanceof Map ? documentLinks : null;
|
||||||
const searchResultCount = Array.isArray(searchResultIds) ? searchResultIds.length : 0;
|
const searchResultCount = Array.isArray(searchResultIds) ? searchResultIds.length : 0;
|
||||||
|
|
||||||
|
const handleDeskDocumentStackSelect = useCallback(
|
||||||
|
(docIds: Array<Identifier | string>) => {
|
||||||
|
if (!Array.isArray(docIds) || docIds.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rowKeys = docIds
|
||||||
|
.map((id) => createDocumentEntryKey(id as Identifier))
|
||||||
|
.filter((value): value is string => typeof value === 'string');
|
||||||
|
|
||||||
|
if (!rowKeys.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextKeys = [...selectedEntries];
|
||||||
|
rowKeys.forEach((key) => {
|
||||||
|
if (!nextKeys.includes(key)) {
|
||||||
|
nextKeys.push(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const anchor = (rowKeys[0]
|
||||||
|
|| selectionAnchorRef.current
|
||||||
|
|| nextKeys[nextKeys.length - 1]) as string | null;
|
||||||
|
|
||||||
|
applySelection(nextKeys, {
|
||||||
|
anchor,
|
||||||
|
interactedKeys: rowKeys,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[applySelection, selectedEntries, selectionAnchorRef],
|
||||||
|
);
|
||||||
|
|
||||||
|
const deskViewId = useMemo(() => {
|
||||||
|
if (showingSearchResults) {
|
||||||
|
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';
|
||||||
|
return `folder:${folderKey}`;
|
||||||
|
}, [
|
||||||
|
showingSearchResults,
|
||||||
|
searchQuery,
|
||||||
|
activeTagFilters,
|
||||||
|
activeCorrespondentFilters,
|
||||||
|
selectedFolder,
|
||||||
|
]);
|
||||||
|
|
||||||
const headerTitle = showingSearchResults
|
const headerTitle = showingSearchResults
|
||||||
? 'Search results'
|
? 'Search results'
|
||||||
: currentFolderName || 'Documents';
|
: currentFolderName || 'Documents';
|
||||||
@@ -286,6 +352,7 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
|||||||
const suppressDocumentClickRef = useRef(false);
|
const suppressDocumentClickRef = useRef(false);
|
||||||
const isGridView = viewMode === 'grid';
|
const isGridView = viewMode === 'grid';
|
||||||
const isDeskView = viewMode === 'desk';
|
const isDeskView = viewMode === 'desk';
|
||||||
|
const gridIconSize = DEFAULT_GRID_ICON_SIZE;
|
||||||
|
|
||||||
type ZoomSource = { url: string; alt?: string | null; mimeType?: string | null };
|
type ZoomSource = { url: string; alt?: string | null; mimeType?: string | null };
|
||||||
|
|
||||||
@@ -545,8 +612,6 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const gridIconSize = DEFAULT_GRID_ICON_SIZE;
|
|
||||||
|
|
||||||
const scrollToTop = useCallback(() => {
|
const scrollToTop = useCallback(() => {
|
||||||
if (scrollRef.current) {
|
if (scrollRef.current) {
|
||||||
scrollRef.current.scrollTop = 0;
|
scrollRef.current.scrollTop = 0;
|
||||||
@@ -650,32 +715,6 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
|||||||
[isTagDragEvent],
|
[isTagDragEvent],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDocumentTagDrop = useCallback(
|
|
||||||
(event, documentId) => {
|
|
||||||
if (!isTagDragEvent(event)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
event.currentTarget.classList.remove('tag-drop-target');
|
|
||||||
const payload =
|
|
||||||
event.dataTransfer.getData('application/x-papercrate-tag') ||
|
|
||||||
event.dataTransfer.getData('text/papercrate-tag');
|
|
||||||
if (!payload) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(payload);
|
|
||||||
if (parsed?.id && onDocumentTagDrop) {
|
|
||||||
onDocumentTagDrop(documentId, parsed);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('[documents] Failed to parse tag drop payload', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[isTagDragEvent, onDocumentTagDrop],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleDocumentClick = useCallback(
|
const handleDocumentClick = useCallback(
|
||||||
(doc, event) => {
|
(doc, event) => {
|
||||||
if (!doc || suppressDocumentClickRef.current || !onEntryPointer) {
|
if (!doc || suppressDocumentClickRef.current || !onEntryPointer) {
|
||||||
@@ -738,23 +777,51 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
|||||||
[entries],
|
[entries],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderBody = () => {
|
const handleListDocumentTagDrop = useCallback(
|
||||||
const hasEntries = entries.length > 0;
|
(event: React.DragEvent<HTMLElement>, docId: Identifier) => {
|
||||||
const isSearchEmpty = (showingSearchResults || isFilterActive) && !hasDocumentEntries && !isSearchLoading;
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
if (isSearchEmpty) {
|
const payload = parseTagTransferPayload(event);
|
||||||
return (
|
if (payload && onDocumentTagDrop) {
|
||||||
<div className="empty-state">
|
onDocumentTagDrop(docId, payload);
|
||||||
No documents match the current filters.
|
}
|
||||||
</div>
|
},
|
||||||
|
[onDocumentTagDrop],
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasEntries) {
|
const deskWorkspaceProps = useMemo(
|
||||||
<div className="empty-state">
|
() => ({
|
||||||
No documents to show here yet. Drop files to make this space come alive.
|
entries: rows,
|
||||||
</div>
|
onDocumentActivate,
|
||||||
}
|
onDocumentClick: onEntryPointer,
|
||||||
|
onDocumentStackSelect: handleDeskDocumentStackSelect,
|
||||||
|
onPromoteSelection: promoteSelectionOrder,
|
||||||
|
onDocumentTagDrop,
|
||||||
|
ensureAssetUrl,
|
||||||
|
getDocumentAsset,
|
||||||
|
activeTagFilters,
|
||||||
|
tenantId: currentTenantId,
|
||||||
|
viewId: deskViewId,
|
||||||
|
documentLinks: documentLinkMap,
|
||||||
|
ensureDownloadUrl,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
rows,
|
||||||
|
onDocumentActivate,
|
||||||
|
onEntryPointer,
|
||||||
|
handleDeskDocumentStackSelect,
|
||||||
|
promoteSelectionOrder,
|
||||||
|
onDocumentTagDrop,
|
||||||
|
ensureAssetUrl,
|
||||||
|
getDocumentAsset,
|
||||||
|
activeTagFilters,
|
||||||
|
currentTenantId,
|
||||||
|
deskViewId,
|
||||||
|
documentLinkMap,
|
||||||
|
ensureDownloadUrl,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
const listProps: DocumentsListProps = {
|
const listProps: DocumentsListProps = {
|
||||||
entries,
|
entries,
|
||||||
@@ -773,7 +840,7 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
|||||||
onDocumentDragEnd: handleDocumentDragEndLocal,
|
onDocumentDragEnd: handleDocumentDragEndLocal,
|
||||||
onDocumentTagDragOver: handleDocumentTagDragOver,
|
onDocumentTagDragOver: handleDocumentTagDragOver,
|
||||||
onDocumentTagDragLeave: handleDocumentTagDragLeave,
|
onDocumentTagDragLeave: handleDocumentTagDragLeave,
|
||||||
onDocumentTagDrop: handleDocumentTagDrop,
|
onDocumentTagDrop: handleListDocumentTagDrop,
|
||||||
onDocumentRename,
|
onDocumentRename,
|
||||||
onFolderRename,
|
onFolderRename,
|
||||||
ensureAssetUrl,
|
ensureAssetUrl,
|
||||||
@@ -783,15 +850,33 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
|||||||
onCorrespondentClick: toggleCorrespondentFilter,
|
onCorrespondentClick: toggleCorrespondentFilter,
|
||||||
activeCorrespondentIdSet: activeCorrespondentIdSet,
|
activeCorrespondentIdSet: activeCorrespondentIdSet,
|
||||||
scrollRef,
|
scrollRef,
|
||||||
focusedRowKey,
|
|
||||||
gridIconSize,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderBody = () => {
|
||||||
|
const hasEntries = entries.length > 0;
|
||||||
|
const isSearchEmpty = (showingSearchResults || isFilterActive) && !hasDocumentEntries && !isSearchLoading;
|
||||||
|
|
||||||
|
if (isSearchEmpty) {
|
||||||
|
return (
|
||||||
|
<div className="empty-state">
|
||||||
|
No documents match the current filters.
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasEntries) {
|
||||||
|
return (
|
||||||
|
<div className="empty-state">
|
||||||
|
No documents to show here yet. Drop files to make this space come alive.
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
switch (viewMode) {
|
switch (viewMode) {
|
||||||
case 'desk':
|
case 'desk':
|
||||||
return <DesktopWorkspace {...deskWorkspaceProps} />;
|
return <DesktopWorkspace {...deskWorkspaceProps} />;
|
||||||
case 'grid':
|
case 'grid':
|
||||||
return <DocumentsGrid {...listProps} />;
|
return <DocumentsGrid {...listProps} gridIconSize={gridIconSize} />;
|
||||||
case 'list':
|
case 'list':
|
||||||
default:
|
default:
|
||||||
return <DocumentsList {...listProps} />;
|
return <DocumentsList {...listProps} />;
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ import useDocumentMutations from './useDocumentMutations';
|
|||||||
import useDetailWorkspace from '../../detail/useDetailWorkspace';
|
import useDetailWorkspace from '../../detail/useDetailWorkspace';
|
||||||
import useWorkspaceTaxonomies from './useWorkspaceTaxonomies';
|
import useWorkspaceTaxonomies from './useWorkspaceTaxonomies';
|
||||||
import useWorkspaceBreadcrumbs from './useWorkspaceBreadcrumbs';
|
import useWorkspaceBreadcrumbs from './useWorkspaceBreadcrumbs';
|
||||||
import useWorkspaceDeskProps from './useWorkspaceDeskProps';
|
|
||||||
import useWorkspaceSelectionSync from './useWorkspaceSelectionSync';
|
import useWorkspaceSelectionSync from './useWorkspaceSelectionSync';
|
||||||
import type { DocumentId, FolderId as FolderIdentifier, Identifier } from '../../types/identifiers';
|
import type { DocumentId, FolderId as FolderIdentifier, Identifier } from '../../types/identifiers';
|
||||||
|
|
||||||
@@ -351,7 +350,6 @@ const useDocumentsWorkspace = ({
|
|||||||
searchResultIds,
|
searchResultIds,
|
||||||
setSearchResultIds,
|
setSearchResultIds,
|
||||||
searchLoading,
|
searchLoading,
|
||||||
activeTagFilters,
|
|
||||||
setActiveTagFilters,
|
setActiveTagFilters,
|
||||||
activeCorrespondentFilters,
|
activeCorrespondentFilters,
|
||||||
setActiveCorrespondentFilters,
|
setActiveCorrespondentFilters,
|
||||||
@@ -807,7 +805,6 @@ const useDocumentsWorkspace = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
promoteSelectionOrder,
|
|
||||||
clearDocumentSelection,
|
clearDocumentSelection,
|
||||||
} = useDocumentsSelection({
|
} = useDocumentsSelection({
|
||||||
showingSearchResults,
|
showingSearchResults,
|
||||||
@@ -1157,22 +1154,6 @@ const useDocumentsWorkspace = ({
|
|||||||
tagLookupById,
|
tagLookupById,
|
||||||
});
|
});
|
||||||
|
|
||||||
const inspectDocumentForDesk = useCallback(
|
|
||||||
(docOrId?: DocumentLike | Identifier | null) => {
|
|
||||||
if (docOrId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const docId: Identifier | null = Object(docOrId) === docOrId
|
|
||||||
? (docOrId as DocumentLike)?.id ?? null
|
|
||||||
: (docOrId as Identifier | null);
|
|
||||||
if (docId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
inspectDocument(docId);
|
|
||||||
},
|
|
||||||
[inspectDocument],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleEntryPointerCore = useEntryPointerCore({
|
const handleEntryPointerCore = useEntryPointerCore({
|
||||||
onSelectEntry: (entry, event, { rowKey, modifierClick, primaryClick }) => {
|
onSelectEntry: (entry, event, { rowKey, modifierClick, primaryClick }) => {
|
||||||
const { type, id } = entry;
|
const { type, id } = entry;
|
||||||
@@ -1211,28 +1192,6 @@ const useDocumentsWorkspace = ({
|
|||||||
tenantIdRef,
|
tenantIdRef,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const deskWorkspaceProps = useWorkspaceDeskProps({
|
|
||||||
viewDocuments,
|
|
||||||
inspectDocumentForDesk,
|
|
||||||
handleEntryPointer: handleEntryPointerCore,
|
|
||||||
selectedEntries,
|
|
||||||
selectionAnchorRef,
|
|
||||||
applySelection,
|
|
||||||
showingSearchResults,
|
|
||||||
searchQuery,
|
|
||||||
activeTagFilters,
|
|
||||||
activeCorrespondentFilters,
|
|
||||||
selectedFolder,
|
|
||||||
promoteSelectionOrder,
|
|
||||||
handleDocumentTagDrop,
|
|
||||||
ensureAssetUrl,
|
|
||||||
getDocumentAsset,
|
|
||||||
currentTenantId,
|
|
||||||
documentLinks,
|
|
||||||
ensureDownloadUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
const documentsPanelProps = useDocumentsPanelProps({
|
const documentsPanelProps = useDocumentsPanelProps({
|
||||||
currentFolderName,
|
currentFolderName,
|
||||||
breadcrumbs,
|
breadcrumbs,
|
||||||
@@ -1281,13 +1240,11 @@ const useDocumentsWorkspace = ({
|
|||||||
ensureDownloadUrl,
|
ensureDownloadUrl,
|
||||||
selectionValue: selection,
|
selectionValue: selection,
|
||||||
});
|
});
|
||||||
|
|
||||||
const documentsTableProps = useMemo(
|
const documentsTableProps = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
...documentsPanelProps,
|
...documentsPanelProps,
|
||||||
deskWorkspaceProps,
|
|
||||||
}),
|
}),
|
||||||
[documentsPanelProps, deskWorkspaceProps],
|
[documentsPanelProps],
|
||||||
);
|
);
|
||||||
|
|
||||||
const sidebarProps = useSidebarProps({
|
const sidebarProps = useSidebarProps({
|
||||||
|
|||||||
@@ -1,138 +0,0 @@
|
|||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import type { MutableRefObject } from 'react';
|
|
||||||
import { createDocumentEntryKey } from '../../app/entryKey';
|
|
||||||
import type { Identifier } from '../../types/identifiers';
|
|
||||||
|
|
||||||
interface ApplySelectionFn {
|
|
||||||
(keys: string[], options?: { anchor: string | null; interactedKeys?: string[] }): unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UseWorkspaceDeskPropsArgs {
|
|
||||||
viewDocuments: unknown[];
|
|
||||||
inspectDocumentForDesk?: (id: Identifier | null) => void;
|
|
||||||
handleEntryPointer: (params: { rowKey?: string | null; id?: Identifier | null; type?: string; event?: any }) => void;
|
|
||||||
selectedEntries: string[];
|
|
||||||
selectionAnchorRef: MutableRefObject<Identifier | string | null>;
|
|
||||||
applySelection: ApplySelectionFn;
|
|
||||||
showingSearchResults: boolean;
|
|
||||||
searchQuery: string;
|
|
||||||
activeTagFilters: Array<string>;
|
|
||||||
activeCorrespondentFilters: Array<string>;
|
|
||||||
selectedFolder: Identifier | 'root' | null;
|
|
||||||
promoteSelectionOrder: () => void;
|
|
||||||
handleDocumentTagDrop: (docId: Identifier, tagId: Identifier) => Promise<void> | void;
|
|
||||||
ensureAssetUrl: (docId: Identifier, asset: any, options?: Record<string, unknown>) => Promise<any> | null;
|
|
||||||
getDocumentAsset: (doc: any, type: string) => any;
|
|
||||||
currentTenantId: Identifier | null;
|
|
||||||
documentLinks: Map<Identifier, unknown> | null;
|
|
||||||
ensureDownloadUrl?: (docId: Identifier, options?: { force?: boolean }) => Promise<unknown>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useWorkspaceDeskProps = ({
|
|
||||||
viewDocuments,
|
|
||||||
inspectDocumentForDesk,
|
|
||||||
handleEntryPointer,
|
|
||||||
selectedEntries,
|
|
||||||
selectionAnchorRef,
|
|
||||||
applySelection,
|
|
||||||
showingSearchResults,
|
|
||||||
searchQuery,
|
|
||||||
activeTagFilters,
|
|
||||||
activeCorrespondentFilters,
|
|
||||||
selectedFolder,
|
|
||||||
promoteSelectionOrder,
|
|
||||||
handleDocumentTagDrop,
|
|
||||||
ensureAssetUrl,
|
|
||||||
getDocumentAsset,
|
|
||||||
currentTenantId,
|
|
||||||
documentLinks,
|
|
||||||
ensureDownloadUrl,
|
|
||||||
}: UseWorkspaceDeskPropsArgs) => {
|
|
||||||
const handleDeskDocumentStackSelect = useCallback(
|
|
||||||
(docIds: Array<Identifier | string>) => {
|
|
||||||
if (!Array.isArray(docIds) || docIds.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rowKeys = docIds
|
|
||||||
.map((id) => createDocumentEntryKey(id as Identifier))
|
|
||||||
.filter((value): value is string => typeof value === 'string');
|
|
||||||
|
|
||||||
if (!rowKeys.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextKeys = [...selectedEntries];
|
|
||||||
rowKeys.forEach((key) => {
|
|
||||||
if (!nextKeys.includes(key)) {
|
|
||||||
nextKeys.push(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const anchor = (rowKeys[0]
|
|
||||||
|| selectionAnchorRef.current
|
|
||||||
|| nextKeys[nextKeys.length - 1]) as string | null;
|
|
||||||
|
|
||||||
applySelection(nextKeys, {
|
|
||||||
anchor,
|
|
||||||
interactedKeys: rowKeys,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[applySelection, selectedEntries, selectionAnchorRef],
|
|
||||||
);
|
|
||||||
|
|
||||||
const deskViewId = useMemo(() => {
|
|
||||||
if (showingSearchResults) {
|
|
||||||
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';
|
|
||||||
return `folder:${folderKey}`;
|
|
||||||
}, [
|
|
||||||
showingSearchResults,
|
|
||||||
searchQuery,
|
|
||||||
activeTagFilters,
|
|
||||||
activeCorrespondentFilters,
|
|
||||||
selectedFolder,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const deskWorkspaceProps = useMemo(
|
|
||||||
() => ({
|
|
||||||
entries: viewDocuments,
|
|
||||||
onDocumentActivate: inspectDocumentForDesk,
|
|
||||||
onDocumentClick: handleEntryPointer,
|
|
||||||
onDocumentStackSelect: handleDeskDocumentStackSelect,
|
|
||||||
onPromoteSelection: promoteSelectionOrder,
|
|
||||||
onDocumentTagDrop: handleDocumentTagDrop,
|
|
||||||
ensureAssetUrl,
|
|
||||||
getDocumentAsset,
|
|
||||||
activeTagFilters,
|
|
||||||
tenantId: currentTenantId,
|
|
||||||
viewId: deskViewId,
|
|
||||||
documentLinks,
|
|
||||||
ensureDownloadUrl,
|
|
||||||
}),
|
|
||||||
[
|
|
||||||
viewDocuments,
|
|
||||||
inspectDocumentForDesk,
|
|
||||||
handleEntryPointer,
|
|
||||||
handleDeskDocumentStackSelect,
|
|
||||||
promoteSelectionOrder,
|
|
||||||
handleDocumentTagDrop,
|
|
||||||
ensureAssetUrl,
|
|
||||||
getDocumentAsset,
|
|
||||||
activeTagFilters,
|
|
||||||
currentTenantId,
|
|
||||||
deskViewId,
|
|
||||||
documentLinks,
|
|
||||||
ensureDownloadUrl,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
return deskWorkspaceProps;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useWorkspaceDeskProps;
|
|
||||||
Reference in New Issue
Block a user