From 5a8d93b36f32b3eb0122b073e25e8b4df399fcc7 Mon Sep 17 00:00:00 2001 From: makearmy Date: Fri, 3 Oct 2025 21:31:39 -0400 Subject: [PATCH] crash fix --- components/details/CO2GalvoDetail.tsx | 100 +++++++++++++++++--------- 1 file changed, 65 insertions(+), 35 deletions(-) diff --git a/components/details/CO2GalvoDetail.tsx b/components/details/CO2GalvoDetail.tsx index 88724af4..eb95aaad 100644 --- a/components/details/CO2GalvoDetail.tsx +++ b/components/details/CO2GalvoDetail.tsx @@ -5,9 +5,7 @@ import { useEffect, useMemo, useState } from "react"; import { useSearchParams, useRouter } from "next/navigation"; import SettingsSubmit from "@/components/forms/SettingsSubmit"; -/** ───────────────────────────────────────────────────────────── - * Types - * ──────────────────────────────────────────────────────────── */ +// ───────────────────────────────── Types & helpers ───────────────────────────── type Rec = { submission_id: string | number; setting_title?: string | null; @@ -38,9 +36,6 @@ type Rec = { last_modified_date?: string | null; }; -/** ───────────────────────────────────────────────────────────── - * Small helpers (no hooks below) - * ──────────────────────────────────────────────────────────── */ 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})`); } @@ -51,11 +46,8 @@ 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; -const assetSrc = (id?: string | null) => (id ? `/api/dx/assets/${id}` : ""); -/** ───────────────────────────────────────────────────────────── - * Component - * ──────────────────────────────────────────────────────────── */ +// ───────────────────────────────── Component ────────────────────────────────── export default function CO2GalvoDetail({ id, mode, @@ -69,10 +61,9 @@ export default function CO2GalvoDetail({ onBack?: () => void; showOwnerEdit?: boolean; }) { - // ── Hooks (top-level only; no conditional usage) ─────────── + // Hooks (top-level only) const sp = useSearchParams(); const router = useRouter(); - const editParam = sp.get("edit") === "1"; const editMode = mode ? mode === "edit" : editParam; @@ -83,7 +74,11 @@ export default function CO2GalvoDetail({ const [meId, setMeId] = useState(null); const [lightbox, setLightbox] = useState<{ src: string; alt: string } | null>(null); - // current user id + // object-URL sources for images + const [photoUrl, setPhotoUrl] = useState(null); + const [screenUrl, setScreenUrl] = useState(null); + + // me id useEffect(() => { let dead = false; (async () => { @@ -109,13 +104,13 @@ 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`; @@ -137,7 +132,7 @@ export default function CO2GalvoDetail({ return () => { dead = true; }; }, [id]); - // derive edit initial values (safe when rec is null) + // build initial values const initialValues = useMemo(() => { if (!rec) return null; const toId = (v: any) => (v == null ? null : typeof v === "object" ? v.id ?? v.submission_id ?? null : v); @@ -171,19 +166,58 @@ export default function CO2GalvoDetail({ }; }, [rec]); - // helpers + // 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; + + 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]); + function clearEditParam() { const params = new URLSearchParams(sp.toString()); params.delete("edit"); router.replace(`?${params.toString()}`); } - // ── Render guards ─────────────────────────────────────────── + // ─────────────────────────────── Render ─────────────────────────────── if (loading) return

Loading setting…

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

Setting not found.

; - // ── EDIT MODE ─────────────────────────────────────────────── if (editMode && initialValues) { return (
@@ -198,11 +232,8 @@ export default function CO2GalvoDetail({ ); } - // ── VIEW MODE ─────────────────────────────────────────────── const ownerDisplay = ownerLabel(rec.owner); const canEdit = showOwnerEdit && isMine(rec.owner, meId); - const photoSrc = assetSrc(resolveFileId(rec.photo)); - const screenSrc = assetSrc(resolveFileId(rec.screen)); return (
@@ -251,20 +282,20 @@ export default function CO2GalvoDetail({ )}
- {/* Right images */} + {/* Right images: compact 1:1 thumbs + lightbox */}
- {photoSrc && ( + {photoUrl && (
-
Result
)} - {screenSrc && ( + {screenUrl && (
-
Settings Screenshot
@@ -272,7 +303,6 @@ export default function CO2GalvoDetail({
- {/* Repeaters */} {(rec.fill_settings?.length ?? 0) > 0 && (

Fill Settings