frontend tweaks, preview workspace
This commit is contained in:
+244
-118
@@ -26,6 +26,7 @@ import './styles.css';
|
||||
import AssetManager, { getAssetFromVersion, resolveDocumentAssetUrl, createAssetView } from './asset_manager';
|
||||
import useApiError from './hooks/useApiError';
|
||||
import SkeuomorphicWorkspace from './skeuomorphic_ws';
|
||||
import PreviewWorkspace from './preview/PreviewWorkspace';
|
||||
import DetailPanel from './detail/DetailPanel';
|
||||
import TagsPanel from './tags/TagsPanel';
|
||||
import CorrespondentsPanel from './correspondents/CorrespondentsPanel';
|
||||
@@ -41,10 +42,13 @@ import {
|
||||
RefreshIcon,
|
||||
ArrowUpIcon,
|
||||
MinusVerticalIcon,
|
||||
DownloadIcon,
|
||||
TextScanIcon,
|
||||
AnalyzeIcon,
|
||||
CloseIcon,
|
||||
} from './ui/icons';
|
||||
import DocumentsTable from './documents/DocumentsTable';
|
||||
import { AppShellContext, useAppShell } from './appShellContext';
|
||||
import DocumentViewerRoute from './routes/DocumentViewerRoute';
|
||||
|
||||
const runtimeApiBase =
|
||||
typeof window !== 'undefined' && window.__PAPERCRATE_API_BASE_URL
|
||||
@@ -3624,6 +3628,26 @@ const AppLayout = () => {
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [previewDocumentId, closeDocumentPreview]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!previewDocumentId) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
ensurePreviewData(previewDocumentId).catch((error) => {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
notifyApiError(error, 'Failed to open document preview.');
|
||||
closeDocumentPreview();
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [previewDocumentId, ensurePreviewData, notifyApiError, closeDocumentPreview]);
|
||||
|
||||
const handleDocumentTitleUpdate = useCallback(
|
||||
async (documentId, nextTitle) => {
|
||||
const trimmed = nextTitle.trim();
|
||||
@@ -5188,6 +5212,8 @@ const AppLayout = () => {
|
||||
exitSkeuoWorkspace,
|
||||
skeuoWorkspaceProps,
|
||||
ensurePreviewData,
|
||||
ensureAssetUrl,
|
||||
getDocumentAsset,
|
||||
notifyApiError,
|
||||
resolveApiPath,
|
||||
openTagsModal,
|
||||
@@ -5226,6 +5252,8 @@ const AppLayout = () => {
|
||||
exitSkeuoWorkspace,
|
||||
skeuoWorkspaceProps,
|
||||
ensurePreviewData,
|
||||
ensureAssetUrl,
|
||||
getDocumentAsset,
|
||||
notifyApiError,
|
||||
resolveApiPath,
|
||||
openTagsModal,
|
||||
@@ -5387,6 +5415,14 @@ const DocumentsRoute = () => {
|
||||
openTagsModal,
|
||||
openCorrespondentsModal,
|
||||
exitSkeuoWorkspace,
|
||||
previewWorkspaceDocument,
|
||||
previewWorkspaceEntry,
|
||||
closeDocumentPreview,
|
||||
handleThumbnailRegeneration,
|
||||
resolveApiPath,
|
||||
ensureAssetUrl,
|
||||
getDocumentAsset,
|
||||
notifyApiError,
|
||||
} = useAppShell();
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -5416,12 +5452,24 @@ const DocumentsRoute = () => {
|
||||
breadcrumbs,
|
||||
} = documentsTableProps;
|
||||
|
||||
const isWorkspace = workspaceMode === 'skeuo';
|
||||
const isPreviewWorkspace = Boolean(previewWorkspaceDocument);
|
||||
const showPreviewWorkspace = !isWorkspace && isPreviewWorkspace;
|
||||
const isGridView = viewMode === 'grid';
|
||||
const showingSearchResults = Array.isArray(searchResults);
|
||||
const headerTitle = showingSearchResults ? 'Search results' : currentFolderName;
|
||||
const headerSubtitle = showingSearchResults
|
||||
? `${searchResults.length} matching document${searchResults.length === 1 ? '' : 's'}`
|
||||
: null;
|
||||
let headerTitle;
|
||||
let headerSubtitle = null;
|
||||
if (showPreviewWorkspace) {
|
||||
const previewTitle = previewWorkspaceDocument?.title
|
||||
|| previewWorkspaceDocument?.original_name
|
||||
|| 'Document preview';
|
||||
headerTitle = previewTitle;
|
||||
} else if (showingSearchResults) {
|
||||
headerTitle = 'Search results';
|
||||
headerSubtitle = `${searchResults.length} matching document${searchResults.length === 1 ? '' : 's'}`;
|
||||
} else {
|
||||
headerTitle = currentFolderName;
|
||||
}
|
||||
const parentBreadcrumb = breadcrumbs && breadcrumbs.length > 1 ? breadcrumbs[breadcrumbs.length - 2] : null;
|
||||
const handleNavigateParent = parentBreadcrumb
|
||||
? () => {
|
||||
@@ -5432,77 +5480,85 @@ const DocumentsRoute = () => {
|
||||
}
|
||||
: null;
|
||||
|
||||
const detailHasContent = detailPanelProps.selectedDocuments?.length > 0;
|
||||
const detailHasContent = !isWorkspace && !showPreviewWorkspace
|
||||
&& detailPanelProps.selectedDocuments?.length > 0;
|
||||
const toggleSidebar = sidebarCollapsed ? expandSidebar : collapseSidebar;
|
||||
const ToggleIcon = sidebarCollapsed ? ChevronsRightIcon : ChevronsLeftIcon;
|
||||
const toggleLabel = sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar';
|
||||
const mainContentClass = (() => {
|
||||
if (isWorkspace) {
|
||||
return 'main-content main-content--workspace';
|
||||
}
|
||||
if (showPreviewWorkspace) {
|
||||
return 'main-content main-content--preview';
|
||||
}
|
||||
return `main-content main-content--documents${
|
||||
detailHasContent ? ' main-content--has-detail' : ''
|
||||
}`;
|
||||
})();
|
||||
|
||||
if (workspaceMode === 'skeuo') {
|
||||
return (
|
||||
<DocumentsLayout
|
||||
sidebarProps={sidebarPropsWithActions}
|
||||
sidebarCollapsed={sidebarCollapsed}
|
||||
>
|
||||
<div className="main-content">
|
||||
<div className="panel-header main-content__header">
|
||||
<div className="panel-actions main-content__actions">
|
||||
{sidebarCollapsed ? (
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button ghost"
|
||||
onClick={toggleSidebar}
|
||||
aria-label={toggleLabel}
|
||||
title={toggleLabel}
|
||||
>
|
||||
<ToggleIcon />
|
||||
</button>
|
||||
) : null}
|
||||
{parentBreadcrumb ? (
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button ghost"
|
||||
onClick={handleNavigateParent}
|
||||
aria-label="Go to parent folder"
|
||||
title="Go to parent folder"
|
||||
>
|
||||
<ArrowUpIcon />
|
||||
</button>
|
||||
) : null}
|
||||
<h2 className="main-content__title">
|
||||
{headerTitle}
|
||||
{headerSubtitle ? (
|
||||
<span className="main-content__subtitle">{headerSubtitle}</span>
|
||||
) : null}
|
||||
</h2>
|
||||
<div className="spacer" />
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button ghost"
|
||||
onClick={skeuoWorkspaceProps.onRefresh}
|
||||
aria-label="Refresh"
|
||||
title="Refresh"
|
||||
>
|
||||
<RefreshIcon />
|
||||
</button>
|
||||
<button type="button" className="secondary" onClick={exitSkeuoWorkspace}>
|
||||
Back to List
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="main-content__body">
|
||||
<SkeuomorphicWorkspace {...skeuoWorkspaceProps} />
|
||||
</div>
|
||||
</div>
|
||||
</DocumentsLayout>
|
||||
);
|
||||
}
|
||||
const bodyClass = (() => {
|
||||
if (isWorkspace) {
|
||||
return 'main-content__body main-content__body--workspace';
|
||||
}
|
||||
if (showPreviewWorkspace) {
|
||||
return 'main-content__body main-content__body--preview';
|
||||
}
|
||||
return `main-content__body main-content__body--documents${
|
||||
detailHasContent ? ' main-content__body--has-detail' : ''
|
||||
}`;
|
||||
})();
|
||||
|
||||
const previewDownloadHref = useMemo(() => {
|
||||
if (!previewWorkspaceDocument?.current_version?.download_path) {
|
||||
return null;
|
||||
}
|
||||
return resolveApiPath(previewWorkspaceDocument.current_version.download_path);
|
||||
}, [previewWorkspaceDocument, resolveApiPath]);
|
||||
|
||||
const previewHasOcr = useMemo(
|
||||
() => Boolean(
|
||||
previewWorkspaceDocument && getDocumentAsset(previewWorkspaceDocument, 'ocr-text'),
|
||||
),
|
||||
[previewWorkspaceDocument, getDocumentAsset],
|
||||
);
|
||||
|
||||
const handlePreviewOcr = useCallback(async () => {
|
||||
if (!previewWorkspaceDocument) {
|
||||
return;
|
||||
}
|
||||
const asset = getDocumentAsset(previewWorkspaceDocument, 'ocr-text');
|
||||
if (!asset) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const ensured = await ensureAssetUrl(previewWorkspaceDocument.id, asset, { force: false });
|
||||
const entry = ensured || asset;
|
||||
const url = entry?.url
|
||||
|| resolveDocumentAssetUrl(previewWorkspaceDocument, 'ocr-text', {
|
||||
ensureAssetUrl,
|
||||
getAsset: getDocumentAsset,
|
||||
});
|
||||
if (!url) {
|
||||
throw new Error('OCR text URL unavailable.');
|
||||
}
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
} catch (error) {
|
||||
notifyApiError(error, 'Unable to open OCR text.');
|
||||
}
|
||||
}, [
|
||||
previewWorkspaceDocument,
|
||||
ensureAssetUrl,
|
||||
getDocumentAsset,
|
||||
notifyApiError,
|
||||
]);
|
||||
|
||||
return (
|
||||
<DocumentsLayout
|
||||
sidebarProps={sidebarPropsWithActions}
|
||||
sidebarCollapsed={sidebarCollapsed}
|
||||
>
|
||||
<div className={`main-content main-content--documents${detailHasContent ? ' main-content--has-detail' : ''}`}>
|
||||
<div className={mainContentClass}>
|
||||
<div className="panel-header main-content__header">
|
||||
<div className="panel-actions main-content__actions">
|
||||
{sidebarCollapsed ? (
|
||||
@@ -5516,7 +5572,7 @@ const DocumentsRoute = () => {
|
||||
<ToggleIcon />
|
||||
</button>
|
||||
) : null}
|
||||
{parentBreadcrumb ? (
|
||||
{parentBreadcrumb && !showPreviewWorkspace ? (
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button ghost"
|
||||
@@ -5534,59 +5590,129 @@ const DocumentsRoute = () => {
|
||||
) : null}
|
||||
</h2>
|
||||
<div className="spacer" />
|
||||
<div className="view-toggle" role="group" aria-label="Change view">
|
||||
<button
|
||||
type="button"
|
||||
className={`view-toggle__button${isGridView ? '' : ' active'}`}
|
||||
onClick={() => onViewModeChange?.('list')}
|
||||
aria-pressed={!isGridView}
|
||||
title="List view"
|
||||
>
|
||||
<ViewListIcon className="view-toggle__icon" size={18} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`view-toggle__button${isGridView ? ' active' : ''}`}
|
||||
onClick={() => onViewModeChange?.('grid')}
|
||||
aria-pressed={isGridView}
|
||||
title="Icons view"
|
||||
>
|
||||
<ViewGridIcon className="view-toggle__icon" size={18} />
|
||||
</button>
|
||||
</div>
|
||||
<span className="main-content__actions-divider" aria-hidden="true">
|
||||
<MinusVerticalIcon />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button ghost"
|
||||
onClick={onRequestCreateFolder}
|
||||
disabled={creatingFolder}
|
||||
aria-label={creatingFolder ? 'Creating folder…' : 'Create folder'}
|
||||
title={creatingFolder ? 'Creating folder…' : 'Create folder'}
|
||||
>
|
||||
<FolderPlusIcon />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button ghost"
|
||||
onClick={onRefresh}
|
||||
aria-label="Refresh"
|
||||
title="Refresh"
|
||||
>
|
||||
<RefreshIcon />
|
||||
</button>
|
||||
<button className="secondary" type="button" onClick={onShowSkeuoWorkspace}>
|
||||
Desk View
|
||||
</button>
|
||||
{isWorkspace ? (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button ghost"
|
||||
onClick={skeuoWorkspaceProps.onRefresh}
|
||||
aria-label="Refresh"
|
||||
title="Refresh"
|
||||
>
|
||||
<RefreshIcon />
|
||||
</button>
|
||||
<button type="button" className="secondary" onClick={exitSkeuoWorkspace}>
|
||||
Back to List
|
||||
</button>
|
||||
</>
|
||||
) : showPreviewWorkspace ? (
|
||||
<>
|
||||
{previewDownloadHref ? (
|
||||
<a
|
||||
className="icon-button"
|
||||
href={previewDownloadHref}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="Download document"
|
||||
title="Download document"
|
||||
>
|
||||
<DownloadIcon />
|
||||
</a>
|
||||
) : null}
|
||||
{previewHasOcr ? (
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button ghost"
|
||||
onClick={handlePreviewOcr}
|
||||
aria-label="View OCR text"
|
||||
title="View OCR text"
|
||||
>
|
||||
<TextScanIcon />
|
||||
</button>
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button ghost"
|
||||
onClick={() => handleThumbnailRegeneration(previewWorkspaceDocument.id)}
|
||||
aria-label="Re-run analysis"
|
||||
title="Re-run analysis"
|
||||
>
|
||||
<AnalyzeIcon />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button ghost"
|
||||
onClick={() => closeDocumentPreview()}
|
||||
aria-label="Close preview"
|
||||
title="Close preview"
|
||||
>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="view-toggle" role="group" aria-label="Change view">
|
||||
<button
|
||||
type="button"
|
||||
className={`view-toggle__button${isGridView ? '' : ' active'}`}
|
||||
onClick={() => onViewModeChange?.('list')}
|
||||
aria-pressed={!isGridView}
|
||||
title="List view"
|
||||
>
|
||||
<ViewListIcon className="view-toggle__icon" size={18} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`view-toggle__button${isGridView ? ' active' : ''}`}
|
||||
onClick={() => onViewModeChange?.('grid')}
|
||||
aria-pressed={isGridView}
|
||||
title="Icons view"
|
||||
>
|
||||
<ViewGridIcon className="view-toggle__icon" size={18} />
|
||||
</button>
|
||||
</div>
|
||||
<span className="main-content__actions-divider" aria-hidden="true">
|
||||
<MinusVerticalIcon />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button ghost"
|
||||
onClick={onRequestCreateFolder}
|
||||
disabled={creatingFolder}
|
||||
aria-label={creatingFolder ? 'Creating folder…' : 'Create folder'}
|
||||
title={creatingFolder ? 'Creating folder…' : 'Create folder'}
|
||||
>
|
||||
<FolderPlusIcon />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button ghost"
|
||||
onClick={onRefresh}
|
||||
aria-label="Refresh"
|
||||
title="Refresh"
|
||||
>
|
||||
<RefreshIcon />
|
||||
</button>
|
||||
<button className="secondary" type="button" onClick={onShowSkeuoWorkspace}>
|
||||
Desk View
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`main-content__body main-content__body--documents${detailHasContent ? ' main-content__body--has-detail' : ''}`}
|
||||
>
|
||||
<DocumentsTable {...documentsTableProps} showHeader={false} />
|
||||
<div className={bodyClass}>
|
||||
{isWorkspace ? (
|
||||
<SkeuomorphicWorkspace {...skeuoWorkspaceProps} />
|
||||
) : showPreviewWorkspace ? (
|
||||
<PreviewWorkspace
|
||||
document={previewWorkspaceDocument}
|
||||
previewEntry={previewWorkspaceEntry}
|
||||
/>
|
||||
) : (
|
||||
<DocumentsTable {...documentsTableProps} showHeader={false} />
|
||||
)}
|
||||
</div>
|
||||
{detailHasContent ? (
|
||||
{!isWorkspace && !showPreviewWorkspace && detailHasContent ? (
|
||||
<DetailPanel {...detailPanelProps} />
|
||||
) : null}
|
||||
</div>
|
||||
@@ -5739,7 +5865,7 @@ const AppRouter = () => (
|
||||
<Route path="/" element={<Navigate to="/documents" replace />} />
|
||||
<Route path="/documents" element={<DocumentsRoute />} />
|
||||
<Route path="/documents/folder/:folderId" element={<DocumentsRoute />} />
|
||||
<Route path="/documents/:documentId" element={<DocumentViewerRoute />} />
|
||||
<Route path="/documents/:documentId" element={<DocumentsRoute />} />
|
||||
<Route path="*" element={<Navigate to="/documents" replace />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import React from 'react';
|
||||
import { DownloadIcon } from '../ui/icons';
|
||||
import { formatFileSize } from '../utils/format';
|
||||
|
||||
const PreviewWorkspace = ({
|
||||
document,
|
||||
previewEntry,
|
||||
resolveApiPath,
|
||||
onClose,
|
||||
onRegenerateThumbnails,
|
||||
}) => {
|
||||
if (!document) {
|
||||
return null;
|
||||
@@ -15,9 +11,6 @@ const PreviewWorkspace = ({
|
||||
|
||||
const title = document.title || document.original_name || 'Document';
|
||||
const mime = previewEntry?.contentType || document.content_type || 'application/pdf';
|
||||
const downloadHref = document.current_version?.download_path
|
||||
? resolveApiPath(document.current_version.download_path)
|
||||
: null;
|
||||
const sizeBytes = Number(document.current_version?.size_bytes) || 0;
|
||||
const sizeLabel = sizeBytes > 0 ? formatFileSize(sizeBytes) : null;
|
||||
const metadata =
|
||||
@@ -25,48 +18,6 @@ const PreviewWorkspace = ({
|
||||
|
||||
return (
|
||||
<section className="preview-workspace">
|
||||
<header className="preview-workspace__header">
|
||||
<div className="preview-workspace__meta">
|
||||
<button
|
||||
type="button"
|
||||
className="secondary"
|
||||
onClick={() => onClose(document.folder_id ?? 'root')}
|
||||
>
|
||||
← Back
|
||||
</button>
|
||||
<div>
|
||||
<h2>{title}</h2>
|
||||
<span className="meta">
|
||||
{document.content_type || mime}
|
||||
{sizeLabel ? ` · ${sizeLabel}` : ''}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="preview-workspace__actions">
|
||||
<a
|
||||
className="button-link with-icon"
|
||||
href={downloadHref || '#'}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-disabled={!downloadHref}
|
||||
onClick={(event) => {
|
||||
if (!downloadHref) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DownloadIcon className="icon-inline" />
|
||||
<span>Download</span>
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
className="secondary"
|
||||
onClick={() => onRegenerateThumbnails(document.id)}
|
||||
>
|
||||
Re-run analysis
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<div className="preview-workspace__body">
|
||||
{!previewEntry?.url ? (
|
||||
<div className="preview-workspace__message">Loading preview…</div>
|
||||
@@ -78,15 +29,16 @@ const PreviewWorkspace = ({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{metadata && (
|
||||
{metadata ? (
|
||||
<section className="preview-workspace__metadata">
|
||||
<h3>Metadata</h3>
|
||||
{sizeLabel ? <p><strong>Size:</strong> {sizeLabel}</p> : null}
|
||||
<p><strong>Type:</strong> {mime}</p>
|
||||
<pre>{JSON.stringify(metadata, null, 2)}</pre>
|
||||
</section>
|
||||
)}
|
||||
) : null}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default PreviewWorkspace;
|
||||
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Navigate, useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { useAppShell } from '../appShellContext';
|
||||
import PreviewWorkspace from '../preview/PreviewWorkspace';
|
||||
|
||||
const DocumentViewerRoute = () => {
|
||||
const {
|
||||
previewWorkspaceDocument,
|
||||
previewWorkspaceEntry,
|
||||
closeDocumentPreview,
|
||||
handleThumbnailRegeneration,
|
||||
ensurePreviewData,
|
||||
notifyApiError,
|
||||
resolveApiPath,
|
||||
} = useAppShell();
|
||||
const { documentId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
@@ -44,29 +39,15 @@ const DocumentViewerRoute = () => {
|
||||
};
|
||||
}, [documentId, ensurePreviewData, notifyApiError, navigate]);
|
||||
|
||||
const isReady =
|
||||
documentId && previewWorkspaceDocument && previewWorkspaceDocument.id === documentId;
|
||||
|
||||
if (!isReady) {
|
||||
return (
|
||||
<main className="preview-main">
|
||||
<div className="preview-workspace__message">Loading preview…</div>
|
||||
</main>
|
||||
);
|
||||
if (!documentId) {
|
||||
return <Navigate to="/documents" replace />;
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="preview-main">
|
||||
<PreviewWorkspace
|
||||
document={previewWorkspaceDocument}
|
||||
previewEntry={previewWorkspaceEntry}
|
||||
resolveApiPath={resolveApiPath}
|
||||
onClose={closeDocumentPreview}
|
||||
onRegenerateThumbnails={handleThumbnailRegeneration}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
if (!previewWorkspaceDocument || previewWorkspaceDocument.id !== documentId) {
|
||||
return <div className="preview-workspace__message">Loading preview…</div>;
|
||||
}
|
||||
|
||||
return <Navigate to="/documents" replace />;
|
||||
};
|
||||
|
||||
export default DocumentViewerRoute;
|
||||
|
||||
|
||||
@@ -291,12 +291,6 @@ const Sidebar = ({
|
||||
);
|
||||
|
||||
const rootNode = folderNodes.get('root');
|
||||
const hintText = appStatus === 'bootstrapping' && loading
|
||||
? 'Loading your library…'
|
||||
: previewActive
|
||||
? 'Viewing document preview. Press ← Back to return to the library.'
|
||||
: 'Drag files here to upload.';
|
||||
|
||||
return (
|
||||
<aside className="sidebar">
|
||||
<div className="panel-header sidebar__header">
|
||||
@@ -371,7 +365,6 @@ const Sidebar = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className="panel-body sidebar__body">
|
||||
<span className="sidebar__hint">{hintText}</span>
|
||||
{status && (
|
||||
<div className="sidebar__status">
|
||||
<div className={`status-banner ${status.variant}`}>{status.message}</div>
|
||||
|
||||
@@ -1188,11 +1188,6 @@ button.danger:hover:not([disabled]) {
|
||||
background: rgba(209, 67, 67, 0.12);
|
||||
}
|
||||
|
||||
.sidebar__hint {
|
||||
color: var(--muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.sidebar__search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
IconMinusVertical,
|
||||
IconLogout,
|
||||
IconChevronDown,
|
||||
IconX,
|
||||
} from '@tabler/icons-react';
|
||||
import FolderSvg from '../assets/folder.svg';
|
||||
|
||||
@@ -185,6 +186,15 @@ export const MinusVerticalIcon = ({ className, size = '1em', stroke = 1.6, ...re
|
||||
/>
|
||||
);
|
||||
|
||||
export const CloseIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||
<IconX
|
||||
className={composeClassName('icon', className)}
|
||||
size={size}
|
||||
stroke={stroke}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
export const AnalyzeIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||
<IconAnalyze
|
||||
className={composeClassName('icon', className)}
|
||||
|
||||
Reference in New Issue
Block a user