diff --git a/frontend/src/app/useDocumentPreview.ts b/frontend/src/app/useDocumentPreview.ts index d55fcf4..427eddd 100644 --- a/frontend/src/app/useDocumentPreview.ts +++ b/frontend/src/app/useDocumentPreview.ts @@ -21,10 +21,6 @@ type PreviewEntry = { contentType?: string | null; filename?: string | null; expiresAt?: number; - canGoPrev?: boolean; - canGoNext?: boolean; - goPrev?: () => void; - goNext?: () => void; }; interface AssetManagerLike { diff --git a/frontend/src/desktop/DesktopPreviewCard.tsx b/frontend/src/desktop/DesktopPreviewCard.tsx index 1488cfd..ef70bc7 100644 --- a/frontend/src/desktop/DesktopPreviewCard.tsx +++ b/frontend/src/desktop/DesktopPreviewCard.tsx @@ -1,8 +1,6 @@ import { useEffect } from 'react'; import type { JSX } from 'react'; -import { ArrowLeftIcon, ArrowRightIcon } from '../ui/icons'; import { useAssetNavigator } from '../hooks/useAssetNavigator'; -import { preventAll } from './events'; type Identifier = string | number; @@ -32,11 +30,6 @@ type GetDocumentAsset = (document: DocumentLike | null, assetType: string) => As interface NavigatorSnapshot { url: string | null; alt?: string; - canGoPrev: boolean; - canGoNext: boolean; - goPrev?: () => void; - goNext?: () => void; - ordinal: number; width: number | null; height: number | null; } @@ -46,7 +39,6 @@ interface DesktopPreviewCardProps { title?: string; ensureAssetUrl?: EnsureAssetUrl | null; getDocumentAsset: GetDocumentAsset; - prefetch?: number; onNavigatorSnapshot?: (docId: Identifier, snapshot: NavigatorSnapshot | null) => void; shouldLoad?: boolean; } @@ -56,7 +48,6 @@ const DesktopPreviewCard = ({ title, ensureAssetUrl, getDocumentAsset, - prefetch = 3, onNavigatorSnapshot, shouldLoad = true, }: DesktopPreviewCardProps): JSX.Element => { @@ -65,10 +56,9 @@ const DesktopPreviewCard = ({ assetType: 'thumbnail', ensureAssetUrl: shouldLoad ? ensureAssetUrl : null, getAsset: getDocumentAsset, - prefetch, }); - const { currentUrl, cardinality, canGoPrev, canGoNext, currentMetadata, ordinal } = navigator; + const { currentUrl, currentMetadata } = navigator; const docId = doc?.id ?? null; const metadataWidth = Number((currentMetadata as { width?: number } | null)?.width); @@ -78,14 +68,13 @@ const DesktopPreviewCard = ({ if (!onNavigatorSnapshot || !docId) { return undefined; } + if (!currentUrl) { + onNavigatorSnapshot(docId, null); + return undefined; + } const snapshot = { - url: currentUrl || null, + url: currentUrl, alt: title, - canGoPrev, - canGoNext, - goPrev: navigator.goPrev, - goNext: navigator.goNext, - ordinal, width: Number.isFinite(metadataWidth) && metadataWidth > 0 ? metadataWidth : null, height: Number.isFinite(metadataHeight) && metadataHeight > 0 ? metadataHeight : null, }; @@ -95,21 +84,14 @@ const DesktopPreviewCard = ({ docId, currentUrl, title, - canGoPrev, - canGoNext, - ordinal, metadataWidth, metadataHeight, - navigator.goPrev, - navigator.goNext, onNavigatorSnapshot, ]); const hasPreview = Boolean(currentUrl); const cardClasses = ['desk-item__card']; if (!hasPreview) cardClasses.push('desk-item__card--empty'); - const showNav = hasPreview && (cardinality > 1 || canGoPrev || canGoNext); - return (
)} - {showNav ? ( -
- - -
- ) : null} ); }; diff --git a/frontend/src/desktop/DesktopWorkspace.tsx b/frontend/src/desktop/DesktopWorkspace.tsx index 8f58d04..df85365 100644 --- a/frontend/src/desktop/DesktopWorkspace.tsx +++ b/frontend/src/desktop/DesktopWorkspace.tsx @@ -46,14 +46,8 @@ export interface DeskDocument { interface NavigatorSnapshot { url: string | null; alt?: string | null; - canGoPrev?: boolean; - canGoNext?: boolean; - goPrev?: () => void; - goNext?: () => void; - ordinal?: number | null; width?: number | null; height?: number | null; - contentType?: string | null; } type OverlayOriginHint = { @@ -71,8 +65,10 @@ interface OverlayOriginTransform { baseHeight: number; } -interface OverlayDisplay extends NavigatorSnapshot { +interface OverlayDisplay { url: string; + alt?: string | null; + contentType?: string | null; } interface DocumentSizeInfo { @@ -380,11 +376,6 @@ const DesktopWorkspace: React.FC = ({ prevSnapshot && prevSnapshot.url === snapshot.url && prevSnapshot.alt === snapshot.alt && - prevSnapshot.canGoPrev === snapshot.canGoPrev && - prevSnapshot.canGoNext === snapshot.canGoNext && - prevSnapshot.goPrev === snapshot.goPrev && - prevSnapshot.goNext === snapshot.goNext && - prevSnapshot.ordinal === snapshot.ordinal && prevSnapshot.width === snapshot.width && prevSnapshot.height === snapshot.height; if (sameSnapshot) { @@ -672,11 +663,7 @@ const DesktopWorkspace: React.FC = ({ if (!overlaySource) { return null; } - return { - ...overlaySource, - canGoPrev: false, - canGoNext: false, - }; + return overlaySource; }, [overlaySource]); const overlayDocument = useMemo(() => { @@ -1122,7 +1109,6 @@ function DesktopWorkspaceView({ void; - goNext?: () => void; }; type DocumentLikeWithPreview = DocumentLike & { previewEntry?: PreviewEntry }; diff --git a/frontend/src/detail/useDetailWorkspace.ts b/frontend/src/detail/useDetailWorkspace.ts index caa1882..9b92b1a 100644 --- a/frontend/src/detail/useDetailWorkspace.ts +++ b/frontend/src/detail/useDetailWorkspace.ts @@ -27,10 +27,7 @@ interface FolderNode { type PreviewEntry = { url?: string; - canGoPrev?: boolean; - canGoNext?: boolean; - goPrev?: () => void; - goNext?: () => void; + contentType?: string | null; } | null; interface UseDetailWorkspaceArgs { diff --git a/frontend/src/hooks/useAssetNavigator.ts b/frontend/src/hooks/useAssetNavigator.ts index bfe8f52..b9274c5 100644 --- a/frontend/src/hooks/useAssetNavigator.ts +++ b/frontend/src/hooks/useAssetNavigator.ts @@ -1,28 +1,26 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { createAssetView } from '../asset_manager'; type Identifier = string | number; -interface DocumentLike { +type DocumentLike = { id?: Identifier; [key: string]: unknown; -} +}; -interface AssetObject { - ordinal?: number; +type AssetObject = { url?: string | null; metadata?: Record | null; [key: string]: unknown; -} +}; -interface AssetLike { +type AssetLike = { id?: Identifier; - cardinality?: number; url?: string | null; metadata?: Record | null; objects?: AssetObject[]; [key: string]: unknown; -} +}; type EnsureAssetUrl = ( documentId: Identifier, @@ -32,21 +30,18 @@ type EnsureAssetUrl = ( type GetAsset = (document: DocumentLike, assetType: string) => AssetLike | null; -interface AssetViewLike { - getCardinality: () => number; - getObjects: () => AssetObject[]; +type AssetViewLike = { getObject: (ordinal?: number) => AssetObject | null; + getObjects: () => AssetObject[]; getPrimaryUrl: () => string | null; getPrimaryMetadata: () => Record | null; -} +}; interface UseAssetNavigatorOptions { document?: DocumentLike | null; assetType: string; - ensureAssetUrl?: EnsureAssetUrl; + ensureAssetUrl?: EnsureAssetUrl | null; getAsset?: GetAsset; - prefetch?: number; - defaultOrdinal?: number; } interface AssetNavigatorReturn { @@ -54,36 +49,16 @@ interface AssetNavigatorReturn { documentId: Identifier | null; asset: AssetLike | null; assetType: string; - ordinal: number; - setOrdinal: (next: number) => void; - goPrev: () => void; - goNext: () => void; - canGoPrev: boolean; - canGoNext: boolean; - cardinality: number; - currentObject: AssetObject | null; currentUrl: string | null; - currentMetadata: AssetObject['metadata']; - objects: AssetObject[]; + currentMetadata: Record | null; isLoading: boolean; } -const clampOrdinalValue = (value: number, cardinality: number, defaultOrdinal: number) => { - const raw = Number.isFinite(value) ? value : defaultOrdinal; - let next = Math.max(1, Math.floor(raw)); - if (cardinality && cardinality > 0) { - next = Math.min(next, cardinality); - } - return next; -}; - export const useAssetNavigator = ({ document, assetType, ensureAssetUrl, getAsset, - prefetch = 2, - defaultOrdinal = 1, }: UseAssetNavigatorOptions): AssetNavigatorReturn => { const documentId = (document?.id ?? null) as Identifier | null; @@ -98,110 +73,42 @@ export const useAssetNavigator = ({ () => createAssetView(asset) as unknown as AssetViewLike, [asset], ); - const cardinality = view.getCardinality(); - const [ordinal, setOrdinalInternal] = useState(defaultOrdinal); - - useEffect(() => { - setOrdinalInternal(defaultOrdinal); - }, [documentId, assetType, defaultOrdinal]); - - const setOrdinal = useCallback( - (next: number) => { - setOrdinalInternal(clampOrdinalValue(next, cardinality, defaultOrdinal)); - }, - [cardinality, defaultOrdinal], - ); - - const goPrev = useCallback(() => { - setOrdinalInternal((prev) => clampOrdinalValue(prev - 1, cardinality, defaultOrdinal)); - }, [cardinality, defaultOrdinal]); - - const goNext = useCallback(() => { - setOrdinalInternal((prev) => clampOrdinalValue(prev + 1, cardinality, defaultOrdinal)); - }, [cardinality, defaultOrdinal]); - - const objects = view.getObjects(); - const currentObject = view.getObject(ordinal); + const currentObject = view.getObject(1) || view.getObjects()[0] || null; const currentUrl = currentObject?.url ?? view.getPrimaryUrl() ?? null; - const currentMetadata = currentObject?.metadata ?? view.getPrimaryMetadata() ?? null; + const currentMetadata = (currentObject?.metadata ?? view.getPrimaryMetadata()) || null; - const canGoPrev = ordinal > 1; - const canGoNext = cardinality ? ordinal < cardinality : true; - - const ordinalsNeedingLoad = useMemo(() => { - const missing: number[] = []; - if (!asset) { - return missing; - } - const safePrefetch = Math.max(1, prefetch); - const maxOrdinal = cardinality && cardinality > 0 - ? Math.min(cardinality, ordinal + safePrefetch - 1) - : ordinal + safePrefetch - 1; - - for (let ord = ordinal; ord <= maxOrdinal; ord += 1) { - const object = view.getObject(ord); - if (!object?.url) { - missing.push(ord); - } - } - - return missing; - }, [asset, view, ordinal, prefetch, cardinality]); - - const fetchStart = ordinalsNeedingLoad.length ? ordinalsNeedingLoad[0] : null; - const fetchEnd = ordinalsNeedingLoad.length - ? ordinalsNeedingLoad[ordinalsNeedingLoad.length - 1] - : null; - const fetchLimit = fetchStart !== null && fetchEnd !== null ? fetchEnd - fetchStart + 1 : null; - - const [loading, setLoading] = useState(false); + const [isLoading, setIsLoading] = useState(false); useEffect(() => { - if (!documentId || !asset || !ensureAssetUrl) { - setLoading(false); - return; + if (!ensureAssetUrl || !documentId || !asset) { + return undefined; } - if (fetchStart === null || fetchLimit === null) { - setLoading(false); - return; + if (currentUrl) { + return undefined; } - let cancelled = false; - setLoading(true); - ensureAssetUrl(documentId, asset, { - start: fetchStart, - limit: fetchLimit, - }) + setIsLoading(true); + ensureAssetUrl(documentId, asset, { start: 1, limit: 1 }) .catch(() => {}) .finally(() => { if (!cancelled) { - setLoading(false); + setIsLoading(false); } }); - return () => { cancelled = true; }; - }, [documentId, asset, ensureAssetUrl, fetchStart, fetchLimit]); + }, [asset, currentUrl, documentId, ensureAssetUrl]); return { document, documentId, asset, assetType, - ordinal, - setOrdinal, - goPrev, - goNext, - canGoPrev, - canGoNext, - cardinality, - currentObject, currentUrl, currentMetadata, - objects, - isLoading: loading, + isLoading, }; }; diff --git a/frontend/src/styles/detail/detail-panels.css b/frontend/src/styles/detail/detail-panels.css index f4dda49..8c05daf 100644 --- a/frontend/src/styles/detail/detail-panels.css +++ b/frontend/src/styles/detail/detail-panels.css @@ -931,9 +931,6 @@ opacity: 1; } -.desk-card__nav { - bottom: 0.5rem; -} .bulk-tags { display: flex; diff --git a/frontend/src/styles/workspace/workspace-cards.css b/frontend/src/styles/workspace/workspace-cards.css index 4079627..85a8c6d 100644 --- a/frontend/src/styles/workspace/workspace-cards.css +++ b/frontend/src/styles/workspace/workspace-cards.css @@ -68,55 +68,3 @@ word-break: break-word; white-space: normal; } - -.desk-card__nav { - position: absolute; - bottom: 1.8rem; - left: 50%; - transform: translateX(-50%); - transform-origin: center; - display: flex; - gap: 1.5rem; - opacity: 0; - pointer-events: none; - transition: opacity 0.2s ease; -} - -.desk-item__card:hover .desk-card__nav { - opacity: 1; - pointer-events: auto; -} - -.desk-card__nav-button { - display: inline-flex; - align-items: center; - justify-content: center; - width: 2.4em; - height: 2.4em; - padding: 0.25em; - border-radius: 50%; - border: none; - background: var(--preview-nav-bg); - color: var(--preview-nav-fg); - cursor: pointer; - transition: background 0.15s ease, opacity 0.15s ease; -} - -.desk-card__nav-button:hover:not([disabled]) { - background: var(--preview-nav-bg-hover); -} - -.desk-card__nav-button:disabled { - opacity: 0.4; - cursor: default; -} - -.desk-card__nav-button:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 2px; -} - -.desk-card__nav-button svg { - width: 100%; - height: 100%; -}