feat: Remove documentLink prop and onNavigatorSnapshot logic, and add openDetailPanel and closeDetailPanel to document shell.
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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,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'],
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,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');
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user