This commit is contained in:
2025-11-02 01:18:24 +01:00
parent 32d627fd6d
commit 7d8595b63f
6 changed files with 65 additions and 118 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
import React, { useCallback, useMemo } from 'react';
import { ChevronsRightIcon } from '../ui/icons';
import { createDocumentsSurface } from '../documents/DocumentsPanel';
import { createPreviewSurface } from '../preview/PreviewWorkspace';
import { createDocumentViewerSurface } from '../preview/DocumentViewerPanel';
import { createDesktopSurface } from '../DesktopWorkspace';
export const useWorkspaceSurface = ({
@@ -83,7 +83,7 @@ export const useWorkspaceSurface = ({
resolveFolderPath,
onFolderNavigate,
} = detailExtras;
return createPreviewSurface({
return createDocumentViewerSurface({
documentId: previewDocumentId,
document: previewWorkspaceDocument,
previewEntry: previewWorkspaceEntry,
-2
View File
@@ -753,8 +753,6 @@ const DetailPanel = ({
onCorrespondentRemove={onCorrespondentRemove}
onUpdateTitle={onUpdateTitle}
onUpdateIssued={onUpdateIssued}
resolveFolderPath={resolveFolderPath}
onFolderNavigate={onFolderNavigate}
/>
</>
);
@@ -244,10 +244,7 @@ const DocumentSummarySection = ({
onCorrespondentAdd,
onCorrespondentRemove,
onUpdateTitle,
onUpdateIssued,
resolveFolderPath,
onFolderNavigate,
showFolderPath = true,
onUpdateIssued
}) => {
const summary = useMemo(() => {
if (!document) {
@@ -283,74 +280,6 @@ const DocumentSummarySection = ({
return sortCorrespondents(document?.correspondents || []);
}, [correspondents, document?.correspondents]);
const folderPath = useMemo(() => {
if (
!showFolderPath
|| !document?.folder_id
|| typeof resolveFolderPath !== 'function'
) {
return null;
}
const segments = resolveFolderPath(document.folder_id);
const filtered = Array.isArray(segments)
? segments.filter((segment) => segment?.id && segment.id !== 'root')
: null;
if (!filtered || !filtered.length) {
return null;
}
return filtered;
}, [document?.folder_id, resolveFolderPath, showFolderPath]);
const folderDisplayNode = useMemo(() => {
if (!folderPath || !folderPath.length) {
return null;
}
return (
<span className="detail-folder-path">
{folderPath.map((segment, index) => {
const label = segment?.name || '…';
const targetId = segment?.id || null;
const key = `${targetId || label}-${index}`;
const isClickable = Boolean(targetId) && typeof onFolderNavigate === 'function';
const href = !isClickable
? null
: targetId === 'root'
? '/documents'
: `/documents/folder/${targetId}`;
return (
<React.Fragment key={key}>
{index > 0 ? <span className="detail-folder-path__separator">/</span> : null}
{isClickable ? (
<a
href={href}
className="detail-folder-path__link"
onClick={(event) => {
if (
event.button !== 0 ||
event.metaKey ||
event.ctrlKey ||
event.shiftKey ||
event.altKey
) {
return;
}
event.preventDefault();
event.stopPropagation();
onFolderNavigate(targetId);
}}
>
{label}
</a>
) : (
<span className="detail-folder-path__segment">{label}</span>
)}
</React.Fragment>
);
})}
</span>
);
}, [folderPath, onFolderNavigate]);
const metaRows = useMemo(() => {
const rows = [];
const currentVersionNumber = document?.current_version?.version_number;
@@ -526,11 +455,6 @@ const DocumentSummarySection = ({
</>
)}
</div>
{showFolderPath && folderDisplayNode ? (
<div className="doc-title-row__path" aria-label="Folder path">
{folderDisplayNode}
</div>
) : null}
</div>
{titleError ? <div className="status-inline error">{titleError}</div> : null}
@@ -14,7 +14,7 @@ const formatDateTime = (value) => {
return Number.isNaN(date.getTime()) ? '—' : date.toLocaleString();
};
const PreviewWorkspace = ({
const DocumentViewerPanel = ({
document,
documentId,
previewEntry,
@@ -27,8 +27,6 @@ const PreviewWorkspace = ({
onCorrespondentRemove,
onUpdateTitle,
onUpdateIssued,
resolveFolderPath,
onFolderNavigate,
}) => {
const sortedCorrespondents = useMemo(
() => sortCorrespondents(document?.correspondents || []),
@@ -66,6 +64,34 @@ const PreviewWorkspace = ({
];
}, [document]);
const contentType = (document?.content_type || '').toLowerCase();
const previewContent = useMemo(() => {
if (!previewEntry?.url) {
return null;
}
const title = document?.title || document?.original_name || 'Document';
if (contentType.startsWith('image/')) {
return (
<img
src={previewEntry.url}
alt={`Preview of ${title}`}
className="document-viewer__object document-viewer__object--image"
draggable={false}
/>
);
}
return (
<iframe
src={previewEntry.url}
title={`Preview of ${title}`}
className="document-viewer__object"
/>
);
}, [previewEntry?.url, contentType, document?.title, document?.original_name]);
const metadataPayload = useMemo(() => {
if (!document || !document.metadata || Object.keys(document.metadata).length === 0) {
return null;
@@ -75,22 +101,22 @@ const PreviewWorkspace = ({
if (!document) {
return (
<section className="preview-workspace preview-workspace--loading">
<div className="preview-workspace__details">
<div className="preview-workspace__message">
<section className="document-viewer document-viewer--loading">
<div className="document-viewer__details">
<div className="document-viewer__message">
Loading document{documentId ? ` ${documentId}` : ''}
</div>
</div>
<div className="preview-workspace__viewer">
<div className="preview-workspace__message">Preparing preview</div>
<div className="document-viewer__viewport">
<div className="document-viewer__message">Preparing preview</div>
</div>
</section>
);
}
return (
<section className="preview-workspace">
<div className="preview-workspace__details">
<section className="document-viewer">
<div className="document-viewer__details">
<DocumentSummarySection
document={document}
tagLookupById={tagLookupById}
@@ -103,8 +129,6 @@ const PreviewWorkspace = ({
onCorrespondentRemove={onCorrespondentRemove}
onUpdateTitle={onUpdateTitle}
onUpdateIssued={onUpdateIssued}
resolveFolderPath={resolveFolderPath}
onFolderNavigate={onFolderNavigate}
/>
<section className="preview-section">
<h3 className="preview-section__title">Metadata</h3>
@@ -124,24 +148,20 @@ const PreviewWorkspace = ({
) : null}
</section>
</div>
<div className="preview-workspace__viewer">
<div className="document-viewer__viewport">
{!previewEntry?.url ? (
<div className="preview-workspace__message">Loading preview</div>
<div className="document-viewer__message">Loading preview</div>
) : (
<iframe
src={previewEntry.url}
title={`Preview of ${document.title}`}
className="preview-workspace__object"
/>
previewContent
)}
</div>
</section>
);
};
export default PreviewWorkspace;
export default DocumentViewerPanel;
export const createPreviewWorkspaceHeaderActions = ({
export const createDocumentViewerHeaderActions = ({
document,
ensureAssetUrl,
ensurePreviewData,
@@ -204,7 +224,7 @@ export const createPreviewWorkspaceHeaderActions = ({
);
};
export const createPreviewSurface = ({
export const createDocumentViewerSurface = ({
documentId,
document,
previewEntry,
@@ -226,7 +246,6 @@ export const createPreviewSurface = ({
onUpdateTitle,
onUpdateIssued,
resolveFolderPath,
onFolderNavigate,
}) => {
if (!documentId && !document) {
return null;
@@ -274,7 +293,7 @@ export const createPreviewSurface = ({
title,
subtitle: null,
leading,
actions: createPreviewWorkspaceHeaderActions({
actions: createDocumentViewerHeaderActions({
document,
ensureAssetUrl,
ensurePreviewData,
@@ -291,7 +310,7 @@ export const createPreviewSurface = ({
variant: 'preview',
header,
content: (
<PreviewWorkspace
<DocumentViewerPanel
document={document}
documentId={documentId}
previewEntry={previewEntry}
@@ -304,9 +323,6 @@ export const createPreviewSurface = ({
onCorrespondentRemove={onCorrespondentRemove}
onUpdateTitle={onUpdateTitle}
onUpdateIssued={onUpdateIssued}
resolveFolderPath={resolveFolderPath}
onFolderNavigate={onFolderNavigate}
showFolderPath={false}
/>
),
supportsDetail: false,
+1 -1
View File
@@ -44,7 +44,7 @@ const DocumentViewerRoute = () => {
}
if (!previewWorkspaceDocument || previewWorkspaceDocument.id !== documentId) {
return <div className="preview-workspace__message">Loading preview</div>;
return <div className="document-viewer__message">Loading preview</div>;
}
return <Navigate to="/documents" replace />;
+17 -8
View File
@@ -752,13 +752,13 @@ button.danger:hover:not([disabled]) {
min-height: 0;
}
.preview-workspace {
.document-viewer {
flex: 1;
display: grid;
grid-template-columns: minmax(0, 30em) minmax(0, 1fr);
gap: 1.5rem;
min-height: 0;
padding: 1rem 1.5rem;
padding: 1rem 1rem;
}
.document-drag-preview {
@@ -859,7 +859,7 @@ button.danger:hover:not([disabled]) {
pointer-events: none;
}
.preview-workspace__details {
.document-viewer__details {
display: flex;
flex-direction: column;
gap: 0.5rem;
@@ -937,23 +937,32 @@ button.danger:hover:not([disabled]) {
font-size: 0.8rem;
}
.preview-workspace__viewer {
.document-viewer__viewport {
flex: 1;
min-width: 0;
background: var(--surface-subtle);
box-shadow: inset 0 0 0 1px var(--outline-subtle);
display: flex;
position: relative;
overflow: hidden;
align-items: flex-start;
}
.preview-workspace__object {
.document-viewer__object {
width: 100%;
height: 100%;
border: none;
background: #fff;
}
.document-viewer__object--image {
width: auto;
height: auto;
max-width: 100%;
max-height: 100%;
object-fit: contain;
background: var(--surface);
align-self: flex-start;
}
.tags-panel__body {
overflow-y: auto;
}
@@ -1128,7 +1137,7 @@ button.danger:hover:not([disabled]) {
min-height: 32px;
}
.preview-workspace__message {
.document-viewer__message {
position: absolute;
top: 50%;
left: 50%;