210 lines
7.1 KiB
TypeScript
210 lines
7.1 KiB
TypeScript
import { useMemo, useCallback, useRef } 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 { useTagInteractions } from '../interactions/useTagInteractions';
|
|
|
|
const EntryType = {
|
|
folder: 'folder',
|
|
document: 'document',
|
|
};
|
|
|
|
export const useDocumentsContextValues = (props: DocumentsPanelInnerProps) => {
|
|
const {
|
|
ensureAssetUrl,
|
|
getDocumentAsset,
|
|
isSearchLoading,
|
|
searchQuery = '',
|
|
activeTagFilters = [],
|
|
activeCorrespondentFilters = [],
|
|
selectedFolder = null,
|
|
onDocumentDragStart,
|
|
onDocumentDragEnd,
|
|
onEntryPointer,
|
|
activeCorrespondentIds = [],
|
|
draggingDocumentIds = [],
|
|
tagLookupById,
|
|
} = props;
|
|
|
|
const {
|
|
setFocusedEntryKey,
|
|
} = useWorkspaceSelectionContext();
|
|
|
|
const {
|
|
toggleTag: toggleTagFilter,
|
|
toggleCorrespondent: toggleCorrespondentFilter,
|
|
} = useDocumentsFilter();
|
|
|
|
const scrollRef = useRef<HTMLElement | null>(null);
|
|
const suppressDocumentClickRef = useRef(false);
|
|
|
|
// Handlers
|
|
const tagHandlers = useTagInteractions({
|
|
onAssignTagToDocument: props.onDocumentTagAttach,
|
|
onRemoveTagFromDocument: props.onDocumentTagDetach,
|
|
onTagClick: toggleTagFilter,
|
|
});
|
|
|
|
// Derived State
|
|
const draggingDocumentIdsSet = useMemo(
|
|
() => new Set(draggingDocumentIds || []),
|
|
[draggingDocumentIds],
|
|
);
|
|
const activeCorrespondentIdSet = useMemo(
|
|
() => new Set(activeCorrespondentIds || []),
|
|
[activeCorrespondentIds],
|
|
);
|
|
const showingSearchResults = Array.isArray(props.searchResultIds);
|
|
const hasDocumentEntries = (props.documents || []).length > 0 || (showingSearchResults && (props.searchResultIds || []).length > 0);
|
|
|
|
const viewId = 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 handleFolderClick = useCallback(
|
|
(folder: any, event: any) => {
|
|
if (!folder) {
|
|
return;
|
|
}
|
|
|
|
if (onEntryPointer) {
|
|
onEntryPointer(
|
|
{ type: EntryType.folder, id: folder.id, key: `folder:${folder.id}`, folder },
|
|
event,
|
|
);
|
|
}
|
|
|
|
if (
|
|
!isPointerModifierEvent(event)
|
|
&& isPrimaryPointerEvent(event)
|
|
&& scrollRef.current
|
|
) {
|
|
scrollRef.current.focus({ preventScroll: true });
|
|
setFocusedEntryKey(`folder:${folder.id}`);
|
|
}
|
|
},
|
|
[onEntryPointer, setFocusedEntryKey],
|
|
);
|
|
|
|
const handleDocumentDragStartLocal = useCallback(
|
|
(event: any, doc: any) => {
|
|
suppressDocumentClickRef.current = true;
|
|
onDocumentDragStart?.(event, doc);
|
|
},
|
|
[onDocumentDragStart],
|
|
);
|
|
|
|
const handleDocumentDragEndLocal = useCallback(
|
|
(event: any) => {
|
|
onDocumentDragEnd?.(event);
|
|
requestAnimationFrame(() => {
|
|
suppressDocumentClickRef.current = false;
|
|
});
|
|
},
|
|
[onDocumentDragEnd],
|
|
);
|
|
|
|
// Context Values Construction
|
|
const assetContextValue = useMemo(() => ({
|
|
ensureAssetUrl,
|
|
getDocumentAsset,
|
|
}), [ensureAssetUrl, getDocumentAsset]);
|
|
|
|
const viewStateContextValue = useMemo(() => ({
|
|
viewId,
|
|
scrollRef,
|
|
tagLookupById,
|
|
activeCorrespondentIdSet,
|
|
draggingDocumentIdsSet,
|
|
draggedFolderId: props.draggedFolderId,
|
|
}), [
|
|
viewId,
|
|
scrollRef,
|
|
tagLookupById,
|
|
activeCorrespondentIdSet,
|
|
draggingDocumentIdsSet,
|
|
props.draggedFolderId,
|
|
]);
|
|
|
|
// Use refs to stabilize handlers and avoid massive dependency arrays
|
|
const latestPropsRef = useRef(props);
|
|
const latestHandlersRef = useRef({
|
|
handleFolderClick,
|
|
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),
|
|
},
|
|
},
|
|
tags: {
|
|
onAttach: (d: any, t: any) => latestPropsRef.current.onDocumentTagAttach?.(d, t),
|
|
onDetach: (d: any, t: any) => latestPropsRef.current.onDocumentTagDetach?.(d, t),
|
|
onClick: (t: any) => latestHandlersRef.current.toggleTagFilter(t),
|
|
},
|
|
correspondents: {
|
|
onClick: (c: any) => latestHandlersRef.current.toggleCorrespondentFilter(c),
|
|
},
|
|
onEntryPointer: (entry: any, e: any) => latestPropsRef.current.onEntryPointer?.(entry, e),
|
|
}), []);
|
|
|
|
return {
|
|
assetContextValue,
|
|
viewStateContextValue,
|
|
commandContextValue,
|
|
tagHandlers,
|
|
scrollRef,
|
|
hasDocumentEntries,
|
|
isSearchLoading,
|
|
showingSearchResults,
|
|
activeTagFilters,
|
|
activeCorrespondentFilters,
|
|
};
|
|
};
|