feat: Remove documentLink prop and onNavigatorSnapshot logic, and add openDetailPanel and closeDetailPanel to document shell.

This commit is contained in:
2025-12-04 01:32:28 +01:00
parent 8bb9f9950a
commit 3f7c294948
9 changed files with 129 additions and 183 deletions
+56 -33
View File
@@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useMemo } from 'react'; import React, { useCallback, useEffect, useMemo } from 'react';
import type { ReactNode } from 'react'; import type { ReactNode, ComponentProps } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { import {
DocumentsFilterProvider, DocumentsFilterProvider,
@@ -13,25 +13,24 @@ import { PanelManagerProvider, usePanelManager } from './PanelManagerContext';
import Sidebar from '../sidebar/Sidebar'; import Sidebar from '../sidebar/Sidebar';
import useDocumentsShell from './useDocumentsShell'; import useDocumentsShell from './useDocumentsShell';
const DocumentsRouteContent: React.FC = () => { const DocumentsInner: React.FC<{
const { sidebarProps: ComponentProps<typeof Sidebar> | null;
sidebarProps, surfaceConfig: any;
surfaceConfig, onNavigate: (documentId: string) => void;
documentsFilter, }> = ({ sidebarProps, surfaceConfig, onNavigate }) => {
} = useDocumentsShell(); const { openPreview } = usePreviewContext();
const navigate = useNavigate();
const { collapsed: sidebarCollapsed } = useSidebarContext(); const { collapsed: sidebarCollapsed } = useSidebarContext();
const { const {
sidebarSuppressed, sidebarSuppressed,
expandSidebar, expandSidebar,
openDetailPanel,
} = usePanelManager(); } = usePanelManager();
const { openPreview } = usePreviewContext(); const { openDetailPanel } = surfaceConfig;
const sidebarHidden = sidebarCollapsed || sidebarSuppressed; const sidebarHidden = sidebarCollapsed || sidebarSuppressed;
const handleHeaderBreadcrumbClick = useCallback((crumb: DocumentsHeaderBreadcrumb) => { const navigate = useNavigate();
const handleBreadcrumbNavigate = useCallback((crumb: DocumentsHeaderBreadcrumb) => {
if (!crumb || !crumb.id) { if (!crumb || !crumb.id) {
return; return;
} }
@@ -39,10 +38,6 @@ const DocumentsRouteContent: React.FC = () => {
navigate(target); navigate(target);
}, [navigate]); }, [navigate]);
const handleDocumentNavigate = useCallback((documentId: string) => {
navigate(`/documents/${documentId}`);
}, [navigate]);
const handleOpenSidepanel = useCallback((docId: string) => { const handleOpenSidepanel = useCallback((docId: string) => {
if (openDetailPanel) { if (openDetailPanel) {
openDetailPanel({ documentIds: [docId] }); openDetailPanel({ documentIds: [docId] });
@@ -51,9 +46,9 @@ const DocumentsRouteContent: React.FC = () => {
const documentsTablePropsWithNav = useMemo(() => ( const documentsTablePropsWithNav = useMemo(() => (
surfaceConfig.documentsTableProps surfaceConfig.documentsTableProps
? { ...surfaceConfig.documentsTableProps, onBreadcrumbNavigate: handleHeaderBreadcrumbClick } ? { ...surfaceConfig.documentsTableProps, onBreadcrumbNavigate: handleBreadcrumbNavigate }
: null : null
), [surfaceConfig.documentsTableProps, handleHeaderBreadcrumbClick]); ), [surfaceConfig.documentsTableProps, handleBreadcrumbNavigate]);
const { surface } = useWorkspaceSurface({ const { surface } = useWorkspaceSurface({
sidebarHidden, sidebarHidden,
@@ -87,30 +82,58 @@ const DocumentsRouteContent: React.FC = () => {
); );
}; };
const content = renderSurface(); return (
<DocumentOpenProvider
onOpenViewer={onNavigate}
onOpenPreview={openPreview}
onOpenSidepanel={handleOpenSidepanel}
>
{renderSurface()}
</DocumentOpenProvider>
);
};
const DocumentsRouteContent: React.FC = () => {
const {
sidebarProps,
surfaceConfig,
documentsFilter,
} = useDocumentsShell();
const navigate = useNavigate();
const handleDocumentNavigate = useCallback((documentId: string) => {
navigate(`/documents/${documentId}`);
}, [navigate]);
return ( return (
<DocumentsFilterProvider value={documentsFilter}> <DocumentsFilterProvider value={documentsFilter}>
<PreviewProvider <PreviewProvider
onNavigate={handleDocumentNavigate} onNavigate={handleDocumentNavigate}
> >
<DocumentOpenProvider <DocumentsInner
onOpenViewer={handleDocumentNavigate} sidebarProps={sidebarProps}
onOpenPreview={openPreview} surfaceConfig={surfaceConfig}
onOpenSidepanel={handleOpenSidepanel} onNavigate={handleDocumentNavigate}
> />
{content}
</DocumentOpenProvider>
</PreviewProvider> </PreviewProvider>
</DocumentsFilterProvider> </DocumentsFilterProvider>
); );
}; };
const DocumentsRoute: React.FC = () => ( const DocumentsRoute: React.FC = () => {
<SidebarProvider> const { surfaceConfig } = useDocumentsShell();
<PanelManagerProvider> const { detailPanelOpen, closeDetailPanel } = surfaceConfig;
<DocumentsRouteContent />
</PanelManagerProvider> return (
</SidebarProvider> <SidebarProvider>
); <PanelManagerProvider
isOpen={detailPanelOpen}
onClose={closeDetailPanel}
>
<DocumentsRouteContent />
</PanelManagerProvider>
</SidebarProvider>
);
};
export default DocumentsRoute; export default DocumentsRoute;
+26 -9
View File
@@ -89,9 +89,11 @@ const applyPanelWidthToRoot = (panel: PanelKey, width: number, active: boolean):
interface PanelManagerProviderProps { interface PanelManagerProviderProps {
children: ReactNode; children: ReactNode;
isOpen?: boolean;
onClose?: () => void;
} }
export const PanelManagerProvider: React.FC<PanelManagerProviderProps> = ({ children }) => { export const PanelManagerProvider: React.FC<PanelManagerProviderProps> = ({ children, isOpen, onClose }) => {
const { collapsed, setCollapsed } = useSidebarContext(); const { collapsed, setCollapsed } = useSidebarContext();
const initialSidebarWidth = readStoredWidth('sidebar', DEFAULT_SIDEBAR_WIDTH); const initialSidebarWidth = readStoredWidth('sidebar', DEFAULT_SIDEBAR_WIDTH);
const initialDetailWidth = readStoredWidth('detail', DEFAULT_DETAIL_WIDTH); const initialDetailWidth = readStoredWidth('detail', DEFAULT_DETAIL_WIDTH);
@@ -100,7 +102,13 @@ export const PanelManagerProvider: React.FC<PanelManagerProviderProps> = ({ chil
const [detailWidth, setDetailWidthState] = useState(() => clampPanelWidth('detail', initialDetailWidth)); const [detailWidth, setDetailWidthState] = useState(() => clampPanelWidth('detail', initialDetailWidth));
const [resizingPanel, setResizingPanel] = useState(null); const [resizingPanel, setResizingPanel] = useState(null);
const [sidebarSuppressed, setSidebarSuppressed] = useState(false); const [sidebarSuppressed, setSidebarSuppressed] = useState(false);
const [detailPanelOpen, setDetailPanelOpen] = useState(false); const [detailPanelOpen, setDetailPanelOpen] = useState(Boolean(isOpen));
useEffect(() => {
if (isOpen !== undefined) {
setDetailPanelOpen(isOpen);
}
}, [isOpen]);
const detailCloseHandlerRef = useRef(null); const detailCloseHandlerRef = useRef(null);
const panelWidthsRef = useRef({ sidebar: sidebarWidth, detail: detailWidth }); const panelWidthsRef = useRef({ sidebar: sidebarWidth, detail: detailWidth });
@@ -109,7 +117,11 @@ export const PanelManagerProvider: React.FC<PanelManagerProviderProps> = ({ chil
const closeDetailPanel = useCallback(() => { const closeDetailPanel = useCallback(() => {
const handler = detailCloseHandlerRef.current; const handler = detailCloseHandlerRef.current;
handler?.(); handler?.();
}, []); onClose?.();
if (isOpen === undefined) {
setDetailPanelOpen(false);
}
}, [onClose, isOpen]);
useEffect(() => { useEffect(() => {
panelWidthsRef.current.sidebar = sidebarWidth; panelWidthsRef.current.sidebar = sidebarWidth;
@@ -228,11 +240,16 @@ export const PanelManagerProvider: React.FC<PanelManagerProviderProps> = ({ chil
}, []); }, []);
const setDetailActive = useCallback( const setDetailActive = useCallback(
(isOpen) => { (active) => {
setDetailPanelOpen(Boolean(isOpen)); if (isOpen === undefined) {
handlePanelLayoutChange('detail', isOpen ? 'opened' : 'closed'); setDetailPanelOpen(Boolean(active));
}
if (!active) {
onClose?.();
}
handlePanelLayoutChange('detail', active ? 'opened' : 'closed');
}, },
[handlePanelLayoutChange], [handlePanelLayoutChange, isOpen, onClose],
); );
const expandSidebar = useCallback(() => { const expandSidebar = useCallback(() => {
@@ -394,8 +411,8 @@ export const usePanelResizeBindings = (
const handleProps = enabled const handleProps = enabled
? { ? {
onPointerDown: handlePointerDown, onPointerDown: handlePointerDown,
} }
: {}; : {};
return { return {
+6 -2
View File
@@ -6,7 +6,10 @@ import type Sidebar from '../sidebar/Sidebar';
import type { UseWorkspaceSurfaceArgs } from './useWorkspaceSurface'; import type { UseWorkspaceSurfaceArgs } from './useWorkspaceSurface';
import type { Identifier } from '../types/identifiers'; import type { Identifier } from '../types/identifiers';
type WorkspaceSurfaceConfig = Omit<UseWorkspaceSurfaceArgs, 'sidebarHidden' | 'onExpandSidebar'>; type WorkspaceSurfaceConfig = Omit<UseWorkspaceSurfaceArgs, 'sidebarHidden' | 'onExpandSidebar'> & {
openDetailPanel?: (args: { documentIds?: Identifier[] }) => void;
closeDetailPanel?: () => void;
};
interface DocumentsShellView { interface DocumentsShellView {
sidebarProps: ComponentProps<typeof Sidebar> | null; sidebarProps: ComponentProps<typeof Sidebar> | null;
@@ -23,8 +26,9 @@ const useDocumentsShell = (): DocumentsShellView => {
documentsTableProps: (shell.documentsTableProps ?? null) as WorkspaceSurfaceConfig['documentsTableProps'], documentsTableProps: (shell.documentsTableProps ?? null) as WorkspaceSurfaceConfig['documentsTableProps'],
detailPanelProps: (shell.detailPanelProps ?? null) as WorkspaceSurfaceConfig['detailPanelProps'], detailPanelProps: (shell.detailPanelProps ?? null) as WorkspaceSurfaceConfig['detailPanelProps'],
detailPanelOpen: Boolean(shell.detailPanelOpen), detailPanelOpen: Boolean(shell.detailPanelOpen),
openDetailPanel: shell.openDetailPanel as WorkspaceSurfaceConfig['openDetailPanel'],
closeDetailPanel: shell.closeDetailPanel as WorkspaceSurfaceConfig['closeDetailPanel'],
previewWorkspaceDocument: shell.previewWorkspaceDocument, previewWorkspaceDocument: shell.previewWorkspaceDocument,
documentLink: shell.documentLink,
previewDocumentId: (shell.previewDocumentId as Identifier | null | undefined) ?? null, previewDocumentId: (shell.previewDocumentId as Identifier | null | undefined) ?? null,
closeDocumentPreview: shell.closeDocumentPreview as WorkspaceSurfaceConfig['closeDocumentPreview'], closeDocumentPreview: shell.closeDocumentPreview as WorkspaceSurfaceConfig['closeDocumentPreview'],
ensurePreviewData: shell.ensurePreviewData as WorkspaceSurfaceConfig['ensurePreviewData'], ensurePreviewData: shell.ensurePreviewData as WorkspaceSurfaceConfig['ensurePreviewData'],
-4
View File
@@ -36,7 +36,6 @@ export interface UseWorkspaceSurfaceArgs {
detailPanelProps?: DetailPanelProps; detailPanelProps?: DetailPanelProps;
detailPanelOpen?: boolean; detailPanelOpen?: boolean;
previewWorkspaceDocument?: unknown; previewWorkspaceDocument?: unknown;
documentLink?: unknown;
previewDocumentId?: Identifier | null; previewDocumentId?: Identifier | null;
ensureAssetUrl?: EnsureAssetUrl; ensureAssetUrl?: EnsureAssetUrl;
ensurePreviewData?: EnsurePreviewData; ensurePreviewData?: EnsurePreviewData;
@@ -56,7 +55,6 @@ export const useWorkspaceSurface = ({
detailPanelProps, detailPanelProps,
detailPanelOpen = false, detailPanelOpen = false,
previewWorkspaceDocument, previewWorkspaceDocument,
documentLink,
previewDocumentId, previewDocumentId,
ensureAssetUrl, ensureAssetUrl,
ensurePreviewData, ensurePreviewData,
@@ -181,7 +179,6 @@ export const useWorkspaceSurface = ({
const viewer = ( const viewer = (
<DocumentViewerPanel <DocumentViewerPanel
document={previewWorkspaceDocument || null} document={previewWorkspaceDocument || null}
documentLink={documentLink}
hydrateDocument={ensurePreviewData} hydrateDocument={ensurePreviewData}
tagLookupById={tagLookupById} tagLookupById={tagLookupById}
tagOptions={tagOptions} tagOptions={tagOptions}
@@ -214,7 +211,6 @@ export const useWorkspaceSurface = ({
}, [ }, [
showPreviewWorkspace, showPreviewWorkspace,
previewWorkspaceDocument, previewWorkspaceDocument,
documentLink,
ensurePreviewData, ensurePreviewData,
ensureAssetUrl, ensureAssetUrl,
getDocumentAsset, getDocumentAsset,
+20 -6
View File
@@ -5,6 +5,7 @@ import { getTagColorStyle } from '../utils/colors';
import type { DocumentId } from '../types/identifiers'; import type { DocumentId } from '../types/identifiers';
import type { Document } from '../types/documents'; import type { Document } from '../types/documents';
import { LayoutCard } from './LayoutSystem'; import { LayoutCard } from './LayoutSystem';
import { useCardPointer } from './useCardPointer';
const preventAll = (event?: React.SyntheticEvent | Event | null) => { const preventAll = (event?: React.SyntheticEvent | Event | null) => {
if (!event) return; if (!event) return;
@@ -21,16 +22,18 @@ interface DesktopDocumentCardProps {
docTagTokens?: string; docTagTokens?: string;
ensureAssetUrl?: (...args: any[]) => Promise<unknown>; ensureAssetUrl?: (...args: any[]) => Promise<unknown>;
getDocumentAsset?: (...args: any[]) => unknown; getDocumentAsset?: (...args: any[]) => unknown;
handleNavigatorSnapshot?: (...args: any[]) => void;
cardPointerHandlers?: React.HTMLAttributes<HTMLDivElement>;
onDocumentActivate?: (id: string, event?: any) => void; onDocumentActivate?: (id: string, event?: any) => void;
onSelect: (ids: string[], extend?: boolean) => void;
onDeselect: (ids: string[]) => void;
selection: string[];
requestCanvasFocus?: () => void;
onTagDragEnter?: (event: React.DragEvent<HTMLDivElement>, docId: DocumentId) => void; onTagDragEnter?: (event: React.DragEvent<HTMLDivElement>, docId: DocumentId) => void;
onTagDragOver?: (event: React.DragEvent<HTMLDivElement>, docId: DocumentId) => void; onTagDragOver?: (event: React.DragEvent<HTMLDivElement>, docId: DocumentId) => void;
onTagDragLeave?: (event: React.DragEvent<HTMLDivElement>, docId: DocumentId) => void; onTagDragLeave?: (event: React.DragEvent<HTMLDivElement>, docId: DocumentId) => void;
onTagDrop?: (event: React.DragEvent<HTMLDivElement>, doc: Document) => void; onTagDrop?: (event: React.DragEvent<HTMLDivElement>, doc: Document) => void;
onDocTagDragStart?: (event: React.DragEvent<HTMLElement>, doc: Document, tag: any) => void; onDocTagDragStart?: (event: React.DragEvent<HTMLElement>, doc: Document, tag: any) => void;
onDocTagDragEnd?: (event: React.DragEvent<HTMLElement>) => void; onDocTagDragEnd?: (event: React.DragEvent<HTMLElement>) => void;
layoutCard?: LayoutCard; layoutCard: LayoutCard;
} }
const DesktopDocumentCard: React.FC<DesktopDocumentCardProps> = ({ const DesktopDocumentCard: React.FC<DesktopDocumentCardProps> = ({
@@ -42,9 +45,11 @@ const DesktopDocumentCard: React.FC<DesktopDocumentCardProps> = ({
docTagTokens, docTagTokens,
ensureAssetUrl, ensureAssetUrl,
getDocumentAsset, getDocumentAsset,
handleNavigatorSnapshot,
cardPointerHandlers,
onDocumentActivate, onDocumentActivate,
onSelect,
onDeselect,
selection,
requestCanvasFocus,
onTagDragEnter, onTagDragEnter,
onTagDragOver, onTagDragOver,
onTagDragLeave, onTagDragLeave,
@@ -53,6 +58,16 @@ const DesktopDocumentCard: React.FC<DesktopDocumentCardProps> = ({
onDocTagDragEnd, onDocTagDragEnd,
layoutCard, layoutCard,
}) => { }) => {
const cardPointerHandlers = useCardPointer(
layoutCard,
!!selected,
selection,
onSelect,
onDeselect,
onDocumentActivate,
requestCanvasFocus
);
const correspondents = useMemo(() => resolveCorrespondents(doc), [doc]); const correspondents = useMemo(() => resolveCorrespondents(doc), [doc]);
const tags = Array.isArray(doc?.tags) ? doc.tags : []; const tags = Array.isArray(doc?.tags) ? doc.tags : [];
@@ -108,7 +123,6 @@ const DesktopDocumentCard: React.FC<DesktopDocumentCardProps> = ({
title={doc.title} title={doc.title}
ensureAssetUrl={ensureAssetUrl} ensureAssetUrl={ensureAssetUrl}
getDocumentAsset={getDocumentAsset} getDocumentAsset={getDocumentAsset}
onNavigatorSnapshot={handleNavigatorSnapshot}
shouldLoad={shouldLoad} shouldLoad={shouldLoad}
/> />
{correspondents.length > 0 && ( {correspondents.length > 0 && (
+1 -45
View File
@@ -1,4 +1,4 @@
import { useEffect, useMemo } from 'react'; import { useMemo } from 'react';
import type { JSX } from 'react'; import type { JSX } from 'react';
import { resolveDocumentAssetUrl } from '../asset_manager'; import { resolveDocumentAssetUrl } from '../asset_manager';
import type { Identifier } from '../types/identifiers'; import type { Identifier } from '../types/identifiers';
@@ -14,19 +14,11 @@ type EnsureAssetUrl = (
type GetDocumentAsset = (document: Document | null, assetType: string) => Asset | null; type GetDocumentAsset = (document: Document | null, assetType: string) => Asset | null;
interface NavigatorSnapshot {
url: string | null;
alt?: string;
width: number | null;
height: number | null;
}
interface DesktopPreviewCardProps { interface DesktopPreviewCardProps {
doc: Document | null; doc: Document | null;
title?: string; title?: string;
ensureAssetUrl?: EnsureAssetUrl | null; ensureAssetUrl?: EnsureAssetUrl | null;
getDocumentAsset?: GetDocumentAsset; getDocumentAsset?: GetDocumentAsset;
onNavigatorSnapshot?: (docId: Identifier, snapshot: NavigatorSnapshot | null) => void;
shouldLoad?: boolean; shouldLoad?: boolean;
} }
@@ -35,7 +27,6 @@ const DesktopPreviewCard = ({
title, title,
ensureAssetUrl, ensureAssetUrl,
getDocumentAsset, getDocumentAsset,
onNavigatorSnapshot,
shouldLoad = true, shouldLoad = true,
}: DesktopPreviewCardProps): JSX.Element => { }: DesktopPreviewCardProps): JSX.Element => {
const currentUrl = useMemo(() => { const currentUrl = useMemo(() => {
@@ -46,41 +37,6 @@ const DesktopPreviewCard = ({
}); });
}, [doc, ensureAssetUrl, getDocumentAsset, shouldLoad]); }, [doc, ensureAssetUrl, getDocumentAsset, shouldLoad]);
const currentMetadata = useMemo(() => {
if (!doc || !getDocumentAsset) return null;
const asset = getDocumentAsset(doc, 'thumbnail');
return asset?.metadata || null;
}, [doc, getDocumentAsset]);
const docId = doc?.id ?? null;
const metadataWidth = Number((currentMetadata as { width?: number } | null)?.width);
const metadataHeight = Number((currentMetadata as { height?: number } | null)?.height);
useEffect(() => {
if (!onNavigatorSnapshot || !docId) {
return undefined;
}
if (!currentUrl) {
onNavigatorSnapshot(docId, null);
return undefined;
}
const snapshot = {
url: currentUrl,
alt: title,
width: Number.isFinite(metadataWidth) && metadataWidth > 0 ? metadataWidth : null,
height: Number.isFinite(metadataHeight) && metadataHeight > 0 ? metadataHeight : null,
};
onNavigatorSnapshot(docId, snapshot);
return () => onNavigatorSnapshot(docId, null);
}, [
docId,
currentUrl,
title,
metadataWidth,
metadataHeight,
onNavigatorSnapshot,
]);
const hasPreview = Boolean(currentUrl); const hasPreview = Boolean(currentUrl);
const cardClasses = ['desk-item__card']; const cardClasses = ['desk-item__card'];
if (!hasPreview) cardClasses.push('desk-item__card--empty'); if (!hasPreview) cardClasses.push('desk-item__card--empty');
+4 -40
View File
@@ -9,7 +9,6 @@ import { LayoutStore, LayoutCard } from './LayoutSystem';
import DesktopDocumentCard from './DesktopDocumentCard'; import DesktopDocumentCard from './DesktopDocumentCard';
import usePreviewMetadata from './hooks/usePreviewMetadata'; import usePreviewMetadata from './hooks/usePreviewMetadata';
import useDeskTagInteractions from './tags/useDeskTagInteractions'; import useDeskTagInteractions from './tags/useDeskTagInteractions';
import { useCardPointer } from './useCardPointer';
import '../styles/workspace/workspace-layout.css'; import '../styles/workspace/workspace-layout.css';
import '../styles/workspace/workspace-items.css'; import '../styles/workspace/workspace-items.css';
import '../styles/workspace/workspace-cards.css'; import '../styles/workspace/workspace-cards.css';
@@ -21,17 +20,6 @@ import type { DocumentsListEntry, Document } from '../types/documents';
import { useAppState } from '../app/appState'; import { useAppState } from '../app/appState';
import { useDocumentOpen } from '../contexts/DocumentOpenContext'; import { useDocumentOpen } from '../contexts/DocumentOpenContext';
type TagLike = { id?: Identifier | null; label?: string; color?: string | null } | null;
type OverlaySource = { url: string; alt?: string | null; mimeType?: string | null; };
export interface DeskDocument {
id?: Identifier | null;
title?: string;
tags?: TagLike[] | null;
documentLink?: OverlaySource | null;
[key: string]: unknown;
}
interface DocumentSizeInfo { interface DocumentSizeInfo {
width: number; width: number;
height: number; height: number;
@@ -39,7 +27,7 @@ interface DocumentSizeInfo {
} }
// Fallback size computation // Fallback size computation
const computeFallbackCardSize = (_doc: DeskDocument, defaultSize: number = 200): DocumentSizeInfo => { const computeFallbackCardSize = (_doc: Document, defaultSize: number = 200): DocumentSizeInfo => {
const size = Math.round(defaultSize * (1 / Math.SQRT2)); const size = Math.round(defaultSize * (1 / Math.SQRT2));
return { width: size, height: size, source: 'fallback' }; return { width: size, height: size, source: 'fallback' };
}; };
@@ -55,29 +43,6 @@ export interface DesktopWorkspaceProps {
defaultCardSize?: number; defaultCardSize?: number;
} }
// Wrapper to handle hooks per card
const DesktopDocumentContainer: React.FC<React.ComponentProps<typeof DesktopDocumentCard> & {
onSelect: (ids: string[], extend?: boolean) => void;
onDeselect: (ids: string[]) => void;
onDocumentActivate?: (id: string, event?: any) => void;
selection: string[];
requestCanvasFocus?: () => void;
}> = React.memo((props) => {
const { layoutCard, selected, onSelect, onDeselect, onDocumentActivate, selection, requestCanvasFocus } = props;
// We assume layoutCard is always present in this context
const cardPointerHandlers = useCardPointer(layoutCard!, !!selected, selection, onSelect, onDeselect, onDocumentActivate, requestCanvasFocus);
return (
<DesktopDocumentCard
{...props}
cardPointerHandlers={cardPointerHandlers}
/>
);
});
DesktopDocumentContainer.displayName = 'DesktopDocumentContainer';
const DesktopWorkspaceContent: React.FC<DesktopWorkspaceProps> = ({ const DesktopWorkspaceContent: React.FC<DesktopWorkspaceProps> = ({
entries, entries,
ensureAssetUrl, ensureAssetUrl,
@@ -102,7 +67,7 @@ const DesktopWorkspaceContent: React.FC<DesktopWorkspaceProps> = ({
.filter((entry): entry is { type: 'document'; document: Document } & DocumentsListEntry => .filter((entry): entry is { type: 'document'; document: Document } & DocumentsListEntry =>
entry.type === 'document' && !!entry.document entry.type === 'document' && !!entry.document
) )
.map(entry => entry.document as DeskDocument); .map(entry => entry.document);
}, [entries]); }, [entries]);
// Layout System Initialization // Layout System Initialization
@@ -189,7 +154,7 @@ const DesktopWorkspaceContent: React.FC<DesktopWorkspaceProps> = ({
const metadataMap = usePreviewMetadata(items, getDocumentAsset, ensureAssetUrl); const metadataMap = usePreviewMetadata(items, getDocumentAsset, ensureAssetUrl);
const ensureDocumentSize = useCallback((doc: DeskDocument): DocumentSizeInfo => { const ensureDocumentSize = useCallback((doc: Document): DocumentSizeInfo => {
if (doc.id) { if (doc.id) {
const meta = metadataMap.get(String(doc.id)); const meta = metadataMap.get(String(doc.id));
if (meta && meta.width && meta.height) if (meta && meta.width && meta.height)
@@ -404,7 +369,7 @@ const DesktopWorkspaceContent: React.FC<DesktopWorkspaceProps> = ({
}); });
return ( return (
<DesktopDocumentContainer <DesktopDocumentCard
key={docId} key={docId}
doc={doc} doc={doc}
style={{ style={{
@@ -421,7 +386,6 @@ const DesktopWorkspaceContent: React.FC<DesktopWorkspaceProps> = ({
docTagTokens="" docTagTokens=""
ensureAssetUrl={ensureAssetUrl} ensureAssetUrl={ensureAssetUrl}
getDocumentAsset={getDocumentAsset} getDocumentAsset={getDocumentAsset}
handleNavigatorSnapshot={() => { }}
onDocumentActivate={(_id, event) => { onDocumentActivate={(_id, event) => {
const isPreview = event && ((event as any).altKey || (event as any).button === 1); const isPreview = event && ((event as any).altKey || (event as any).button === 1);
openDocument(doc, isPreview ? 'preview' : 'sidepanel'); openDocument(doc, isPreview ? 'preview' : 'sidepanel');
+16 -25
View File
@@ -6,12 +6,6 @@ import MediaViewer from './MediaViewer';
import type { Document } from '../types/documents'; import type { Document } from '../types/documents';
interface DocumentLink {
url?: string;
mimeType?: string;
filename?: string;
}
interface ContentTabConfig { interface ContentTabConfig {
id?: string; id?: string;
label?: string; label?: string;
@@ -29,7 +23,6 @@ type LayoutMode = 'split' | 'stacked' | (string & {});
interface DocumentViewerLayoutProps { interface DocumentViewerLayoutProps {
document?: Document | null; document?: Document | null;
documentLink?: DocumentLink | null;
summaryProps?: Record<string, unknown>; summaryProps?: Record<string, unknown>;
metadataPayload?: unknown; metadataPayload?: unknown;
contentTabConfig?: ContentTabConfig | null; contentTabConfig?: ContentTabConfig | null;
@@ -43,7 +36,6 @@ interface DocumentViewerLayoutProps {
const DocumentViewerLayout = ({ const DocumentViewerLayout = ({
document, document,
documentLink,
summaryProps = {}, summaryProps = {},
metadataPayload, metadataPayload,
contentTabConfig, contentTabConfig,
@@ -58,28 +50,27 @@ const DocumentViewerLayout = ({
const viewportRef = useRef<HTMLDivElement>(null); const viewportRef = useRef<HTMLDivElement>(null);
const previewContent = useMemo(() => { const previewContent = useMemo(() => {
if (!document || !documentLink?.url) { if (!document) {
return null; return null;
} }
const normalizedMimeType = (documentLink.mimeType const downloadUrl = document.current_version?.download?.url;
|| document.mime_type if (!downloadUrl) {
|| '') return null;
.toLowerCase(); }
const normalizedFilename = documentLink.filename
|| document.filename const normalizedMimeType = (document.mime_type || '').toLowerCase();
|| document.original_name const normalizedFilename = document.filename;
|| '';
const isPdf = normalizedMimeType === 'application/pdf' const isPdf = normalizedMimeType === 'application/pdf'
|| normalizedMimeType === 'application/x-pdf'; || normalizedMimeType === 'application/x-pdf';
if (isPdf) { if (isPdf) {
const documentTitle = document.title const documentTitle = document.title;
|| document.filename
|| document.original_name;
return ( return (
<PdfViewer <PdfViewer
src={documentLink.url} src={downloadUrl}
title={documentTitle ? `Preview of ${documentTitle}` : undefined} title={documentTitle ? `Preview of ${documentTitle}` : undefined}
viewportRef={viewportRef} viewportRef={viewportRef}
/> />
@@ -88,23 +79,23 @@ const DocumentViewerLayout = ({
return ( return (
<MediaViewer <MediaViewer
src={documentLink.url} src={downloadUrl}
mimeType={normalizedMimeType} mimeType={normalizedMimeType}
filename={normalizedFilename} filename={normalizedFilename}
alt={`Preview of ${document.title}`} alt={`Preview of ${document.title}`}
/> />
); );
}, [document, documentLink, viewportRef]); }, [document, viewportRef]);
const renderViewportPane = useCallback(() => ( const renderViewportPane = useCallback(() => (
<div className="document-viewer__viewport" ref={viewportRef}> <div className="document-viewer__viewport" ref={viewportRef}>
{!documentLink?.url ? ( {!document?.current_version?.download?.url ? (
<div className="document-viewer__message">{previewLoadingMessage}</div> <div className="document-viewer__message">{previewLoadingMessage}</div>
) : ( ) : (
previewContent previewContent
)} )}
</div> </div>
), [previewContent, documentLink?.url, previewLoadingMessage, viewportRef]); ), [previewContent, document?.current_version?.download?.url, previewLoadingMessage, viewportRef]);
const viewportPane = renderViewportPane(); const viewportPane = renderViewportPane();
@@ -230,24 +230,6 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
const panelRef = useRef<HTMLDivElement | HTMLFormElement | HTMLElement | null>(null); const panelRef = useRef<HTMLDivElement | HTMLFormElement | HTMLElement | null>(null);
const isStackedLayout = useViewerLayoutMode(panelRef, document?.id); const isStackedLayout = useViewerLayoutMode(panelRef, document?.id);
const resolvedDocumentLink = useMemo(() => {
if (!document) {
return null;
}
const downloadUrl = document.current_version?.download?.url;
const href = downloadUrl;
if (!href) {
return null;
}
const mimeType = document.mime_type;
const filename = document.current_version?.filename || document.filename || document.title || null;
return {
url: href,
mimeType,
filename,
};
}, [document]);
const { const {
panelStyle: managedDetailPanelStyle, panelStyle: managedDetailPanelStyle,
handleProps: managedResizeHandleProps, handleProps: managedResizeHandleProps,
@@ -407,7 +389,6 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
<section className={viewerClassName}> <section className={viewerClassName}>
<DocumentViewerLayout <DocumentViewerLayout
document={document} document={document}
documentLink={resolvedDocumentLink}
summaryProps={summaryProps} summaryProps={summaryProps}
metadataPayload={metadataPayload} metadataPayload={metadataPayload}
contentTabConfig={contentTabConfig} contentTabConfig={contentTabConfig}