From 2d0d02765ac2fb8a5d927c81c946820e8892d5f1 Mon Sep 17 00:00:00 2001 From: makearmy Date: Fri, 3 Oct 2025 21:39:33 -0400 Subject: [PATCH] images fix for co2-galvo details --- components/details/CO2GalvoDetail.tsx | 152 ++++++++++++++------------ 1 file changed, 81 insertions(+), 71 deletions(-) diff --git a/components/details/CO2GalvoDetail.tsx b/components/details/CO2GalvoDetail.tsx index eb95aaad..8b0ad178 100644 --- a/components/details/CO2GalvoDetail.tsx +++ b/components/details/CO2GalvoDetail.tsx @@ -5,7 +5,6 @@ import { useEffect, useMemo, useState } from "react"; import { useSearchParams, useRouter } from "next/navigation"; import SettingsSubmit from "@/components/forms/SettingsSubmit"; -// ───────────────────────────────── Types & helpers ───────────────────────────── type Rec = { submission_id: string | number; setting_title?: string | null; @@ -40,14 +39,23 @@ async function readJson(res: Response) { const text = await res.text(); try { return text ? JSON.parse(text) : null; } catch { throw new Error(`Unexpected response (HTTP ${res.status})`); } } + const ownerLabel = (o: Rec["owner"]) => !o ? "—" : (typeof o === "string" || typeof o === "number") ? String(o) : (o.username || String(o.id ?? "—")); + const isMine = (owner: Rec["owner"], meId: string | null) => !!meId && !!owner && ((typeof owner === "string" || typeof owner === "number") ? String(owner) === meId : (owner.id != null && String(owner.id) === meId)); + const resolveFileId = (v: Rec["photo"]): string | null => v == null ? null : (typeof v === "string" || typeof v === "number") ? String(v) : v.id ? String(v.id) : null; -// ───────────────────────────────── Component ────────────────────────────────── +// ✅ Use public Directus assets (works on your stack). Fallback to proxy if no base configured. +const API_BASE = (process.env.NEXT_PUBLIC_API_BASE_URL || "").replace(/\/$/, ""); +function fileUrl(id?: string) { + if (!id) return ""; + return API_BASE ? `${API_BASE}/assets/${id}` : `/api/dx/assets/${id}`; +} + export default function CO2GalvoDetail({ id, mode, @@ -61,7 +69,6 @@ export default function CO2GalvoDetail({ onBack?: () => void; showOwnerEdit?: boolean; }) { - // Hooks (top-level only) const sp = useSearchParams(); const router = useRouter(); const editParam = sp.get("edit") === "1"; @@ -74,11 +81,7 @@ export default function CO2GalvoDetail({ const [meId, setMeId] = useState(null); const [lightbox, setLightbox] = useState<{ src: string; alt: string } | null>(null); - // object-URL sources for images - const [photoUrl, setPhotoUrl] = useState(null); - const [screenUrl, setScreenUrl] = useState(null); - - // me id + // me id (for owner-gated Edit button) useEffect(() => { let dead = false; (async () => { @@ -93,10 +96,11 @@ export default function CO2GalvoDetail({ return () => { dead = true; }; }, []); - // load record + // load record (readable fields) useEffect(() => { if (!id) return; let dead = false; + (async () => { try { setLoading(true); @@ -104,16 +108,19 @@ export default function CO2GalvoDetail({ const fields = [ "submission_id","setting_title","setting_notes", - "photo.id","screen.id", - "mat.id","mat.name","mat_coat.id","mat_coat.name","mat_color.id","mat_color.name","mat_opacity.id","mat_opacity.opacity","mat_thickness", - "source.submission_id","source.make","source.model","source.nm", - "lens.id","lens.field_size","lens.focal_length", - "focus","laser_soft","repeat_all", - "fill_settings","line_settings","raster_settings", - "owner.id","owner.username","uploader","last_modified_date", + "photo.id","screen.id", + "mat.id","mat.name","mat_coat.id","mat_coat.name","mat_color.id","mat_color.name","mat_opacity.id","mat_opacity.opacity","mat_thickness", + "source.submission_id","source.make","source.model","source.nm", + "lens.id","lens.field_size","lens.focal_length", + "focus","laser_soft","repeat_all", + "fill_settings","line_settings","raster_settings", + "owner.id","owner.username","uploader","last_modified_date", ].join(","); - const url = `/api/dx/items/settings_co2gal?fields=${encodeURIComponent(fields)}&filter[submission_id][_eq]=${encodeURIComponent(String(id))}&limit=1`; + const url = `/api/dx/items/settings_co2gal?fields=${encodeURIComponent( + fields + )}&filter[submission_id][_eq]=${encodeURIComponent(String(id))}&limit=1`; + const r = await fetch(url, { cache: "no-store", credentials: "include" }); if (!r.ok) { const j = await readJson(r).catch(() => null); @@ -129,15 +136,18 @@ export default function CO2GalvoDetail({ if (!dead) setLoading(false); } })(); + return () => { dead = true; }; }, [id]); - // build initial values + // initial values for edit form const initialValues = useMemo(() => { if (!rec) return null; const toId = (v: any) => (v == null ? null : typeof v === "object" ? v.id ?? v.submission_id ?? null : v); + const photoId = resolveFileId(rec.photo); const screenId = resolveFileId(rec.screen); + const matId = toId(rec.mat); const coatId = toId(rec.mat_coat); const colorId = toId(rec.mat_color); @@ -166,46 +176,16 @@ export default function CO2GalvoDetail({ }; }, [rec]); - // fetch images as blobs with credentials → object URLs (works if /api/dx/assets requires auth) - useEffect(() => { - let revoke1: string | null = null; - let revoke2: string | null = null; + // derive image URLs directly (no proxy/blob) + const photoUrl = useMemo(() => { + const pid = resolveFileId(rec?.photo ?? null); + return pid ? fileUrl(pid) : null; + }, [rec?.photo]); - async function load(id: string): Promise { - try { - const res = await fetch(`/api/dx/assets/${id}`, { credentials: "include" }); - if (!res.ok) return null; - const blob = await res.blob(); - return URL.createObjectURL(blob); - } catch { - return null; - } - } - - (async () => { - const pid = resolveFileId(rec?.photo ?? null); - const sid = resolveFileId(rec?.screen ?? null); - - if (pid) { - const u = await load(pid); - if (u) { setPhotoUrl(u); revoke1 = u; } else { setPhotoUrl(null); } - } else { - setPhotoUrl(null); - } - - if (sid) { - const u = await load(sid); - if (u) { setScreenUrl(u); revoke2 = u; } else { setScreenUrl(null); } - } else { - setScreenUrl(null); - } - })(); - - return () => { - if (revoke1) URL.revokeObjectURL(revoke1); - if (revoke2) URL.revokeObjectURL(revoke2); - }; - }, [rec?.photo, rec?.screen]); + const screenUrl = useMemo(() => { + const sid = resolveFileId(rec?.screen ?? null); + return sid ? fileUrl(sid) : null; + }, [rec?.screen]); function clearEditParam() { const params = new URLSearchParams(sp.toString()); @@ -213,11 +193,15 @@ export default function CO2GalvoDetail({ router.replace(`?${params.toString()}`); } - // ─────────────────────────────── Render ─────────────────────────────── if (loading) return

Loading setting…

; - if (err) return
{err}
; + if (err) return ( +
+
{err}
+
+ ); if (!rec) return

Setting not found.

; + // EDIT mode if (editMode && initialValues) { return (
@@ -227,11 +211,17 @@ export default function CO2GalvoDetail({ Cancel - +
); } + // VIEW mode const ownerDisplay = ownerLabel(rec.owner); const canEdit = showOwnerEdit && isMine(rec.owner, meId); @@ -243,7 +233,7 @@ export default function CO2GalvoDetail({
- {/* Left meta */} + {/* Meta */}
Owner: {ownerDisplay}
@@ -282,24 +272,45 @@ export default function CO2GalvoDetail({ )}
- {/* Right images: compact 1:1 thumbs + lightbox */} + {/* Images: 1:1 thumbs + click to enlarge; only render when we have a URL */}
- {photoUrl && ( + {photoUrl ? (
-
Result
- )} - {screenUrl && ( + ) : null} + + {screenUrl ? (
-
Settings Screenshot
- )} + ) : null}
@@ -409,7 +420,6 @@ export default function CO2GalvoDetail({ )} - {/* Lightbox */} {lightbox && (