Files
atelier/web/Gallery/useGalleryManager.ts
T
pastilhas 859fb5767c web: three panels — Generate, Gallery, Collections — and a layout
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.
2026-08-20 04:01:10 +00:00

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