From 096c377897e0f5f98ef7f1ec488286be8a49215c Mon Sep 17 00:00:00 2001 From: Nils Schneider Date: Mon, 24 Nov 2025 22:38:01 +0100 Subject: [PATCH] refactor: Consolidate workspace utilities into new `workspaceUtils.ts` with typed entry keys, replacing `appLayoutUtils.ts`, and update related components. --- frontend/src/app/DocumentsRoute.tsx | 4 - frontend/src/app/appLayoutUtils.ts | 115 ------------------ frontend/src/app/useDocumentsPreferences.ts | 2 +- frontend/src/app/useDocumentsSearch.ts | 2 +- frontend/src/app/useWorkspaceSurface.tsx | 65 +++++----- frontend/src/app/workspaceUtils.ts | 65 ++++++++++ frontend/src/asset_manager.ts | 13 +- frontend/src/detail/useDetailWorkspace.ts | 6 +- frontend/src/documents/documentActions.ts | 38 +++--- frontend/src/documents/documentSummary.ts | 2 +- frontend/src/folders/FolderManagerContext.tsx | 2 +- .../hooks/documents/useDocumentMutations.ts | 2 +- .../src/hooks/documents/useDocumentUploads.ts | 20 +-- .../hooks/documents/useDocumentsWorkspace.ts | 29 ++--- frontend/src/hooks/documents/useFolderTree.ts | 2 +- .../hooks/documents/useFolderTreeActions.ts | 2 +- .../documents/useWorkspaceBreadcrumbs.ts | 2 +- frontend/src/preview/DocumentViewerPanel.tsx | 112 ++++++++--------- frontend/src/sidebar/useSidebarProps.ts | 2 +- 19 files changed, 202 insertions(+), 283 deletions(-) delete mode 100644 frontend/src/app/appLayoutUtils.ts create mode 100644 frontend/src/app/workspaceUtils.ts diff --git a/frontend/src/app/DocumentsRoute.tsx b/frontend/src/app/DocumentsRoute.tsx index bdcdfd6..aa47378 100644 --- a/frontend/src/app/DocumentsRoute.tsx +++ b/frontend/src/app/DocumentsRoute.tsx @@ -22,7 +22,6 @@ type EnsureAssetUrl = ( type EnsurePreviewData = (docId: Identifier, options?: Record) => Promise; type GetDocumentAsset = (document: unknown, assetType: string) => unknown; -type ResolveApiPath = (path: string) => string; type NotifyApiError = (error: unknown, fallbackMessage?: string) => void; interface DocumentsTableProps { @@ -43,7 +42,6 @@ interface DocumentsRouteAppShell { previewDocumentId?: Identifier | null; closeDocumentPreview?: () => void; ensurePreviewData?: EnsurePreviewData; - resolveApiPath?: ResolveApiPath; ensureAssetUrl?: EnsureAssetUrl; getDocumentAsset?: GetDocumentAsset; notifyApiError?: NotifyApiError; @@ -63,7 +61,6 @@ const DocumentsRouteContent: React.FC = () => { previewDocumentId, closeDocumentPreview, ensurePreviewData, - resolveApiPath, ensureAssetUrl, getDocumentAsset, notifyApiError, @@ -118,7 +115,6 @@ const DocumentsRouteContent: React.FC = () => { ensureAssetUrl, ensurePreviewData, getDocumentAsset, - resolveApiPath, notifyApiError, closeDocumentPreview, }); diff --git a/frontend/src/app/appLayoutUtils.ts b/frontend/src/app/appLayoutUtils.ts deleted file mode 100644 index eebdea1..0000000 --- a/frontend/src/app/appLayoutUtils.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { resolveAssetExpiresAt, resolveAssetUrl } from '../asset_manager'; - -export const ASSET_PRESIGN_TTL_MS = 240 * 1000; // backend issues 5 min tokens; refresh slightly early -export const DEFAULT_FOLDER_NAME = 'Documents'; -export const DEFAULT_SORT_FIELD = 'title'; -export const DEFAULT_SORT_DIRECTION = 'asc'; -export const SORT_FIELD_VALUES = ['title', 'issued_at', 'created_at', 'updated_at']; -export const TAG_FILTER_UNTAGGED = '__UNTAGGED__'; - -const ROW_KEY_SEPARATOR = ':'; -const DOCUMENT_ROW_PREFIX = 'document'; -const FOLDER_ROW_PREFIX = 'folder'; - -export const resolveApiPath = (path = '') => path; - -const makeRowKey = (type, id) => - id ? `${type}${ROW_KEY_SEPARATOR}${id}` : `${type}${ROW_KEY_SEPARATOR}`; - -const normalizeRowKey = (key: string | number | null) => String(key ?? ''); - -const getRowType = (key) => normalizeRowKey(key).split(ROW_KEY_SEPARATOR, 1)[0] ?? ''; - -export const getRowId = (key) => { - const normalized = normalizeRowKey(key); - const separatorIndex = normalized.indexOf(ROW_KEY_SEPARATOR); - if (separatorIndex === -1) return normalized; - return normalized.slice(separatorIndex + 1); -}; - -export const isDocumentRowKey = (key) => getRowType(key) === DOCUMENT_ROW_PREFIX; -export const isFolderRowKey = (key) => getRowType(key) === FOLDER_ROW_PREFIX; - -export const resolveDocumentRowKey = (documentId) => - documentId ? makeRowKey(DOCUMENT_ROW_PREFIX, documentId) : null; - -export const resolveFolderRowKey = (folderId) => - folderId ? makeRowKey(FOLDER_ROW_PREFIX, folderId) : null; - -export const hasFiles = (event) => - Array.from(event.dataTransfer?.types || []).includes('Files'); - -const isAssetEquivalent = (lhs, rhs) => { - if (!lhs || !rhs) return false; - const lhsPrimaryMetadata = lhs?.metadata; - const rhsPrimaryMetadata = rhs?.metadata; - const lhsExpiresAt = resolveAssetExpiresAt(lhs); - const rhsExpiresAt = resolveAssetExpiresAt(rhs); - return ( - lhs.id === rhs.id - && resolveAssetUrl(lhs) === resolveAssetUrl(rhs) - && lhsExpiresAt === rhsExpiresAt - && lhsPrimaryMetadata?.width === rhsPrimaryMetadata?.width - && lhsPrimaryMetadata?.height === rhsPrimaryMetadata?.height - && lhs.mime_type === rhs.mime_type - && lhs.asset_type === rhs.asset_type - && lhs.updated_at === rhs.updated_at - ); -}; - -const mergeAssetIntoGroup = (group, assetData) => { - if (!assetData || !assetData.asset_type) { - if (Array.isArray(group)) { - return group; - } - return group || {}; - } - - if (Array.isArray(group) || !group) { - const list = Array.isArray(group) ? group : []; - const index = list.findIndex((item) => item?.id === assetData.id); - if (index >= 0) { - const existing = list[index]; - if (isAssetEquivalent(existing, assetData)) { - return list; - } - const next = list.slice(); - next[index] = { ...existing, ...assetData }; - return next; - } - return list.concat({ ...assetData }); - } - - const key = assetData.asset_type; - const previous = group?.[key]; - if (previous && isAssetEquivalent(previous, assetData)) { - return group; - } - - const next = { ...(group || {}) }; - next[key] = { ...(previous || {}), ...assetData }; - return next; -}; - -export const mergeAssetIntoDocument = (doc, assetData) => { - if (!doc) return doc; - const existingGroup = doc.current_version?.assets || null; - const nextGroup = mergeAssetIntoGroup(existingGroup, assetData); - if (nextGroup === existingGroup) { - return doc; - } - const updatedCurrentVersion = doc.current_version - ? { ...doc.current_version, assets: nextGroup } - : { assets: nextGroup }; - return { ...doc, current_version: updatedCurrentVersion }; -}; - -export const createRootNode = () => ({ - id: 'root', - name: DEFAULT_FOLDER_NAME, - parentId: null, - children: [], - expanded: true, - loaded: false, - hasChildren: false, -}); diff --git a/frontend/src/app/useDocumentsPreferences.ts b/frontend/src/app/useDocumentsPreferences.ts index 76fcf13..8371916 100644 --- a/frontend/src/app/useDocumentsPreferences.ts +++ b/frontend/src/app/useDocumentsPreferences.ts @@ -3,7 +3,7 @@ import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD, SORT_FIELD_VALUES, -} from './appLayoutUtils'; +} from './workspaceUtils'; const VIEW_MODE_STORAGE_KEY = 'papercrate_view_mode'; const SORT_FIELD_STORAGE_KEY = 'papercrate_sort_field'; diff --git a/frontend/src/app/useDocumentsSearch.ts b/frontend/src/app/useDocumentsSearch.ts index e0db80b..be2ad34 100644 --- a/frontend/src/app/useDocumentsSearch.ts +++ b/frontend/src/app/useDocumentsSearch.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import type { Dispatch, SetStateAction } from 'react'; -import { TAG_FILTER_UNTAGGED } from './appLayoutUtils'; +import { TAG_FILTER_UNTAGGED } from './workspaceUtils'; import { listDocuments } from '../lib/apiClient'; type Identifier = string | number; diff --git a/frontend/src/app/useWorkspaceSurface.tsx b/frontend/src/app/useWorkspaceSurface.tsx index 6b94b9c..1a97459 100644 --- a/frontend/src/app/useWorkspaceSurface.tsx +++ b/frontend/src/app/useWorkspaceSurface.tsx @@ -11,7 +11,6 @@ type Identifier = string | number; type EnsureAssetUrl = (docId: Identifier, asset: unknown, options?: Record) => Promise | void; type EnsurePreviewData = (docId: Identifier, options?: Record) => Promise; type GetDocumentAsset = (document: unknown, assetType: string) => unknown; -type ResolveApiPath = (path: string) => string; type NotifyApiError = (error: unknown, fallbackMessage?: string) => void; type WorkspaceSurface = { content: ReactNode; detail?: ReactNode | null } | null; @@ -28,7 +27,6 @@ interface UseWorkspaceSurfaceArgs { ensureAssetUrl?: EnsureAssetUrl; ensurePreviewData?: EnsurePreviewData; getDocumentAsset?: GetDocumentAsset; - resolveApiPath?: ResolveApiPath; notifyApiError?: NotifyApiError; closeDocumentPreview?: () => void; } @@ -49,7 +47,6 @@ export const useWorkspaceSurface = ({ ensureAssetUrl, ensurePreviewData, getDocumentAsset, - resolveApiPath, notifyApiError, closeDocumentPreview, }: UseWorkspaceSurfaceArgs): UseWorkspaceSurfaceResult => { @@ -92,34 +89,34 @@ export const useWorkspaceSurface = ({ const detail = detailPanelOpen && detailPanelProps ? (() => { - const { - onClose, - onOpenPreview, - tags: tagOptions, - folderNodes, - ensureFolderData, - ...restDetailProps - } = detailPanelProps; - const viewer = ( - - ); - if (folderNodes && ensureFolderData) { - return ( - - {viewer} - - ); - } + const { + onClose, + onOpenPreview, + tags: tagOptions, + folderNodes, + ensureFolderData, + ...restDetailProps + } = detailPanelProps; + const viewer = ( + + ); + if (folderNodes && ensureFolderData) { return ( - <>{viewer} + + {viewer} + ); - })() + } + return ( + <>{viewer} + ); + })() : null; return { @@ -179,7 +176,6 @@ export const useWorkspaceSurface = ({ ensureAssetUrl={ensureAssetUrl} getDocumentAsset={getDocumentAsset} ensurePreviewData={ensurePreviewData} - resolveApiPath={resolveApiPath} notifyApiError={notifyApiError} sidebarToggle={sidebarToggle} onClosePanel={closeDocumentPreview} @@ -189,10 +185,10 @@ export const useWorkspaceSurface = ({ const content = folderNodes && ensureFolderData ? ( - - {viewer} - - ) + + {viewer} + + ) : viewer; return { content, detail: null }; @@ -203,7 +199,6 @@ export const useWorkspaceSurface = ({ ensurePreviewData, ensureAssetUrl, getDocumentAsset, - resolveApiPath, notifyApiError, renderSidebarToggle, closeDocumentPreview, diff --git a/frontend/src/app/workspaceUtils.ts b/frontend/src/app/workspaceUtils.ts new file mode 100644 index 0000000..a81b2e8 --- /dev/null +++ b/frontend/src/app/workspaceUtils.ts @@ -0,0 +1,65 @@ +export const DEFAULT_FOLDER_NAME = 'Documents'; +export const DEFAULT_SORT_FIELD = 'title'; +export const DEFAULT_SORT_DIRECTION = 'asc'; +export const SORT_FIELD_VALUES = ['title', 'issued_at', 'created_at', 'updated_at']; +export const TAG_FILTER_UNTAGGED = '__UNTAGGED__'; + +const ROW_KEY_SEPARATOR = ':'; +const DOCUMENT_ROW_PREFIX = 'document'; +const FOLDER_ROW_PREFIX = 'folder'; + +const makeRowKey = (type, id) => + id ? `${type}${ROW_KEY_SEPARATOR}${id}` : `${type}${ROW_KEY_SEPARATOR}`; + +const getRowType = (key: string | null) => (key ?? '').split(ROW_KEY_SEPARATOR, 1)[0] ?? ''; + +export const getRowId = (key: string) => { + const parts = key.split(ROW_KEY_SEPARATOR); + return parts.slice(1).join(ROW_KEY_SEPARATOR); +}; + +export const isDocumentRowKey = (key) => getRowType(key) === DOCUMENT_ROW_PREFIX; +export const isFolderRowKey = (key) => getRowType(key) === FOLDER_ROW_PREFIX; + +export const resolveDocumentRowKey = (documentId) => + documentId ? makeRowKey(DOCUMENT_ROW_PREFIX, documentId) : null; + +export const resolveFolderRowKey = (folderId) => + folderId ? makeRowKey(FOLDER_ROW_PREFIX, folderId) : null; + +export const hasFiles = (event) => + Array.from(event.dataTransfer?.types || []).includes('Files'); + +const mergeAssetIntoGroup = (group, assetData) => { + if (!assetData || !assetData.asset_type) { + return group || []; + } + + const list = Array.isArray(group) ? group : []; + const index = list.findIndex((item) => item?.asset_type === assetData.asset_type); + if (index >= 0) { + const next = list.slice(); + next[index] = assetData; + return next; + } + return list.concat(assetData); +}; + +export const mergeAssetIntoDocument = (doc, assetData) => { + if (!doc) return doc; + const nextGroup = mergeAssetIntoGroup(doc.current_version?.assets, assetData); + return { + ...doc, + current_version: { ...(doc.current_version || {}), assets: nextGroup }, + }; +}; + +export const createRootNode = () => ({ + id: 'root', + name: DEFAULT_FOLDER_NAME, + parentId: null, + children: [], + expanded: true, + loaded: false, + hasChildren: false, +}); diff --git a/frontend/src/asset_manager.ts b/frontend/src/asset_manager.ts index f2fbd00..1b207be 100644 --- a/frontend/src/asset_manager.ts +++ b/frontend/src/asset_manager.ts @@ -42,7 +42,7 @@ export const resolveAssetUrl = (asset?: { download?: { url: string } | null } | export type EnsureAssetUrl = ( documentId: Identifier, asset: AssetLike, - options?: { force?: boolean; [key: string]: unknown }, + options?: { force?: boolean;[key: string]: unknown }, ) => Promise; export type GetAsset = (document: DocumentLike, assetType: string) => Nullable; @@ -171,7 +171,7 @@ export const resolveDocumentAssetUrl = ( }: { ensureAssetUrl?: EnsureAssetUrl; getAsset?: GetAsset; - ensureOptions?: { force?: boolean; [key: string]: unknown }; + ensureOptions?: { force?: boolean;[key: string]: unknown }; } = {}, ): Nullable => { if (!doc || !type) { @@ -191,24 +191,23 @@ export const resolveDocumentAssetUrl = ( } if (doc.id && asset.id && ensureAssetUrl) { const force = Boolean(url && expiresAt && expiresAt <= now); - const options: { force: boolean; [key: string]: unknown } = { + const options: { force: boolean;[key: string]: unknown } = { force, ...(ensureOptions || {}), }; - ensureAssetUrl(doc.id, asset, options).catch(() => {}); + ensureAssetUrl(doc.id, asset, options).catch(() => { }); } return null; }; class AssetManager { fetchAsset: ((id: Identifier) => Promise) | null; - assetPresignTtlMs: number; + assetCache: Map; assetInflight: Map>; - constructor({ fetchAsset, assetPresignTtlMs }: { fetchAsset: ((id: Identifier) => Promise) | null; assetPresignTtlMs: number }) { + constructor({ fetchAsset }: { fetchAsset: ((id: Identifier) => Promise) | null }) { this.fetchAsset = fetchAsset; - this.assetPresignTtlMs = assetPresignTtlMs; this.assetCache = new Map(); this.assetInflight = new Map(); } diff --git a/frontend/src/detail/useDetailWorkspace.ts b/frontend/src/detail/useDetailWorkspace.ts index 91a050e..2c4916b 100644 --- a/frontend/src/detail/useDetailWorkspace.ts +++ b/frontend/src/detail/useDetailWorkspace.ts @@ -6,7 +6,7 @@ import { DEFAULT_FOLDER_NAME, getRowId, isDocumentRowKey, -} from '../app/appLayoutUtils'; +} from '../app/workspaceUtils'; import type { DocumentInfoPanelProps } from '../documents/DocumentInfoPanel'; import type { EnsureAssetUrl, GetDocumentAsset } from '../utils/ocr'; @@ -46,7 +46,6 @@ interface UseDetailWorkspaceArgs { correspondents?: unknown[]; handleCorrespondentAdd?: (...args: unknown[]) => void; handleCorrespondentRemove?: (...args: unknown[]) => void; - resolveApiPath?: (path: string) => string; selectFolder?: (folderId?: Identifier | 'root') => void; tags?: unknown[]; tagLookupById?: Map | null; @@ -86,7 +85,6 @@ const useDetailWorkspace = ({ correspondents, handleCorrespondentAdd, handleCorrespondentRemove, - resolveApiPath, selectFolder, tags, tagLookupById, @@ -281,7 +279,6 @@ const useDetailWorkspace = ({ correspondents, onCorrespondentAdd: handleCorrespondentAdd, onCorrespondentRemove: handleCorrespondentRemove, - resolveApiPath, onFolderNavigate: selectFolder, onClose: handleDetailPanelClose, resolveFolderPath, @@ -304,7 +301,6 @@ const useDetailWorkspace = ({ folderNodes, ensureFolderData, openDocumentPreview, - resolveApiPath, resolveFolderPath, selectFolder, tags, diff --git a/frontend/src/documents/documentActions.ts b/frontend/src/documents/documentActions.ts index c1875c2..f635f44 100644 --- a/frontend/src/documents/documentActions.ts +++ b/frontend/src/documents/documentActions.ts @@ -6,13 +6,11 @@ import type { DocumentLike as OcrDocumentLike, } from '../utils/ocr'; -type ResolveApiPath = (path: string) => string; - export type DocumentLike = OcrDocumentLike; const asyncFalse = async () => false; -const resolveDocumentDownloadHref = (document?: DocumentLike | null, resolveApiPath?: ResolveApiPath | null): string | null => { +const resolveDocumentDownloadHref = (document?: DocumentLike | null): string | null => { if (!document) { return null; } @@ -20,7 +18,7 @@ const resolveDocumentDownloadHref = (document?: DocumentLike | null, resolveApiP if (!downloadUrl) { return null; } - return resolveApiPath ? resolveApiPath(downloadUrl) : downloadUrl; + return downloadUrl; }; const hasDocumentOcrAsset = (document?: DocumentLike | null, getDocumentAsset?: GetDocumentAsset | null): boolean => { @@ -32,7 +30,6 @@ const hasDocumentOcrAsset = (document?: DocumentLike | null, getDocumentAsset?: interface CreateDocumentActionStateArgs { document: DocumentLike | null; - resolveApiPath?: ResolveApiPath | null; ensurePreviewData: EnsurePreviewData; ensureAssetUrl: EnsureAssetUrl; getDocumentAsset?: GetDocumentAsset | null; @@ -42,7 +39,6 @@ interface CreateDocumentActionStateArgs { export const createDocumentActionState = ({ document, - resolveApiPath, ensurePreviewData, ensureAssetUrl, getDocumentAsset, @@ -57,27 +53,27 @@ export const createDocumentActionState = ({ }; } - const downloadHref = resolveDocumentDownloadHref(document, resolveApiPath); + const downloadHref = resolveDocumentDownloadHref(document); const hasOcr = hasDocumentOcrAsset(document, getDocumentAsset); const openOcr = hasOcr ? async () => { - try { - const success = await openOcrTextInNewTab({ - document, - ensurePreviewData, - getDocumentAsset, - ensureAssetUrl, - }); - if (!success) { - notifyApiError?.(new Error('OCR text URL unavailable.'), ocrErrorMessage); - } - return success; - } catch (error) { - notifyApiError?.(error, ocrErrorMessage); - throw error; + try { + const success = await openOcrTextInNewTab({ + document, + ensurePreviewData, + getDocumentAsset, + ensureAssetUrl, + }); + if (!success) { + notifyApiError?.(new Error('OCR text URL unavailable.'), ocrErrorMessage); } + return success; + } catch (error) { + notifyApiError?.(error, ocrErrorMessage); + throw error; } + } : asyncFalse; return { diff --git a/frontend/src/documents/documentSummary.ts b/frontend/src/documents/documentSummary.ts index e883a64..1537cd1 100644 --- a/frontend/src/documents/documentSummary.ts +++ b/frontend/src/documents/documentSummary.ts @@ -1,6 +1,6 @@ import { formatFileSize } from '../utils/format'; import { formatDateTime as defaultFormatDateTime } from '../utils/date'; -import { DEFAULT_FOLDER_NAME } from '../app/appLayoutUtils'; +import { DEFAULT_FOLDER_NAME } from '../app/workspaceUtils'; interface DocumentPageMetadata { page_count?: number | string | null; diff --git a/frontend/src/folders/FolderManagerContext.tsx b/frontend/src/folders/FolderManagerContext.tsx index 9fbb1a6..85fcc83 100644 --- a/frontend/src/folders/FolderManagerContext.tsx +++ b/frontend/src/folders/FolderManagerContext.tsx @@ -1,5 +1,5 @@ import React, { createContext, useContext, useMemo, type ReactNode } from 'react'; -import { DEFAULT_FOLDER_NAME } from '../app/appLayoutUtils'; +import { DEFAULT_FOLDER_NAME } from '../app/workspaceUtils'; type FolderId = string | null; diff --git a/frontend/src/hooks/documents/useDocumentMutations.ts b/frontend/src/hooks/documents/useDocumentMutations.ts index 4dd5477..6e1649a 100644 --- a/frontend/src/hooks/documents/useDocumentMutations.ts +++ b/frontend/src/hooks/documents/useDocumentMutations.ts @@ -1,7 +1,7 @@ import { useCallback } from 'react'; import type { Dispatch, MutableRefObject, SetStateAction } from 'react'; -import { DEFAULT_FOLDER_NAME, getRowId, isDocumentRowKey } from '../../app/appLayoutUtils'; +import { DEFAULT_FOLDER_NAME, getRowId, isDocumentRowKey } from '../../app/workspaceUtils'; import { addDocumentTags, createTag, diff --git a/frontend/src/hooks/documents/useDocumentUploads.ts b/frontend/src/hooks/documents/useDocumentUploads.ts index 9f96637..c4a3791 100644 --- a/frontend/src/hooks/documents/useDocumentUploads.ts +++ b/frontend/src/hooks/documents/useDocumentUploads.ts @@ -1,7 +1,7 @@ import { useCallback, useRef, useState } from 'react'; import type { Dispatch, MutableRefObject, SetStateAction } from 'react'; import useFileDrop from './useFileDrop'; -import { DEFAULT_FOLDER_NAME, hasFiles } from '../../app/appLayoutUtils'; +import { DEFAULT_FOLDER_NAME, hasFiles } from '../../app/workspaceUtils'; import { fetchDocument } from '../../lib/apiClient'; type Identifier = string | number; @@ -87,9 +87,9 @@ const mapFilesToEntries = (filesInput?: FileList | File[] | null): FileEntry[] = const relativePath = (file as File & { webkitRelativePath?: string })?.webkitRelativePath ?? ''; const segments = relativePath ? relativePath - .split('/') - .slice(0, -1) - .filter(Boolean) + .split('/') + .slice(0, -1) + .filter(Boolean) : []; return { file, segments }; }); @@ -325,9 +325,9 @@ const useDocumentUploads = ({ const relativePath = (fileFromItem as File & { webkitRelativePath?: string })?.webkitRelativePath ?? ''; const segments = relativePath ? relativePath - .split('/') - .slice(0, -1) - .filter(Boolean) + .split('/') + .slice(0, -1) + .filter(Boolean) : []; pushFile(fileFromItem, segments); } @@ -355,9 +355,9 @@ const useDocumentUploads = ({ const relativePath = (file as File & { webkitRelativePath?: string })?.webkitRelativePath ?? ''; const segments = relativePath ? relativePath - .split('/') - .slice(0, -1) - .filter(Boolean) + .split('/') + .slice(0, -1) + .filter(Boolean) : []; pushFile(file, segments); }); diff --git a/frontend/src/hooks/documents/useDocumentsWorkspace.ts b/frontend/src/hooks/documents/useDocumentsWorkspace.ts index df9582f..77a0071 100644 --- a/frontend/src/hooks/documents/useDocumentsWorkspace.ts +++ b/frontend/src/hooks/documents/useDocumentsWorkspace.ts @@ -29,7 +29,7 @@ import useDocumentsPanelProps from '../../documents/hooks/useDocumentsPanelProps import useDocumentPreview from '../../app/useDocumentPreview'; import useSidebarProps from '../../sidebar/useSidebarProps'; import { - ASSET_PRESIGN_TTL_MS, + DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD, createRootNode, @@ -37,10 +37,9 @@ import { isDocumentRowKey, isFolderRowKey, mergeAssetIntoDocument, - resolveApiPath, resolveDocumentRowKey, resolveFolderRowKey, -} from '../../app/appLayoutUtils'; +} from '../../app/workspaceUtils'; import useDocumentsSearch from '../../app/useDocumentsSearch'; import useDocumentsStore from './store/useDocumentsStore'; import useAuthManager from './useAuthManager'; @@ -64,7 +63,7 @@ const EntryType = Object.freeze({ folder: 'folder', }); -const noop = () => {}; +const noop = () => { }; type Identifier = string | number; type DocumentId = Identifier; @@ -79,7 +78,7 @@ interface DocumentLike { interface FolderContentsEntry { folder?: { id?: FolderId; name?: string | null } | null; documents?: DocumentLike[]; - subfolders?: Array<{ id?: FolderId; name?: string | null; [key: string]: unknown }>; + subfolders?: Array<{ id?: FolderId; name?: string | null;[key: string]: unknown }>; __includesDocuments?: boolean; __sortField?: string | null; __sortDirection?: string | null; @@ -189,7 +188,7 @@ const useDocumentsWorkspace = ({ const breadcrumbFetchRef = useRef(new Set()); const tagRemovalCursorActiveRef = useRef(false); const tenantIdRef = useRef(currentTenantId); - const detailPanelControlRef = useRef({ open: () => {}, close: () => {} }); + const detailPanelControlRef = useRef({ open: () => { }, close: () => { } }); const setTagRemovalCursor = useCallback((active) => { if (tagRemovalCursorActiveRef.current === active) { return; @@ -222,7 +221,7 @@ const useDocumentsWorkspace = ({ const asset = await fetchAsset(id); return (asset as unknown) as any; }; - assetManagerRef.current = new AssetManager({ fetchAsset: fetcher, assetPresignTtlMs: ASSET_PRESIGN_TTL_MS }); + assetManagerRef.current = new AssetManager({ fetchAsset: fetcher }); } const assetManager = assetManagerRef.current; @@ -328,7 +327,7 @@ const useDocumentsWorkspace = ({ () => documentsManager.getSnapshot(), () => documentsManager.getSnapshot(), ); - + const { folderNodes, setFolderNodes, @@ -425,8 +424,8 @@ const useDocumentsWorkspace = ({ showingSearchResults ? [] : currentSubfolders - .map((folder) => resolveFolderRowKey(folder.id)) - .filter(Boolean), + .map((folder) => resolveFolderRowKey(folder.id)) + .filter(Boolean), [showingSearchResults, currentSubfolders], ); @@ -846,7 +845,7 @@ const useDocumentsWorkspace = ({ const initializeAfterLogin = useCallback(async () => { await Promise.all([refreshTags(), refreshCorrespondents()]); const initialFolder = routeFolderId && routeFolderId !== 'root' ? routeFolderId : 'root'; - await loadFolder(initialFolder, {} ); + await loadFolder(initialFolder, {}); }, [refreshTags, refreshCorrespondents, routeFolderId, loadFolder]); useEffect(() => { @@ -960,12 +959,6 @@ const useDocumentsWorkspace = ({ [assetManager, updateDocumentCaches, notifyApiError], ); - - - - - - const handleDocumentTagDrop = useCallback( async (documentId, tag) => { if (!documentId || !tag?.id) { @@ -1176,7 +1169,6 @@ const useDocumentsWorkspace = ({ correspondents, handleCorrespondentAdd, handleCorrespondentRemove, - resolveApiPath, selectFolder, tags, tagLookupById, @@ -1462,5 +1454,4 @@ const useDocumentsWorkspace = ({ }; }; - export default useDocumentsWorkspace; diff --git a/frontend/src/hooks/documents/useFolderTree.ts b/frontend/src/hooks/documents/useFolderTree.ts index 5bf9980..277c072 100644 --- a/frontend/src/hooks/documents/useFolderTree.ts +++ b/frontend/src/hooks/documents/useFolderTree.ts @@ -8,7 +8,7 @@ import { isFolderRowKey, resolveDocumentRowKey, resolveFolderRowKey, -} from '../../app/appLayoutUtils'; +} from '../../app/workspaceUtils'; type Identifier = string | number; type FolderId = Identifier | 'root'; diff --git a/frontend/src/hooks/documents/useFolderTreeActions.ts b/frontend/src/hooks/documents/useFolderTreeActions.ts index 71ef6b4..781840b 100644 --- a/frontend/src/hooks/documents/useFolderTreeActions.ts +++ b/frontend/src/hooks/documents/useFolderTreeActions.ts @@ -1,6 +1,6 @@ import { useCallback, useMemo } from 'react'; import type { DragEvent } from 'react'; -import { DEFAULT_FOLDER_NAME, hasFiles } from '../../app/appLayoutUtils'; +import { DEFAULT_FOLDER_NAME, hasFiles } from '../../app/workspaceUtils'; import { createFolder, deleteFolder, diff --git a/frontend/src/hooks/documents/useWorkspaceBreadcrumbs.ts b/frontend/src/hooks/documents/useWorkspaceBreadcrumbs.ts index b1b7922..9d422e2 100644 --- a/frontend/src/hooks/documents/useWorkspaceBreadcrumbs.ts +++ b/frontend/src/hooks/documents/useWorkspaceBreadcrumbs.ts @@ -1,5 +1,5 @@ import React, { useEffect, useMemo } from 'react'; -import { DEFAULT_FOLDER_NAME } from '../../app/appLayoutUtils'; +import { DEFAULT_FOLDER_NAME } from '../../app/workspaceUtils'; type Identifier = string | number; type FolderId = Identifier | 'root'; diff --git a/frontend/src/preview/DocumentViewerPanel.tsx b/frontend/src/preview/DocumentViewerPanel.tsx index 4da9967..3926ba6 100644 --- a/frontend/src/preview/DocumentViewerPanel.tsx +++ b/frontend/src/preview/DocumentViewerPanel.tsx @@ -62,7 +62,6 @@ interface DocumentViewerPanelProps extends DocumentSummarySectionProps { ensureAssetUrl?: (docId: string | number, asset: AssetLike, options?: { force?: boolean }) => Promise; getDocumentAsset?: (doc: DocumentLike | null, type: string) => AssetLike | null; ensurePreviewData?: (docId: string | number, options?: { signal?: AbortSignal }) => Promise; - resolveApiPath?: (path: string) => string; notifyApiError?: (error: unknown, fallbackMessage?: string) => void; sidebarToggle?: ReactNode; onClosePanel?: () => void; @@ -131,7 +130,6 @@ const DocumentViewerPanel: React.FC = ({ ensureAssetUrl, getDocumentAsset, ensurePreviewData, - resolveApiPath, notifyApiError, sidebarToggle = null, onClosePanel, @@ -264,7 +262,7 @@ const DocumentViewerPanel: React.FC = ({ return null; } const downloadUrl = document.current_version?.download?.url; - const href = resolveApiPath ? resolveApiPath(downloadUrl) : downloadUrl; + const href = downloadUrl; if (!href) { return null; } @@ -275,7 +273,7 @@ const DocumentViewerPanel: React.FC = ({ mimeType, filename, }; - }, [document, resolveApiPath]); + }, [document]); const handleZoomOpen = useCallback(() => { if (!resolvedDocumentLink?.url) { @@ -311,18 +309,16 @@ const DocumentViewerPanel: React.FC = ({ () => document ? createDocumentActionState({ - document, - resolveApiPath, - ensurePreviewData, - ensureAssetUrl, - getDocumentAsset, - notifyApiError, - ocrErrorMessage: 'Unable to open OCR text.', - }) + document, + ensurePreviewData, + ensureAssetUrl, + getDocumentAsset, + notifyApiError, + ocrErrorMessage: 'Unable to open OCR text.', + }) : null, [ document, - resolveApiPath, ensurePreviewData, ensureAssetUrl, getDocumentAsset, @@ -337,8 +333,8 @@ const DocumentViewerPanel: React.FC = ({ const folderSegments = resolveFolderPath(document.folder_id); const normalizedSegments = Array.isArray(folderSegments) ? folderSegments - .filter((segment) => segment && segment.id && segment.name) - .map((segment) => ({ id: segment.id, name: segment.name })) + .filter((segment) => segment && segment.id && segment.name) + .map((segment) => ({ id: segment.id, name: segment.name })) : []; return [ @@ -380,62 +376,62 @@ const DocumentViewerPanel: React.FC = ({ const collapseButton = isSidebarVariant && onCollapsePanel ? ( - - ) + + ) : null; const maximizeButton = isSidebarVariant && onMaximizePanel ? ( - - ) + + ) : null; const closeButton = !isSidebarVariant && onClosePanel ? ( - - ) + + ) : null; const headerLeadingButtons = isSidebarVariant ? [ - collapseButton ? {collapseButton} : null, - maximizeButton ? {maximizeButton} : null, - ].filter(Boolean) + collapseButton ? {collapseButton} : null, + maximizeButton ? {maximizeButton} : null, + ].filter(Boolean) : [ - sidebarToggle ? {sidebarToggle} : null, - closeButton ? {closeButton} : null, - ].filter(Boolean); + sidebarToggle ? {sidebarToggle} : null, + closeButton ? {closeButton} : null, + ].filter(Boolean); const headerLeadingContent = headerLeadingButtons.length ? headerLeadingButtons : null; const resizeHandle = isSidebarVariant ? ( diff --git a/frontend/src/sidebar/useSidebarProps.ts b/frontend/src/sidebar/useSidebarProps.ts index c80df36..2699f7d 100644 --- a/frontend/src/sidebar/useSidebarProps.ts +++ b/frontend/src/sidebar/useSidebarProps.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react'; import type { DragEvent } from 'react'; -import { TAG_FILTER_UNTAGGED } from '../app/appLayoutUtils'; +import { TAG_FILTER_UNTAGGED } from '../app/workspaceUtils'; import type { StatusMessage } from '../hooks/documents/store/useDocumentsStore'; type Identifier = string | number;