This commit is contained in:
2025-10-31 02:06:38 +01:00
parent 17db827e3a
commit f796932cd8
6 changed files with 493 additions and 306 deletions
+67 -227
View File
@@ -13,14 +13,11 @@ import AssetManager, {
createAssetView,
} from '../asset_manager';
import useApiError from '../hooks/useApiError';
import TagsPanel from '../tags/TagsPanel';
import CorrespondentsPanel from '../correspondents/CorrespondentsPanel';
import DocumentTypesPanel from '../documentTypes/DocumentTypesPanel';
import TagManager from '../tag_manager';
import SettingsModal from '../settings/SettingsModal';
import usePasskeys from '../settings/usePasskeys';
import { AppShellContext } from '../appShellContext';
import DropOverlay from './DropOverlay';
import { useManagementModals } from './useManagementModals';
import { api, useAppDispatch, useAppState } from './appState';
const ASSET_PRESIGN_TTL_MS = 240 * 1000; // backend issues 5 min tokens; refresh slightly early
@@ -182,10 +179,6 @@ const AppLayout = () => {
[reportApiError],
);
const [loading, setLoading] = useState(false);
const [isTagsModalOpen, setTagsModalOpen] = useState(false);
const [isCorrespondentsModalOpen, setCorrespondentsModalOpen] = useState(false);
const [isDocumentTypesModalOpen, setDocumentTypesModalOpen] = useState(false);
const [isSettingsModalOpen, setSettingsModalOpen] = useState(false);
const [creatingFolder, setCreatingFolder] = useState(false);
const [folderNodes, setFolderNodes] = useState(() => {
const rootNode = createRootNode();
@@ -1596,20 +1589,6 @@ const AppLayout = () => {
token,
});
useEffect(() => {
if (!isSettingsModalOpen) {
return;
}
refreshWebdavTokens();
refreshPasskeys();
}, [isSettingsModalOpen, refreshPasskeys, refreshWebdavTokens]);
useEffect(() => {
if (!isSettingsModalOpen) {
setWebdavTokenSecret(null);
}
}, [isSettingsModalOpen]);
const handleTagUpdate = useCallback(
async (tagId, changes) => {
if (!tagId) {
@@ -4059,83 +4038,34 @@ const AppLayout = () => {
}
}, [creatingFolder, handleFolderCreate, setStatusMessage]);
const openTagsModal = useCallback(() => {
setCorrespondentsModalOpen(false);
setDocumentTypesModalOpen(false);
setTagsModalOpen(true);
}, []);
const {
managementModals,
openTagsModal,
openCorrespondentsModal,
openDocumentTypesModal,
} = useManagementModals({
locationPathname: location.pathname,
tags,
refreshTags,
onTagCreate: handleTagCreate,
onTagUpdate: handleTagUpdate,
onTagDelete: handleTagDelete,
correspondents,
refreshCorrespondents,
onCorrespondentCreate: handleCorrespondentCreate,
onCorrespondentUpdate: handleCorrespondentUpdate,
onCorrespondentDelete: handleCorrespondentDelete,
documentTypes,
refreshDocumentTypes,
onDocumentTypeCreate: handleDocumentTypeCreate,
onDocumentTypeUpdate: handleDocumentTypeUpdate,
onDocumentTypeDelete: handleDocumentTypeDelete,
setStatusMessage,
});
const closeTagsModal = useCallback(() => {
setTagsModalOpen(false);
}, []);
const openCorrespondentsModal = useCallback(() => {
setTagsModalOpen(false);
setCorrespondentsModalOpen(true);
setDocumentTypesModalOpen(false);
}, []);
const closeCorrespondentsModal = useCallback(() => {
setCorrespondentsModalOpen(false);
}, []);
const openDocumentTypesModal = useCallback(() => {
setTagsModalOpen(false);
setCorrespondentsModalOpen(false);
setDocumentTypesModalOpen(true);
}, []);
const closeDocumentTypesModal = useCallback(() => {
setDocumentTypesModalOpen(false);
}, []);
const openSettingsModal = useCallback(() => {
setSettingsModalOpen(true);
}, []);
const closeSettingsModal = useCallback(() => {
setSettingsModalOpen(false);
}, []);
useEffect(() => {
setTagsModalOpen(false);
setCorrespondentsModalOpen(false);
setDocumentTypesModalOpen(false);
setSettingsModalOpen(false);
}, [location.pathname]);
useEffect(() => {
if (!isTagsModalOpen && !isCorrespondentsModalOpen && !isDocumentTypesModalOpen && !isSettingsModalOpen) {
return;
}
const handleKeyDown = (event) => {
if (event.key === 'Escape') {
event.preventDefault();
if (isTagsModalOpen) {
closeTagsModal();
} else if (isCorrespondentsModalOpen) {
closeCorrespondentsModal();
} else if (isDocumentTypesModalOpen) {
closeDocumentTypesModal();
} else if (isSettingsModalOpen) {
closeSettingsModal();
}
}
};
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [
isTagsModalOpen,
isCorrespondentsModalOpen,
isDocumentTypesModalOpen,
isSettingsModalOpen,
closeTagsModal,
closeCorrespondentsModal,
closeDocumentTypesModal,
closeSettingsModal,
]);
const openSettings = useCallback(() => {
navigate('/settings');
}, [navigate]);
useEffect(() => {
if (!token) return undefined;
@@ -5216,7 +5146,7 @@ const AppLayout = () => {
tenants: tenantOptions,
activeTenantId: currentTenantId,
onSelectTenant: handleTenantSelect,
onOpenSettings: openSettingsModal,
onOpenSettings: openSettings,
}),
[
activeCorrespondentFilters,
@@ -5237,7 +5167,7 @@ const AppLayout = () => {
handleSearchSubmit,
handleTenantSelect,
loading,
openSettingsModal,
openSettings,
previewActive,
searchQuery,
draggedFolderId,
@@ -5401,6 +5331,23 @@ const AppLayout = () => {
handleDocumentTypeSet,
handleBulkDocumentTypeSet,
handleBulkDocumentTypeClear,
webdavTokens,
webdavTokensLoading,
creatingWebdavToken,
deletingWebdavTokenId,
refreshWebdavTokens,
createWebdavToken,
deleteWebdavToken,
webdavTokenSecret,
dismissCreatedWebdavToken,
passkeys,
passkeysSupported,
passkeysLoading,
registeringPasskey,
revokingPasskeyId,
refreshPasskeys,
registerPasskey,
revokePasskey,
previewActive,
previewWorkspaceDocument,
previewWorkspaceEntry,
@@ -5420,7 +5367,7 @@ const AppLayout = () => {
openTagsModal,
openCorrespondentsModal,
openDocumentTypesModal,
openSettingsModal,
openSettings,
detailPanelOpen,
setDetailPanelOpen,
}),
@@ -5455,6 +5402,23 @@ const AppLayout = () => {
handleDocumentTypeSet,
handleBulkDocumentTypeSet,
handleBulkDocumentTypeClear,
webdavTokens,
webdavTokensLoading,
creatingWebdavToken,
deletingWebdavTokenId,
refreshWebdavTokens,
createWebdavToken,
deleteWebdavToken,
webdavTokenSecret,
dismissCreatedWebdavToken,
passkeys,
passkeysSupported,
passkeysLoading,
registeringPasskey,
revokingPasskeyId,
refreshPasskeys,
registerPasskey,
revokePasskey,
previewActive,
previewWorkspaceDocument,
previewWorkspaceEntry,
@@ -5473,7 +5437,7 @@ const AppLayout = () => {
openTagsModal,
openCorrespondentsModal,
openDocumentTypesModal,
openSettingsModal,
openSettings,
detailPanelOpen,
setDetailPanelOpen,
],
@@ -5502,131 +5466,7 @@ const AppLayout = () => {
folderName={dropOverlayState.folderName}
/>
<Outlet />
{isTagsModalOpen && (
<div
className="modal-backdrop"
role="presentation"
onClick={closeTagsModal}
>
<div
className="modal modal--panel"
role="dialog"
aria-modal="true"
aria-labelledby="tags-modal-title"
onClick={(event) => event.stopPropagation()}
>
<div className="panel-modal__header">
<h3 id="tags-modal-title">Manage Tags</h3>
<button type="button" className="secondary" onClick={closeTagsModal}>
Close
</button>
</div>
<div className="panel-modal__body">
<TagsPanel
tags={tags}
onRefresh={refreshTags}
onCreateTag={handleTagCreate}
onUpdateTag={handleTagUpdate}
onDeleteTag={handleTagDelete}
onNotify={setStatusMessage}
/>
</div>
</div>
</div>
)}
{isCorrespondentsModalOpen && (
<div
className="modal-backdrop"
role="presentation"
onClick={closeCorrespondentsModal}
>
<div
className="modal modal--panel"
role="dialog"
aria-modal="true"
aria-labelledby="correspondents-modal-title"
onClick={(event) => event.stopPropagation()}
>
<div className="panel-modal__header">
<h3 id="correspondents-modal-title">Manage Correspondents</h3>
<button
type="button"
className="secondary"
onClick={closeCorrespondentsModal}
>
Close
</button>
</div>
<div className="panel-modal__body">
<CorrespondentsPanel
correspondents={correspondents}
onRefresh={refreshCorrespondents}
onCreate={handleCorrespondentCreate}
onUpdate={handleCorrespondentUpdate}
onDelete={handleCorrespondentDelete}
onNotify={setStatusMessage}
/>
</div>
</div>
</div>
)}
{isDocumentTypesModalOpen && (
<div
className="modal-backdrop"
role="presentation"
onClick={closeDocumentTypesModal}
>
<div
className="modal modal--panel"
role="dialog"
aria-modal="true"
aria-labelledby="document-types-modal-title"
onClick={(event) => event.stopPropagation()}
>
<div className="panel-modal__header">
<h3 id="document-types-modal-title">Manage Document Types</h3>
<button
type="button"
className="secondary"
onClick={closeDocumentTypesModal}
>
Close
</button>
</div>
<div className="panel-modal__body">
<DocumentTypesPanel
documentTypes={documentTypes}
onRefresh={refreshDocumentTypes}
onCreate={handleDocumentTypeCreate}
onUpdate={handleDocumentTypeUpdate}
onDelete={handleDocumentTypeDelete}
onNotify={setStatusMessage}
/>
</div>
</div>
</div>
)}
<SettingsModal
open={isSettingsModalOpen}
onClose={closeSettingsModal}
tokens={webdavTokens}
loading={webdavTokensLoading}
creating={creatingWebdavToken}
deletingId={deletingWebdavTokenId}
onRefresh={refreshWebdavTokens}
onCreate={createWebdavToken}
onDelete={deleteWebdavToken}
createdToken={webdavTokenSecret}
onDismissCreatedToken={dismissCreatedWebdavToken}
passkeys={passkeys}
passkeysSupported={passkeysSupported}
passkeysLoading={passkeysLoading}
registeringPasskey={registeringPasskey}
revokingPasskeyId={revokingPasskeyId}
onRefreshPasskeys={refreshPasskeys}
onRegisterPasskey={registerPasskey}
onRevokePasskey={revokePasskey}
/>
{managementModals}
</div>
</AppShellContext.Provider>
);
+2
View File
@@ -3,6 +3,7 @@ import { Navigate, Route, Routes } from 'react-router-dom';
import AppLayout from './AppLayout';
import DocumentsRoute from './DocumentsRoute';
import LoginRoute from './LoginRoute';
import SettingsRoute from './SettingsRoute';
const AppRouter = () => (
<Routes>
@@ -12,6 +13,7 @@ const AppRouter = () => (
<Route path="/documents" element={<DocumentsRoute />} />
<Route path="/documents/folder/:folderId" element={<DocumentsRoute />} />
<Route path="/documents/:documentId" element={<DocumentsRoute />} />
<Route path="/settings" element={<SettingsRoute />} />
<Route path="*" element={<Navigate to="/documents" replace />} />
</Route>
</Routes>
+9 -79
View File
@@ -1,11 +1,8 @@
import React, { useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { ChevronsRightIcon } from '../ui/icons';
import { createDocumentsSurface } from '../documents/DocumentsTable';
import { createPreviewSurface } from '../preview/PreviewWorkspace';
import { createDesktopSurface } from '../DesktopWorkspace';
import { useAppShell } from '../appShellContext';
import DocumentsLayout from './DocumentsLayout';
import { useWorkspaceSurface } from './useWorkspaceSurface';
const DocumentsRoute = () => {
const {
@@ -63,66 +60,14 @@ const DocumentsRoute = () => {
navigate(target);
}, [navigate, parentBreadcrumb]);
const isWorkspace = workspaceMode === 'skeuo';
const isPreviewWorkspace = Boolean(previewWorkspaceDocument);
const showPreviewWorkspace = !isWorkspace && isPreviewWorkspace;
const renderSidebarToggle = useCallback(() => {
if (!sidebarCollapsed) {
return null;
}
return (
<button
type="button"
className="icon-button"
onClick={expandSidebar}
aria-label="Expand sidebar"
title="Expand sidebar"
>
<ChevronsRightIcon />
</button>
);
}, [sidebarCollapsed, expandSidebar]);
const documentsSurface = useMemo(() => {
if (!documentsTableProps) {
return null;
}
return createDocumentsSurface({
tableProps: documentsTableProps,
parentBreadcrumb,
onNavigateParent: parentBreadcrumb ? handleNavigateParent : null,
renderSidebarToggle,
detailProps: detailPanelProps,
detailOpen: detailPanelOpen,
});
}, [
const { surface } = useWorkspaceSurface({
sidebarCollapsed,
onExpandSidebar: expandSidebar,
documentsTableProps,
parentBreadcrumb,
handleNavigateParent,
renderSidebarToggle,
detailPanelProps,
detailPanelOpen,
]);
const previewSurface = useMemo(() => {
if (!showPreviewWorkspace || !previewWorkspaceDocument) {
return null;
}
return createPreviewSurface({
document: previewWorkspaceDocument,
previewEntry: previewWorkspaceEntry,
ensureAssetUrl,
ensurePreviewData,
getDocumentAsset,
resolveApiPath,
notifyApiError,
onRegenerate: handleThumbnailRegeneration,
onClose: closeDocumentPreview,
renderSidebarToggle,
});
}, [
showPreviewWorkspace,
workspaceMode,
skeuoWorkspaceProps,
previewWorkspaceDocument,
previewWorkspaceEntry,
ensureAssetUrl,
@@ -132,24 +77,9 @@ const DocumentsRoute = () => {
notifyApiError,
handleThumbnailRegeneration,
closeDocumentPreview,
renderSidebarToggle,
]);
const workspaceSurface = useMemo(() => {
if (!isWorkspace) {
return null;
}
return createDesktopSurface({
workspaceProps: skeuoWorkspaceProps,
renderSidebarToggle,
});
}, [isWorkspace, skeuoWorkspaceProps, renderSidebarToggle]);
const surface = showPreviewWorkspace
? previewSurface
: isWorkspace
? workspaceSurface
: documentsSurface;
parentBreadcrumb,
onNavigateParent: handleNavigateParent,
});
if (!surface) {
return (
+78
View File
@@ -0,0 +1,78 @@
import React, { useEffect, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import SettingsModal from '../settings/SettingsModal';
import { useAppShell } from '../appShellContext';
const SettingsRoute = () => {
const navigate = useNavigate();
const {
webdavTokens,
webdavTokensLoading,
creatingWebdavToken,
deletingWebdavTokenId,
refreshWebdavTokens,
createWebdavToken,
deleteWebdavToken,
webdavTokenSecret,
dismissCreatedWebdavToken,
passkeys,
passkeysSupported,
passkeysLoading,
registeringPasskey,
revokingPasskeyId,
refreshPasskeys,
registerPasskey,
revokePasskey,
} = useAppShell();
useEffect(() => {
refreshWebdavTokens();
refreshPasskeys();
}, [refreshWebdavTokens, refreshPasskeys]);
const handleClose = useCallback(() => {
dismissCreatedWebdavToken();
navigate(-1);
}, [dismissCreatedWebdavToken, navigate]);
useEffect(() => {
const handleKeyDown = (event) => {
if (event.key === 'Escape') {
event.preventDefault();
handleClose();
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [handleClose]);
useEffect(() => () => {
dismissCreatedWebdavToken();
}, [dismissCreatedWebdavToken]);
return (
<SettingsModal
open
onClose={handleClose}
tokens={webdavTokens}
loading={webdavTokensLoading}
creating={creatingWebdavToken}
deletingId={deletingWebdavTokenId}
onRefresh={refreshWebdavTokens}
onCreate={createWebdavToken}
onDelete={deleteWebdavToken}
createdToken={webdavTokenSecret}
onDismissCreatedToken={dismissCreatedWebdavToken}
passkeys={passkeys}
passkeysSupported={passkeysSupported}
passkeysLoading={passkeysLoading}
registeringPasskey={registeringPasskey}
revokingPasskeyId={revokingPasskeyId}
onRefreshPasskeys={refreshPasskeys}
onRegisterPasskey={registerPasskey}
onRevokePasskey={revokePasskey}
/>
);
};
export default SettingsRoute;
+219
View File
@@ -0,0 +1,219 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import TagsPanel from '../tags/TagsPanel';
import CorrespondentsPanel from '../correspondents/CorrespondentsPanel';
import DocumentTypesPanel from '../documentTypes/DocumentTypesPanel';
const TAGS_MODAL = 'tags';
const CORRESPONDENTS_MODAL = 'correspondents';
const DOCUMENT_TYPES_MODAL = 'document-types';
export const useManagementModals = ({
locationPathname,
tags,
refreshTags,
onTagCreate,
onTagUpdate,
onTagDelete,
correspondents,
refreshCorrespondents,
onCorrespondentCreate,
onCorrespondentUpdate,
onCorrespondentDelete,
documentTypes,
refreshDocumentTypes,
onDocumentTypeCreate,
onDocumentTypeUpdate,
onDocumentTypeDelete,
setStatusMessage,
}) => {
const [activeModal, setActiveModal] = useState(null);
const openTagsModal = useCallback(() => setActiveModal(TAGS_MODAL), []);
const openCorrespondentsModal = useCallback(
() => setActiveModal(CORRESPONDENTS_MODAL),
[],
);
const openDocumentTypesModal = useCallback(
() => setActiveModal(DOCUMENT_TYPES_MODAL),
[],
);
const closeActiveModal = useCallback(() => setActiveModal(null), []);
useEffect(() => {
setActiveModal(null);
}, [locationPathname]);
useEffect(() => {
if (!activeModal) {
return undefined;
}
const handleKeyDown = (event) => {
if (event.key === 'Escape') {
event.preventDefault();
setActiveModal(null);
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [activeModal]);
const tagModal = useMemo(() => {
if (activeModal !== TAGS_MODAL) {
return null;
}
return (
<div
className="modal-backdrop"
role="presentation"
onClick={closeActiveModal}
>
<div
className="modal modal--panel"
role="dialog"
aria-modal="true"
aria-labelledby="tags-modal-title"
onClick={(event) => event.stopPropagation()}
>
<div className="panel-modal__header">
<h3 id="tags-modal-title">Manage Tags</h3>
<button type="button" className="secondary" onClick={closeActiveModal}>
Close
</button>
</div>
<div className="panel-modal__body">
<TagsPanel
tags={tags}
onRefresh={refreshTags}
onCreateTag={onTagCreate}
onUpdateTag={onTagUpdate}
onDeleteTag={onTagDelete}
onNotify={setStatusMessage}
/>
</div>
</div>
</div>
);
}, [
activeModal,
closeActiveModal,
onTagCreate,
onTagDelete,
onTagUpdate,
refreshTags,
setStatusMessage,
tags,
]);
const correspondentsModal = useMemo(() => {
if (activeModal !== CORRESPONDENTS_MODAL) {
return null;
}
return (
<div
className="modal-backdrop"
role="presentation"
onClick={closeActiveModal}
>
<div
className="modal modal--panel"
role="dialog"
aria-modal="true"
aria-labelledby="correspondents-modal-title"
onClick={(event) => event.stopPropagation()}
>
<div className="panel-modal__header">
<h3 id="correspondents-modal-title">Manage Correspondents</h3>
<button type="button" className="secondary" onClick={closeActiveModal}>
Close
</button>
</div>
<div className="panel-modal__body">
<CorrespondentsPanel
correspondents={correspondents}
onRefresh={refreshCorrespondents}
onCreate={onCorrespondentCreate}
onUpdate={onCorrespondentUpdate}
onDelete={onCorrespondentDelete}
onNotify={setStatusMessage}
/>
</div>
</div>
</div>
);
}, [
activeModal,
closeActiveModal,
correspondents,
onCorrespondentCreate,
onCorrespondentDelete,
onCorrespondentUpdate,
refreshCorrespondents,
setStatusMessage,
]);
const documentTypesModal = useMemo(() => {
if (activeModal !== DOCUMENT_TYPES_MODAL) {
return null;
}
return (
<div
className="modal-backdrop"
role="presentation"
onClick={closeActiveModal}
>
<div
className="modal modal--panel"
role="dialog"
aria-modal="true"
aria-labelledby="document-types-modal-title"
onClick={(event) => event.stopPropagation()}
>
<div className="panel-modal__header">
<h3 id="document-types-modal-title">Manage Document Types</h3>
<button type="button" className="secondary" onClick={closeActiveModal}>
Close
</button>
</div>
<div className="panel-modal__body">
<DocumentTypesPanel
documentTypes={documentTypes}
onRefresh={refreshDocumentTypes}
onCreate={onDocumentTypeCreate}
onUpdate={onDocumentTypeUpdate}
onDelete={onDocumentTypeDelete}
onNotify={setStatusMessage}
/>
</div>
</div>
</div>
);
}, [
activeModal,
closeActiveModal,
documentTypes,
onDocumentTypeCreate,
onDocumentTypeDelete,
onDocumentTypeUpdate,
refreshDocumentTypes,
setStatusMessage,
]);
const managementModals = (
<>
{tagModal}
{correspondentsModal}
{documentTypesModal}
</>
);
return {
managementModals,
openTagsModal,
openCorrespondentsModal,
openDocumentTypesModal,
closeActiveModal,
activeModal,
};
};
export default useManagementModals;
+118
View File
@@ -0,0 +1,118 @@
import React, { useCallback, useMemo } from 'react';
import { ChevronsRightIcon } from '../ui/icons';
import { createDocumentsSurface } from '../documents/DocumentsTable';
import { createPreviewSurface } from '../preview/PreviewWorkspace';
import { createDesktopSurface } from '../DesktopWorkspace';
export const useWorkspaceSurface = ({
sidebarCollapsed,
onExpandSidebar,
documentsTableProps,
detailPanelProps,
detailPanelOpen,
workspaceMode,
skeuoWorkspaceProps,
previewWorkspaceDocument,
previewWorkspaceEntry,
ensureAssetUrl,
ensurePreviewData,
getDocumentAsset,
resolveApiPath,
notifyApiError,
handleThumbnailRegeneration,
closeDocumentPreview,
parentBreadcrumb,
onNavigateParent,
}) => {
const renderSidebarToggle = useCallback(() => {
if (!sidebarCollapsed) {
return null;
}
return (
<button
type="button"
className="icon-button"
onClick={onExpandSidebar}
aria-label="Expand sidebar"
title="Expand sidebar"
>
<ChevronsRightIcon />
</button>
);
}, [sidebarCollapsed, onExpandSidebar]);
const documentsSurface = useMemo(() => {
if (!documentsTableProps) {
return null;
}
return createDocumentsSurface({
tableProps: documentsTableProps,
parentBreadcrumb,
onNavigateParent: parentBreadcrumb ? onNavigateParent : null,
renderSidebarToggle,
detailProps: detailPanelProps,
detailOpen: detailPanelOpen,
});
}, [
documentsTableProps,
parentBreadcrumb,
onNavigateParent,
renderSidebarToggle,
detailPanelProps,
detailPanelOpen,
]);
const showPreviewWorkspace = workspaceMode !== 'skeuo' && Boolean(previewWorkspaceDocument);
const previewSurface = useMemo(() => {
if (!showPreviewWorkspace || !previewWorkspaceDocument) {
return null;
}
return createPreviewSurface({
document: previewWorkspaceDocument,
previewEntry: previewWorkspaceEntry,
ensureAssetUrl,
ensurePreviewData,
getDocumentAsset,
resolveApiPath,
notifyApiError,
onRegenerate: handleThumbnailRegeneration,
onClose: closeDocumentPreview,
renderSidebarToggle,
});
}, [
showPreviewWorkspace,
previewWorkspaceDocument,
previewWorkspaceEntry,
ensureAssetUrl,
ensurePreviewData,
getDocumentAsset,
resolveApiPath,
notifyApiError,
handleThumbnailRegeneration,
closeDocumentPreview,
renderSidebarToggle,
]);
const workspaceSurface = useMemo(() => {
if (workspaceMode !== 'skeuo') {
return null;
}
return createDesktopSurface({
workspaceProps: skeuoWorkspaceProps,
renderSidebarToggle,
});
}, [workspaceMode, skeuoWorkspaceProps, renderSidebarToggle]);
const surface = useMemo(() => {
if (showPreviewWorkspace) {
return previewSurface;
}
if (workspaceMode === 'skeuo') {
return workspaceSurface;
}
return documentsSurface;
}, [showPreviewWorkspace, workspaceMode, previewSurface, workspaceSurface, documentsSurface]);
return { surface };
};