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(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, }; };