images fix for co2-galvo details
This commit is contained in:
parent
5a8d93b36f
commit
2d0d02765a
1 changed files with 81 additions and 71 deletions
|
|
@ -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<string | null>(null);
|
||||
const [lightbox, setLightbox] = useState<{ src: string; alt: string } | null>(null);
|
||||
|
||||
// object-URL sources for images
|
||||
const [photoUrl, setPhotoUrl] = useState<string | null>(null);
|
||||
const [screenUrl, setScreenUrl] = useState<string | null>(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<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]);
|
||||
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 <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 (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">
|
||||
|
|
@ -227,11 +211,17 @@ export default function CO2GalvoDetail({
|
|||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
<SettingsSubmit mode="edit" initialTarget="settings_co2gal" submissionId={rec.submission_id} initialValues={initialValues} />
|
||||
<SettingsSubmit
|
||||
mode="edit"
|
||||
initialTarget="settings_co2gal"
|
||||
submissionId={rec.submission_id}
|
||||
initialValues={initialValues}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
// VIEW mode
|
||||
const ownerDisplay = ownerLabel(rec.owner);
|
||||
const canEdit = showOwnerEdit && isMine(rec.owner, meId);
|
||||
|
||||
|
|
@ -243,7 +233,7 @@ export default function CO2GalvoDetail({
|
|||
</header>
|
||||
|
||||
<section className="grid md:grid-cols-2 gap-6">
|
||||
{/* Left meta */}
|
||||
{/* Meta */}
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm">
|
||||
<div><span className="font-medium">Owner:</span> {ownerDisplay}</div>
|
||||
|
|
@ -282,24 +272,45 @@ export default function CO2GalvoDetail({
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* Right images: compact 1:1 thumbs + lightbox */}
|
||||
{/* Images: 1:1 thumbs + click to enlarge; only render when we have a URL */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{photoUrl && (
|
||||
{photoUrl ? (
|
||||
<figure className="border rounded overflow-hidden">
|
||||
<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
|
||||
type="button"
|
||||
onClick={() => setLightbox({ src: photoUrl, alt: "Result" })}
|
||||
className="block w-full max-w-[320px] aspect-square"
|
||||
>
|
||||
<img
|
||||
src={photoUrl}
|
||||
alt="Result"
|
||||
className="w-full h-full object-cover"
|
||||
loading="lazy"
|
||||
onError={(e) => { (e.currentTarget as HTMLImageElement).style.display = "none"; }}
|
||||
/>
|
||||
</button>
|
||||
<figcaption className="text-xs p-1 text-muted-foreground">Result</figcaption>
|
||||
</figure>
|
||||
)}
|
||||
{screenUrl && (
|
||||
) : null}
|
||||
|
||||
{screenUrl ? (
|
||||
<figure className="border rounded overflow-hidden">
|
||||
<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
|
||||
type="button"
|
||||
onClick={() => setLightbox({ src: screenUrl, alt: "Settings Screenshot" })}
|
||||
className="block w-full max-w-[320px] aspect-square"
|
||||
>
|
||||
<img
|
||||
src={screenUrl}
|
||||
alt="Settings Screenshot"
|
||||
className="w-full h-full object-cover"
|
||||
loading="lazy"
|
||||
onError={(e) => { (e.currentTarget as HTMLImageElement).style.display = "none"; }}
|
||||
/>
|
||||
</button>
|
||||
<figcaption className="text-xs p-1 text-muted-foreground">Settings Screenshot</figcaption>
|
||||
</figure>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -409,7 +420,6 @@ export default function CO2GalvoDetail({
|
|||
</section>
|
||||
)}
|
||||
|
||||
{/* Lightbox */}
|
||||
{lightbox && (
|
||||
<div
|
||||
className="fixed inset-0 z-50 bg-black/80 flex items-center justify-center p-4"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue