feat(catalog): generate derived scale 32 with preview and backup restore
CI / check (push) Successful in 1m32s
CI / deploy (push) Successful in 1m12s
CI / e2e (push) Successful in 21s

This commit is contained in:
Simo committed 2026-09-05 17:36:22 +02:00
1 parent 08b0b5021e
commit cd75361b9f
9 files changed
+749 -11

No files matched your search

@@ -19,6 +19,7 @@ import {
import { useCallback, useEffect, useState } from "react";
import { toast } from "sonner";
import { NitroQualityPanel } from "@/components/admin/studio/nitro-quality-panel";
import { NitroScale32Generator } from "@/components/admin/studio/nitro-scale32-generator";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
@@ -140,6 +141,7 @@ export function NitroEditorDialog({
const [jsonText, setJsonText] = useState("");
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [bundleRevision, setBundleRevision] = useState(0);
const [activeTab, setActiveTab] = useState<string>("visual");
const [flags, setFlags] = useState({
canstandon: false,
@@ -443,7 +445,19 @@ export function NitroEditorDialog({
value="quality"
className="min-h-0 overflow-y-auto flex-1 px-4 sm:px-6 py-4"
>
<NitroQualityPanel classname={classname} />
<NitroScale32Generator
classname={classname}
disabled={hasChanges || saving}
onChanged={(updated) => {
setBundleRevision((value) => value + 1);
setMetadata(updated as NitroMetadata);
setOriginalMetadata(
structuredClone(updated) as NitroMetadata,
);
setJsonText(JSON.stringify(updated, null, 2));
}}
/>
<NitroQualityPanel key={bundleRevision} classname={classname} />
</TabsContent>
{/* ── Visual Tab ── */}
<TabsContent
@@ -105,15 +105,23 @@ export const PUT = withAdmin(
if (!existsSync(/*turbopackIgnore: true*/ nitroPath))
return apiError("Nitro file not found", 404);
// 1. Read existing .nitro to extract PNG
const existingBuffer = await fs.readFile(
/*turbopackIgnore: true*/ nitroPath,
);
const { png } = parseNitroBundle(existingBuffer);
// 2. Rebuild .nitro with updated JSON + same PNG
const newBundle = createNitroBundle(metadata, png, base);
await fs.writeFile(nitroPath, newBundle);
const saved = await withFurniDataLock(async () => {
const existingBuffer = await fs.readFile(nitroPath);
const { png, json: currentMetadata } = parseNitroBundle(existingBuffer);
if (
JSON.stringify(currentMetadata.catalogStudio) !==
JSON.stringify(metadata.catalogStudio)
)
return false;
const newBundle = createNitroBundle(metadata, png, base);
await fs.writeFile(nitroPath, newBundle);
return true;
});
if (!saved)
return apiError(
"The bundle changed. Reopen the editor before saving.",
409,
);
// 3. Extract dimensions from metadata
const dims = metadata.logic?.model?.dimensions || { x: 1, y: 1, z: 0 };
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -23,7 +23,9 @@ function ScalePanel({
? "Not included"
: report.state === "issues"
? "Needs review"
: "Present"}
: report.derived
? "Derived from 64"
: "Present"}
</h3>
<p className="text-xs text-muted-foreground">
{report.assets.length} sprites · {report.animations.length} animation
File diff suppressed because it is too large. Load diff
+4
View File
@@ -4,6 +4,7 @@ export interface ScaleIssue {
}
export interface ScaleReport {
size: 32 | 64;
derived?: boolean;
state: "missing" | "issues" | "present";
assets: string[];
animations: string[];
@@ -178,6 +179,9 @@ export function inspectNitroScales(
].sort();
return {
size,
derived:
size === 32 &&
object(object(metadata.catalogStudio).scale32).derivedFrom === 64,
state: issues.length ? "issues" : names.length ? "present" : "missing",
assets: names,
animations,
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff