diff --git a/src/components/LibraryApp.tsx b/src/components/LibraryApp.tsx
index 1046715..5db7e84 100644
--- a/src/components/LibraryApp.tsx
+++ b/src/components/LibraryApp.tsx
@@ -53,6 +53,8 @@ export default function LibraryApp() {
if (r.ok) notify('Alternative wird erzeugt — erscheint gleich hier.'); else notify(r.error || 'Fehler.');
setTimeout(load, 2500);
};
+ // Ergebnis als neue Vorlage ins Studio übernehmen und weiterbearbeiten.
+ const reuse = (id: string) => { window.location.href = `/?reuse=${id}`; };
const newFolder = async () => {
const name = prompt('Name des Ordners?'); if (!name) return;
@@ -143,8 +145,9 @@ export default function LibraryApp() {
{v.delivery_status === 'failed' && Picdrop ✕}
-
+ {v.mode !== 'generate' &&
}
Laden
+
- {compare.filename} · {compare.output_px}px — Regler: links Original, rechts Ergebnis
+ {compare.filename} · {compare.output_px}px — Regler: links {compare.mode === 'compose' ? 'Vorlage' : 'Original'}, rechts Ergebnis
)}
{toast && {toast}
}
@@ -208,6 +211,7 @@ function LibStyles() {
.name{margin-top:8px;width:100%;font-size:12.5px;padding:6px 8px;font-family:var(--font-mono);border:1px solid var(--line);border-radius:3px;background:#fff;}
.meta{font-size:11px;color:var(--soft);margin-top:5px;}
.mini.stark{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:600;}
+ .mini.stark2{border-color:var(--accent);color:var(--accent);font-weight:600;}
.dbadge{display:inline-block;margin-left:6px;font-family:var(--font-mono);font-size:9px;letter-spacing:.08em;text-transform:uppercase;background:var(--paper);color:var(--soft);padding:1px 6px;border-radius:20px;}
.dbadge.ok{background:var(--accent-bg);color:var(--accent);}
.dbadge.err{color:var(--err);}
diff --git a/src/components/StudioApp.tsx b/src/components/StudioApp.tsx
index 2c73103..62d48a8 100644
--- a/src/components/StudioApp.tsx
+++ b/src/components/StudioApp.tsx
@@ -115,6 +115,25 @@ export default function StudioApp({ recipes }: { recipes: any[] }) {
}).catch(() => {});
}, []);
+ // Ergebnis aus der Bibliothek übernehmen: ?reuse= lädt es als Vorlage.
+ useEffect(() => {
+ const params = new URLSearchParams(window.location.search);
+ const reuseId = params.get('reuse');
+ if (!reuseId) return;
+ (async () => {
+ const id = crypto.randomUUID();
+ setPics((p) => [...p, { id, src: `/api/items/${reuseId}/file?thumb=1`, name: 'weiterbearbeiten.png', uploading: true }]);
+ try {
+ const j = await fetch(`/api/items/${reuseId}/reuse`, { method: 'POST' }).then((r) => r.json());
+ setPics((p) => p.map((x) => x.id === id
+ ? { ...x, uploading: false, source_path: j.source_path, name: j.filename || x.name, error: j.error } : x));
+ } catch {
+ setPics((p) => p.map((x) => x.id === id ? { ...x, uploading: false, error: 'Konnte Bild nicht laden' } : x));
+ }
+ history.replaceState(null, '', '/');
+ })();
+ }, []);
+
function toggleTask(id: string) {
setTasks((t) => {
let next = t.includes(id) ? t.filter((x) => x !== id) : [...t, id];
diff --git a/src/pages/api/items/[id]/file.ts b/src/pages/api/items/[id]/file.ts
index 2ada5e3..6e0f441 100644
--- a/src/pages/api/items/[id]/file.ts
+++ b/src/pages/api/items/[id]/file.ts
@@ -11,9 +11,10 @@ export const GET: APIRoute = async ({ params, url, locals }) => {
const wantThumb = q.get('thumb') === '1';
const wantSrc = q.get('src') === '1';
const item = await one(
- `SELECT source_path, result_path, thumb_path, filename, has_alpha FROM items WHERE id=$1`, [params.id]);
- // Vorschau: Thumbnail bevorzugen, sonst Ergebnis. Quelle nur explizit.
- const path = wantSrc ? item?.source_path
+ `SELECT source_path, source_paths, result_path, thumb_path, filename, has_alpha FROM items WHERE id=$1`, [params.id]);
+ // Quelle: Einzelquelle, sonst erste Kombinier-Quelle (für Vorher/Nachher).
+ const firstSource = item?.source_path || (Array.isArray(item?.source_paths) ? item.source_paths[0] : null);
+ const path = wantSrc ? firstSource
: wantThumb ? (item?.thumb_path || item?.result_path)
: item?.result_path;
if (!path) return new Response('Nicht gefunden', { status: 404 });
diff --git a/src/pages/api/items/[id]/reuse.ts b/src/pages/api/items/[id]/reuse.ts
new file mode 100644
index 0000000..dd64432
--- /dev/null
+++ b/src/pages/api/items/[id]/reuse.ts
@@ -0,0 +1,26 @@
+import type { APIRoute } from 'astro';
+import { randomUUID } from 'node:crypto';
+import { one } from '../../../../lib/db';
+import { getObject, putObject, sourceKey } from '../../../../lib/storage';
+
+export const prerender = false;
+const json = (b: unknown, s = 200) =>
+ new Response(JSON.stringify(b), { status: s, headers: { 'Content-Type': 'application/json' } });
+
+/** Kopiert das Ergebnis eines Items in eine neue Quelle, damit es im Studio
+ * als Vorlage weiterbearbeitet werden kann. */
+export const POST: APIRoute = async ({ params, locals }) => {
+ if (!locals.user) return new Response('Unauthorized', { status: 401 });
+ const it = await one<{ result_path: string | null; filename: string | null }>(
+ 'SELECT result_path, filename FROM items WHERE id=$1', [params.id]);
+ if (!it?.result_path) return json({ error: 'Kein Ergebnis vorhanden.' }, 404);
+ try {
+ const buf = await getObject(it.result_path);
+ const key = sourceKey(randomUUID(), 'png');
+ await putObject(key, buf, 'image/png');
+ const name = (it.filename || 'bild').replace(/\.[^.]+$/, '') + '.png';
+ return json({ source_path: key, filename: name });
+ } catch (e: any) {
+ return json({ error: e?.message || 'Konnte Ergebnis nicht laden.' }, 500);
+ }
+};