feat(catalog): inspect Nitro scales and preview original sprites
This commit is contained in:
1 parent
2619bec165
commit
08b0b5021e
6 files changed
+544
-3
No files matched your search
@@ -0,0 +1,147 @@
|
||||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { adminFetch } from "@/lib/admin-fetch";
|
||||
import type { NitroQuality, ScaleReport } from "@/lib/furni/nitro-quality";
|
||||
|
||||
function ScalePanel({
|
||||
report,
|
||||
classname,
|
||||
revision,
|
||||
}: {
|
||||
report: ScaleReport;
|
||||
classname: string;
|
||||
revision: number;
|
||||
}) {
|
||||
const [asset, setAsset] = useState(report.assets[0] ?? ""),
|
||||
[failed, setFailed] = useState(false);
|
||||
return (
|
||||
<section className="min-w-0 space-y-3 rounded-lg border p-3">
|
||||
<h3 className="font-semibold">
|
||||
Scale {report.size} ·{" "}
|
||||
{report.state === "missing"
|
||||
? "Not included"
|
||||
: report.state === "issues"
|
||||
? "Needs review"
|
||||
: "Present"}
|
||||
</h3>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{report.assets.length} sprites · {report.animations.length} animation
|
||||
definitions · directions {report.directions.join(", ") || "—"}
|
||||
</p>
|
||||
{report.state === "missing" && (
|
||||
<p className="text-sm">
|
||||
This bundle contains no sprites at this scale. Missing scale 32 does
|
||||
not by itself mean the furniture is unusable.
|
||||
</p>
|
||||
)}
|
||||
{report.assets.length > 0 && (
|
||||
<>
|
||||
<label className="block text-sm">
|
||||
Sprite
|
||||
<select
|
||||
className="mt-1 block w-full rounded border bg-background p-2"
|
||||
value={asset}
|
||||
onChange={(event) => {
|
||||
setAsset(event.target.value);
|
||||
setFailed(false);
|
||||
}}
|
||||
>
|
||||
{report.assets.map((name) => (
|
||||
<option key={name} value={name}>
|
||||
{name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<div className="flex h-56 items-center justify-center overflow-auto rounded border bg-muted/30">
|
||||
{failed ? (
|
||||
<p role="alert">This sprite could not be rendered.</p>
|
||||
) : (
|
||||
<img
|
||||
key={`${asset}-${revision}`}
|
||||
src={`/api/admin/studio/nitro-quality?classname=${encodeURIComponent(classname)}&asset=${encodeURIComponent(asset)}&v=${revision}`}
|
||||
alt={asset}
|
||||
style={{
|
||||
imageRendering: "pixelated",
|
||||
maxWidth: "none",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
onError={() => setFailed(true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{report.issues.length > 0 && (
|
||||
<ul className="max-h-40 overflow-auto space-y-2 text-xs">
|
||||
{report.issues.map((issue) => (
|
||||
<li key={`${issue.asset ?? ""}-${issue.message}`}>
|
||||
<strong className="break-all">{issue.asset}</strong>{" "}
|
||||
{issue.message}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
export function NitroQualityPanel({ classname }: { classname: string }) {
|
||||
const [report, setReport] = useState<NitroQuality | null>(null),
|
||||
[error, setError] = useState(""),
|
||||
[revision, setRevision] = useState(0);
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
setReport(null);
|
||||
setError("");
|
||||
void (async () => {
|
||||
try {
|
||||
const response = await adminFetch(
|
||||
`/api/admin/studio/nitro-quality?classname=${encodeURIComponent(classname)}&v=${revision}`,
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
const data = await response.json();
|
||||
if (!response.ok) throw Error(data.error || "Inspection failed");
|
||||
if (!controller.signal.aborted) setReport(data.report);
|
||||
} catch (error) {
|
||||
if (!controller.signal.aborted)
|
||||
setError(
|
||||
error instanceof Error ? error.message : "Inspection failed",
|
||||
);
|
||||
}
|
||||
})();
|
||||
return () => controller.abort();
|
||||
}, [classname, revision]);
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<p className="text-sm">
|
||||
Inspect the saved bundle at both scales. Previews show individual
|
||||
sprites, not the assembled furniture or animation playback.
|
||||
</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setRevision((value) => value + 1)}
|
||||
>
|
||||
Recheck
|
||||
</Button>
|
||||
</div>
|
||||
{error ? (
|
||||
<p role="alert">{error}</p>
|
||||
) : !report ? (
|
||||
<p role="status">Checking scales and texture references…</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{report.scales.map((scale) => (
|
||||
<ScalePanel
|
||||
key={`${scale.size}-${revision}`}
|
||||
report={scale}
|
||||
classname={classname}
|
||||
revision={revision}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user