ported from the standalone app's hash-tab UI. The panels do not coordinate through props (the panel contract only ever hands a component its panelId) or usePanelChannel-as-selection: - Gallery's lightbox is ?selected=<id>, Collections' detail is ?collection=<id> — different names since both panels render on the same route at once - Generate bumps useAtelierRefresh (added to officerdev/src/channels.ts) when a job finishes; Gallery subscribes to it — a genuine signal, not a selection, so it stays a channel images render through GET /files/:filename with a ?token= query param appended client-side (lib/api.ts's fileUrl), the same escape hatch FileViewer and the music plugin use for exactly this: a browser cannot set an Authorization header on an <img src>. Providers is not here — see PLUGIN.md for why it became a platform settings screen instead. assets/icon.png and assets/splash.png are [placeholder] flat swatches, not real artwork — see assets/PLACEHOLDER.md.
43 lines
1.4 KiB
TypeScript
43 lines
1.4 KiB
TypeScript
import { useCallback, useEffect } from 'react';
|
|
import { useState } from 'react';
|
|
import { useAtelierRefresh } from 'officerdev';
|
|
import { useAtelierClient } from '../lib/api';
|
|
import type { GenerationRow } from '../lib/api';
|
|
|
|
export const useGalleryManager = () => {
|
|
const client = useAtelierClient();
|
|
const [rows, setRows] = useState<GenerationRow[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [refreshSignal] = useAtelierRefresh();
|
|
|
|
const refresh = useCallback(async () => {
|
|
try {
|
|
setRows(await client.listGenerations());
|
|
setError(null);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : String(err));
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
void refresh();
|
|
}, [refresh]);
|
|
|
|
// A finished generation elsewhere on the dashboard (the Generate panel) bumps this — re-read.
|
|
useEffect(() => {
|
|
if (refreshSignal > 0) void refresh();
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [refreshSignal]);
|
|
|
|
const succeeded = rows.filter((row) => row.status === 'succeeded' && row.image_path);
|
|
const totalCost = rows.reduce((sum, row) => sum + Number(row.cost_usd ?? 0), 0);
|
|
|
|
return { rows, succeeded, totalCost, loading, error, refresh };
|
|
};
|
|
|
|
export type GalleryManager = ReturnType<typeof useGalleryManager>;
|