crash fix
This commit is contained in:
parent
7903a573a6
commit
5a8d93b36f
1 changed files with 65 additions and 35 deletions
|
|
@ -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<string | null>(null);
|
||||
const [lightbox, setLightbox] = useState<{ src: string; alt: string } | null>(null);
|
||||
|
||||
// current user id
|
||||
// object-URL sources for images
|
||||
const [photoUrl, setPhotoUrl] = useState<string | null>(null);
|
||||
const [screenUrl, setScreenUrl] = useState<string | null>(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<string | null> {
|
||||
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 <p className="p-6">Loading setting…</p>;
|
||||
if (err) return <div className="p-6"><div className="rounded-md border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-700">{err}</div></div>;
|
||||
if (!rec) return <p className="p-6">Setting not found.</p>;
|
||||
|
||||
// ── EDIT MODE ───────────────────────────────────────────────
|
||||
if (editMode && initialValues) {
|
||||
return (
|
||||
<main className="space-y-4">
|
||||
|
|
@ -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 (
|
||||
<div className="space-y-6">
|
||||
|
|
@ -251,20 +282,20 @@ export default function CO2GalvoDetail({
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* Right images */}
|
||||
{/* Right images: compact 1:1 thumbs + lightbox */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{photoSrc && (
|
||||
{photoUrl && (
|
||||
<figure className="border rounded overflow-hidden">
|
||||
<button type="button" onClick={() => setLightbox({ src: photoSrc, alt: "Result" })} className="block w-full aspect-square">
|
||||
<img src={photoSrc} alt="Result" className="w-full h-full object-cover" loading="lazy" />
|
||||
<button type="button" onClick={() => setLightbox({ src: photoUrl, alt: "Result" })} className="block w-full max-w-[360px] aspect-square">
|
||||
<img src={photoUrl} alt="Result" className="w-full h-full object-cover" loading="lazy" />
|
||||
</button>
|
||||
<figcaption className="text-xs p-1 text-muted-foreground">Result</figcaption>
|
||||
</figure>
|
||||
)}
|
||||
{screenSrc && (
|
||||
{screenUrl && (
|
||||
<figure className="border rounded overflow-hidden">
|
||||
<button type="button" onClick={() => setLightbox({ src: screenSrc, alt: "Settings Screenshot" })} className="block w-full aspect-square">
|
||||
<img src={screenSrc} alt="Settings Screenshot" className="w-full h-full object-cover" loading="lazy" />
|
||||
<button type="button" onClick={() => setLightbox({ src: screenUrl, alt: "Settings Screenshot" })} className="block w-full max-w-[360px] aspect-square">
|
||||
<img src={screenUrl} alt="Settings Screenshot" className="w-full h-full object-cover" loading="lazy" />
|
||||
</button>
|
||||
<figcaption className="text-xs p-1 text-muted-foreground">Settings Screenshot</figcaption>
|
||||
</figure>
|
||||
|
|
@ -272,7 +303,6 @@ export default function CO2GalvoDetail({
|
|||
</div>
|
||||
</section>
|
||||
|
||||
{/* Repeaters */}
|
||||
{(rec.fill_settings?.length ?? 0) > 0 && (
|
||||
<section className="space-y-2">
|
||||
<h2 className="text-lg font-semibold">Fill Settings</h2>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue