no prev/next anymore

This commit is contained in:
2025-11-16 21:25:39 +01:00
parent 1f2c62241c
commit fa6b4b94f6
8 changed files with 35 additions and 278 deletions
+24 -117
View File
@@ -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<string, unknown> | null;
[key: string]: unknown;
}
};
interface AssetLike {
type AssetLike = {
id?: Identifier;
cardinality?: number;
url?: string | null;
metadata?: Record<string, unknown> | 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<string, unknown> | 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<string, unknown> | 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<number[]>(() => {
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,
};
};