cleanup
This commit is contained in:
+67
-227
@@ -13,14 +13,11 @@ import AssetManager, {
|
|||||||
createAssetView,
|
createAssetView,
|
||||||
} from '../asset_manager';
|
} from '../asset_manager';
|
||||||
import useApiError from '../hooks/useApiError';
|
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 TagManager from '../tag_manager';
|
||||||
import SettingsModal from '../settings/SettingsModal';
|
|
||||||
import usePasskeys from '../settings/usePasskeys';
|
import usePasskeys from '../settings/usePasskeys';
|
||||||
import { AppShellContext } from '../appShellContext';
|
import { AppShellContext } from '../appShellContext';
|
||||||
import DropOverlay from './DropOverlay';
|
import DropOverlay from './DropOverlay';
|
||||||
|
import { useManagementModals } from './useManagementModals';
|
||||||
import { api, useAppDispatch, useAppState } from './appState';
|
import { api, useAppDispatch, useAppState } from './appState';
|
||||||
|
|
||||||
const ASSET_PRESIGN_TTL_MS = 240 * 1000; // backend issues 5 min tokens; refresh slightly early
|
const ASSET_PRESIGN_TTL_MS = 240 * 1000; // backend issues 5 min tokens; refresh slightly early
|
||||||
@@ -182,10 +179,6 @@ const AppLayout = () => {
|
|||||||
[reportApiError],
|
[reportApiError],
|
||||||
);
|
);
|
||||||
const [loading, setLoading] = useState(false);
|
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 [creatingFolder, setCreatingFolder] = useState(false);
|
||||||
const [folderNodes, setFolderNodes] = useState(() => {
|
const [folderNodes, setFolderNodes] = useState(() => {
|
||||||
const rootNode = createRootNode();
|
const rootNode = createRootNode();
|
||||||
@@ -1596,20 +1589,6 @@ const AppLayout = () => {
|
|||||||
token,
|
token,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isSettingsModalOpen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
refreshWebdavTokens();
|
|
||||||
refreshPasskeys();
|
|
||||||
}, [isSettingsModalOpen, refreshPasskeys, refreshWebdavTokens]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isSettingsModalOpen) {
|
|
||||||
setWebdavTokenSecret(null);
|
|
||||||
}
|
|
||||||
}, [isSettingsModalOpen]);
|
|
||||||
|
|
||||||
const handleTagUpdate = useCallback(
|
const handleTagUpdate = useCallback(
|
||||||
async (tagId, changes) => {
|
async (tagId, changes) => {
|
||||||
if (!tagId) {
|
if (!tagId) {
|
||||||
@@ -4059,83 +4038,34 @@ const AppLayout = () => {
|
|||||||
}
|
}
|
||||||
}, [creatingFolder, handleFolderCreate, setStatusMessage]);
|
}, [creatingFolder, handleFolderCreate, setStatusMessage]);
|
||||||
|
|
||||||
const openTagsModal = useCallback(() => {
|
const {
|
||||||
setCorrespondentsModalOpen(false);
|
managementModals,
|
||||||
setDocumentTypesModalOpen(false);
|
openTagsModal,
|
||||||
setTagsModalOpen(true);
|
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(() => {
|
const openSettings = useCallback(() => {
|
||||||
setTagsModalOpen(false);
|
navigate('/settings');
|
||||||
}, []);
|
}, [navigate]);
|
||||||
|
|
||||||
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,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!token) return undefined;
|
if (!token) return undefined;
|
||||||
@@ -5216,7 +5146,7 @@ const AppLayout = () => {
|
|||||||
tenants: tenantOptions,
|
tenants: tenantOptions,
|
||||||
activeTenantId: currentTenantId,
|
activeTenantId: currentTenantId,
|
||||||
onSelectTenant: handleTenantSelect,
|
onSelectTenant: handleTenantSelect,
|
||||||
onOpenSettings: openSettingsModal,
|
onOpenSettings: openSettings,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
activeCorrespondentFilters,
|
activeCorrespondentFilters,
|
||||||
@@ -5237,7 +5167,7 @@ const AppLayout = () => {
|
|||||||
handleSearchSubmit,
|
handleSearchSubmit,
|
||||||
handleTenantSelect,
|
handleTenantSelect,
|
||||||
loading,
|
loading,
|
||||||
openSettingsModal,
|
openSettings,
|
||||||
previewActive,
|
previewActive,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
draggedFolderId,
|
draggedFolderId,
|
||||||
@@ -5401,6 +5331,23 @@ const AppLayout = () => {
|
|||||||
handleDocumentTypeSet,
|
handleDocumentTypeSet,
|
||||||
handleBulkDocumentTypeSet,
|
handleBulkDocumentTypeSet,
|
||||||
handleBulkDocumentTypeClear,
|
handleBulkDocumentTypeClear,
|
||||||
|
webdavTokens,
|
||||||
|
webdavTokensLoading,
|
||||||
|
creatingWebdavToken,
|
||||||
|
deletingWebdavTokenId,
|
||||||
|
refreshWebdavTokens,
|
||||||
|
createWebdavToken,
|
||||||
|
deleteWebdavToken,
|
||||||
|
webdavTokenSecret,
|
||||||
|
dismissCreatedWebdavToken,
|
||||||
|
passkeys,
|
||||||
|
passkeysSupported,
|
||||||
|
passkeysLoading,
|
||||||
|
registeringPasskey,
|
||||||
|
revokingPasskeyId,
|
||||||
|
refreshPasskeys,
|
||||||
|
registerPasskey,
|
||||||
|
revokePasskey,
|
||||||
previewActive,
|
previewActive,
|
||||||
previewWorkspaceDocument,
|
previewWorkspaceDocument,
|
||||||
previewWorkspaceEntry,
|
previewWorkspaceEntry,
|
||||||
@@ -5420,7 +5367,7 @@ const AppLayout = () => {
|
|||||||
openTagsModal,
|
openTagsModal,
|
||||||
openCorrespondentsModal,
|
openCorrespondentsModal,
|
||||||
openDocumentTypesModal,
|
openDocumentTypesModal,
|
||||||
openSettingsModal,
|
openSettings,
|
||||||
detailPanelOpen,
|
detailPanelOpen,
|
||||||
setDetailPanelOpen,
|
setDetailPanelOpen,
|
||||||
}),
|
}),
|
||||||
@@ -5455,6 +5402,23 @@ const AppLayout = () => {
|
|||||||
handleDocumentTypeSet,
|
handleDocumentTypeSet,
|
||||||
handleBulkDocumentTypeSet,
|
handleBulkDocumentTypeSet,
|
||||||
handleBulkDocumentTypeClear,
|
handleBulkDocumentTypeClear,
|
||||||
|
webdavTokens,
|
||||||
|
webdavTokensLoading,
|
||||||
|
creatingWebdavToken,
|
||||||
|
deletingWebdavTokenId,
|
||||||
|
refreshWebdavTokens,
|
||||||
|
createWebdavToken,
|
||||||
|
deleteWebdavToken,
|
||||||
|
webdavTokenSecret,
|
||||||
|
dismissCreatedWebdavToken,
|
||||||
|
passkeys,
|
||||||
|
passkeysSupported,
|
||||||
|
passkeysLoading,
|
||||||
|
registeringPasskey,
|
||||||
|
revokingPasskeyId,
|
||||||
|
refreshPasskeys,
|
||||||
|
registerPasskey,
|
||||||
|
revokePasskey,
|
||||||
previewActive,
|
previewActive,
|
||||||
previewWorkspaceDocument,
|
previewWorkspaceDocument,
|
||||||
previewWorkspaceEntry,
|
previewWorkspaceEntry,
|
||||||
@@ -5473,7 +5437,7 @@ const AppLayout = () => {
|
|||||||
openTagsModal,
|
openTagsModal,
|
||||||
openCorrespondentsModal,
|
openCorrespondentsModal,
|
||||||
openDocumentTypesModal,
|
openDocumentTypesModal,
|
||||||
openSettingsModal,
|
openSettings,
|
||||||
detailPanelOpen,
|
detailPanelOpen,
|
||||||
setDetailPanelOpen,
|
setDetailPanelOpen,
|
||||||
],
|
],
|
||||||
@@ -5502,131 +5466,7 @@ const AppLayout = () => {
|
|||||||
folderName={dropOverlayState.folderName}
|
folderName={dropOverlayState.folderName}
|
||||||
/>
|
/>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
{isTagsModalOpen && (
|
{managementModals}
|
||||||
<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}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</AppShellContext.Provider>
|
</AppShellContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Navigate, Route, Routes } from 'react-router-dom';
|
|||||||
import AppLayout from './AppLayout';
|
import AppLayout from './AppLayout';
|
||||||
import DocumentsRoute from './DocumentsRoute';
|
import DocumentsRoute from './DocumentsRoute';
|
||||||
import LoginRoute from './LoginRoute';
|
import LoginRoute from './LoginRoute';
|
||||||
|
import SettingsRoute from './SettingsRoute';
|
||||||
|
|
||||||
const AppRouter = () => (
|
const AppRouter = () => (
|
||||||
<Routes>
|
<Routes>
|
||||||
@@ -12,6 +13,7 @@ const AppRouter = () => (
|
|||||||
<Route path="/documents" element={<DocumentsRoute />} />
|
<Route path="/documents" element={<DocumentsRoute />} />
|
||||||
<Route path="/documents/folder/:folderId" element={<DocumentsRoute />} />
|
<Route path="/documents/folder/:folderId" element={<DocumentsRoute />} />
|
||||||
<Route path="/documents/:documentId" element={<DocumentsRoute />} />
|
<Route path="/documents/:documentId" element={<DocumentsRoute />} />
|
||||||
|
<Route path="/settings" element={<SettingsRoute />} />
|
||||||
<Route path="*" element={<Navigate to="/documents" replace />} />
|
<Route path="*" element={<Navigate to="/documents" replace />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
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 { useAppShell } from '../appShellContext';
|
||||||
import DocumentsLayout from './DocumentsLayout';
|
import DocumentsLayout from './DocumentsLayout';
|
||||||
|
import { useWorkspaceSurface } from './useWorkspaceSurface';
|
||||||
|
|
||||||
const DocumentsRoute = () => {
|
const DocumentsRoute = () => {
|
||||||
const {
|
const {
|
||||||
@@ -63,66 +60,14 @@ const DocumentsRoute = () => {
|
|||||||
navigate(target);
|
navigate(target);
|
||||||
}, [navigate, parentBreadcrumb]);
|
}, [navigate, parentBreadcrumb]);
|
||||||
|
|
||||||
const isWorkspace = workspaceMode === 'skeuo';
|
const { surface } = useWorkspaceSurface({
|
||||||
const isPreviewWorkspace = Boolean(previewWorkspaceDocument);
|
sidebarCollapsed,
|
||||||
const showPreviewWorkspace = !isWorkspace && isPreviewWorkspace;
|
onExpandSidebar: expandSidebar,
|
||||||
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}, [
|
|
||||||
documentsTableProps,
|
documentsTableProps,
|
||||||
parentBreadcrumb,
|
|
||||||
handleNavigateParent,
|
|
||||||
renderSidebarToggle,
|
|
||||||
detailPanelProps,
|
detailPanelProps,
|
||||||
detailPanelOpen,
|
detailPanelOpen,
|
||||||
]);
|
workspaceMode,
|
||||||
|
skeuoWorkspaceProps,
|
||||||
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,
|
previewWorkspaceDocument,
|
||||||
previewWorkspaceEntry,
|
previewWorkspaceEntry,
|
||||||
ensureAssetUrl,
|
ensureAssetUrl,
|
||||||
@@ -132,24 +77,9 @@ const DocumentsRoute = () => {
|
|||||||
notifyApiError,
|
notifyApiError,
|
||||||
handleThumbnailRegeneration,
|
handleThumbnailRegeneration,
|
||||||
closeDocumentPreview,
|
closeDocumentPreview,
|
||||||
renderSidebarToggle,
|
parentBreadcrumb,
|
||||||
]);
|
onNavigateParent: handleNavigateParent,
|
||||||
|
|
||||||
const workspaceSurface = useMemo(() => {
|
|
||||||
if (!isWorkspace) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return createDesktopSurface({
|
|
||||||
workspaceProps: skeuoWorkspaceProps,
|
|
||||||
renderSidebarToggle,
|
|
||||||
});
|
});
|
||||||
}, [isWorkspace, skeuoWorkspaceProps, renderSidebarToggle]);
|
|
||||||
|
|
||||||
const surface = showPreviewWorkspace
|
|
||||||
? previewSurface
|
|
||||||
: isWorkspace
|
|
||||||
? workspaceSurface
|
|
||||||
: documentsSurface;
|
|
||||||
|
|
||||||
if (!surface) {
|
if (!surface) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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 };
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user