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%;
-}