unify detailpanel and documentviewer
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
@@ -20,46 +19,7 @@ import PanelHeader from '../ui/PanelHeader';
|
||||
import BreadcrumbTrail from '../ui/BreadcrumbTrail';
|
||||
import { useAssetNavigator } from '../hooks/useAssetNavigator';
|
||||
import DocumentViewerLayout from './DocumentViewerLayout';
|
||||
|
||||
const PORTRAIT_WIDTH_TO_HEIGHT = 1 / Math.sqrt(2); // ≈0.707 (A-series aspect ratio)
|
||||
const DOCUMENT_VIEWER_PORTRAIT_HEIGHT_RATIO = 0.4;
|
||||
const PORTRAIT_RATIO_STYLE_ID = 'document-viewer-portrait-ratio-style';
|
||||
|
||||
const ensurePortraitRatioStyle = () => {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const cssValue = String(DOCUMENT_VIEWER_PORTRAIT_HEIGHT_RATIO);
|
||||
const cssText = `:root { --document-viewer-portrait-height-ratio: ${cssValue}; }`;
|
||||
|
||||
let styleEl = document.getElementById(PORTRAIT_RATIO_STYLE_ID);
|
||||
if (!styleEl) {
|
||||
styleEl = document.createElement('style');
|
||||
styleEl.id = PORTRAIT_RATIO_STYLE_ID;
|
||||
document.head.appendChild(styleEl);
|
||||
}
|
||||
|
||||
if (styleEl.textContent !== cssText) {
|
||||
styleEl.textContent = cssText;
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
ensurePortraitRatioStyle();
|
||||
}
|
||||
|
||||
const computeStackedLayoutBreakpoint = () => {
|
||||
const viewportHeight = typeof window !== 'undefined' ? window.innerHeight : 900;
|
||||
const portraitViewportWidth = viewportHeight
|
||||
* DOCUMENT_VIEWER_PORTRAIT_HEIGHT_RATIO
|
||||
* PORTRAIT_WIDTH_TO_HEIGHT;
|
||||
const detailsColumnWidth = 320; // px ~ 20rem for metadata & tabs
|
||||
const gutterAllowance = 48; // padding + grid gap
|
||||
|
||||
const desiredWidth = portraitViewportWidth + detailsColumnWidth + gutterAllowance;
|
||||
return desiredWidth;
|
||||
};
|
||||
import useViewerLayoutMode from './useViewerLayoutMode';
|
||||
|
||||
export const createDocumentViewerHeaderActions = ({
|
||||
document,
|
||||
@@ -226,8 +186,6 @@ const DocumentViewerPanel = ({
|
||||
[hasOcr, loadOcrContent],
|
||||
);
|
||||
|
||||
const viewerRef = useRef(null);
|
||||
const [isStackedLayout, setIsStackedLayout] = useState(false);
|
||||
const [zoomOverlayOpen, setZoomOverlayOpen] = useState(false);
|
||||
const previewNavigator = useAssetNavigator({
|
||||
document,
|
||||
@@ -263,60 +221,8 @@ const DocumentViewerPanel = ({
|
||||
}
|
||||
}, [hydrateDocument, document?.id]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
ensurePortraitRatioStyle();
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const node = viewerRef.current;
|
||||
if (!node) {
|
||||
setIsStackedLayout(false);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let frame = null;
|
||||
const commitMeasure = (width) => {
|
||||
if (frame) {
|
||||
cancelAnimationFrame(frame);
|
||||
}
|
||||
frame = requestAnimationFrame(() => {
|
||||
const breakpoint = computeStackedLayoutBreakpoint();
|
||||
setIsStackedLayout(width < breakpoint);
|
||||
});
|
||||
};
|
||||
|
||||
const measure = () => {
|
||||
commitMeasure(node.getBoundingClientRect().width);
|
||||
};
|
||||
|
||||
measure();
|
||||
|
||||
if (typeof ResizeObserver === 'undefined') {
|
||||
window.addEventListener('resize', measure);
|
||||
return () => {
|
||||
if (frame) {
|
||||
cancelAnimationFrame(frame);
|
||||
}
|
||||
window.removeEventListener('resize', measure);
|
||||
};
|
||||
}
|
||||
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
if (!entries.length) {
|
||||
return;
|
||||
}
|
||||
commitMeasure(entries[0].contentRect.width);
|
||||
});
|
||||
|
||||
observer.observe(node);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
if (frame) {
|
||||
cancelAnimationFrame(frame);
|
||||
}
|
||||
};
|
||||
}, [document?.id]);
|
||||
const panelRef = useRef(null);
|
||||
const isStackedLayout = useViewerLayoutMode(panelRef, document?.id);
|
||||
|
||||
const viewerClassName = isStackedLayout
|
||||
? 'document-viewer document-viewer--stacked'
|
||||
@@ -450,15 +356,13 @@ const DocumentViewerPanel = ({
|
||||
|
||||
if (!document) {
|
||||
return (
|
||||
<section className="document-viewer-panel">
|
||||
<section className="document-viewer-panel" ref={panelRef}>
|
||||
<PanelHeader title="Document preview" />
|
||||
<div className="document-viewer-panel__body">
|
||||
<section className="document-viewer document-viewer--loading" ref={viewerRef}>
|
||||
<section className="document-viewer document-viewer--loading">
|
||||
<div className="document-viewer__details-pane">
|
||||
<div className="document-viewer__details">
|
||||
<div className="document-viewer__message">
|
||||
Loading document…
|
||||
</div>
|
||||
<div className="document-viewer__message">Loading document…</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="document-viewer__viewport">
|
||||
@@ -482,7 +386,7 @@ const DocumentViewerPanel = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="document-viewer-panel">
|
||||
<section className="document-viewer-panel" ref={panelRef}>
|
||||
<PanelHeader
|
||||
leading={headerLeadingContent}
|
||||
title={headerTitle}
|
||||
@@ -490,7 +394,7 @@ const DocumentViewerPanel = ({
|
||||
actions={headerActions}
|
||||
/>
|
||||
<div className="document-viewer-panel__body">
|
||||
<section className={viewerClassName} ref={viewerRef}>
|
||||
<section className={viewerClassName}>
|
||||
<DocumentViewerLayout
|
||||
document={document}
|
||||
previewEntry={previewEntry}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
|
||||
const PORTRAIT_WIDTH_TO_HEIGHT = 1 / Math.sqrt(2); // ≈0.707 (A-series aspect ratio)
|
||||
export const DOCUMENT_VIEWER_PORTRAIT_HEIGHT_RATIO = 0.4;
|
||||
const PORTRAIT_RATIO_STYLE_ID = 'document-viewer-portrait-ratio-style';
|
||||
|
||||
const ensurePortraitRatioStyle = () => {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const cssValue = String(DOCUMENT_VIEWER_PORTRAIT_HEIGHT_RATIO);
|
||||
const cssText = `:root { --document-viewer-portrait-height-ratio: ${cssValue}; }`;
|
||||
|
||||
let styleEl = document.getElementById(PORTRAIT_RATIO_STYLE_ID);
|
||||
if (!styleEl) {
|
||||
styleEl = document.createElement('style');
|
||||
styleEl.id = PORTRAIT_RATIO_STYLE_ID;
|
||||
document.head.appendChild(styleEl);
|
||||
}
|
||||
|
||||
if (styleEl.textContent !== cssText) {
|
||||
styleEl.textContent = cssText;
|
||||
}
|
||||
};
|
||||
|
||||
const computeStackedLayoutBreakpoint = () => {
|
||||
const viewportHeight = typeof window !== 'undefined' ? window.innerHeight : 900;
|
||||
const portraitViewportWidth = viewportHeight
|
||||
* DOCUMENT_VIEWER_PORTRAIT_HEIGHT_RATIO
|
||||
* PORTRAIT_WIDTH_TO_HEIGHT;
|
||||
const detailsColumnWidth = 320; // px ~ 20rem for metadata & tabs
|
||||
const gutterAllowance = 48; // padding + grid gap
|
||||
|
||||
return portraitViewportWidth + detailsColumnWidth + gutterAllowance;
|
||||
};
|
||||
|
||||
export const useViewerLayoutMode = (ref, dependency) => {
|
||||
const [isStacked, setIsStacked] = useState(false);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
ensurePortraitRatioStyle();
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const node = ref?.current;
|
||||
if (!node) {
|
||||
setIsStacked(false);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let frame = null;
|
||||
const commitMeasure = (width) => {
|
||||
if (frame) {
|
||||
cancelAnimationFrame(frame);
|
||||
}
|
||||
frame = requestAnimationFrame(() => {
|
||||
const breakpoint = computeStackedLayoutBreakpoint();
|
||||
setIsStacked(width < breakpoint);
|
||||
});
|
||||
};
|
||||
|
||||
const measure = () => {
|
||||
commitMeasure(node.getBoundingClientRect().width);
|
||||
};
|
||||
|
||||
measure();
|
||||
|
||||
if (typeof ResizeObserver === 'undefined') {
|
||||
window.addEventListener('resize', measure);
|
||||
return () => {
|
||||
if (frame) {
|
||||
cancelAnimationFrame(frame);
|
||||
}
|
||||
window.removeEventListener('resize', measure);
|
||||
};
|
||||
}
|
||||
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
if (!entries.length) {
|
||||
return;
|
||||
}
|
||||
commitMeasure(entries[0].contentRect.width);
|
||||
});
|
||||
|
||||
observer.observe(node);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
if (frame) {
|
||||
cancelAnimationFrame(frame);
|
||||
}
|
||||
};
|
||||
}, [ref, dependency]);
|
||||
|
||||
return isStacked;
|
||||
};
|
||||
|
||||
export default useViewerLayoutMode;
|
||||
Reference in New Issue
Block a user