diff --git a/src/app/admin/import/furni/nitro-editor-dialog.tsx b/src/app/admin/import/furni/nitro-editor-dialog.tsx index db5de15a64..719562de38 100644 --- a/src/app/admin/import/furni/nitro-editor-dialog.tsx +++ b/src/app/admin/import/furni/nitro-editor-dialog.tsx @@ -39,28 +39,56 @@ interface NitroEditorDialogProps { onOpenChange: (open: boolean) => void; } -// Keep these helpers on `any` — Next's build-time checker has repeatedly -// disagreed with `tsc` on stricter JSON typings for this editor. -// biome-ignore lint/suspicious/noExplicitAny: metadata tree is untyped JSON -function updateNested(obj: any, path: string, value: unknown): any { - const clone = JSON.parse(JSON.stringify(obj)); +interface NitroVisualization { + size?: number; + layers?: Record; + colors?: Record>; + animations?: Record; +} + +// The .nitro metadata is untyped JSON from disk; typed only for the fields the +// editor reads/writes, with an index signature so the generic path helpers can +// traverse arbitrary nested keys. +interface NitroMetadata { + visualizationType?: string; + logicType?: string; + logic?: { + model?: { + dimensions?: { x: number; y: number; z: number }; + directions?: number[]; + }; + }; + visualizations?: NitroVisualization[]; + [key: string]: unknown; +} + +type JsonObject = Record; + +function updateNested( + obj: T, + path: string, + value: unknown, +): T { + const clone: T = JSON.parse(JSON.stringify(obj)); const keys = path.split("."); - let current = clone; + let current: JsonObject = clone; for (let i = 0; i < keys.length - 1; i++) { - if (!current[keys[i]]) current[keys[i]] = {}; - current = current[keys[i]]; + const next = current[keys[i]]; + if (!next || typeof next !== "object" || Array.isArray(next)) { + current[keys[i]] = {}; + } + current = current[keys[i]] as JsonObject; } current[keys[keys.length - 1]] = value; return clone; } -// biome-ignore lint/suspicious/noExplicitAny: metadata tree is untyped JSON -function getNested(obj: any, path: string, fallback?: unknown): any { +function getNested(obj: unknown, path: string, fallback?: unknown): unknown { const keys = path.split("."); - let current = obj; + let current: unknown = obj; for (const key of keys) { if (current == null || typeof current !== "object") return fallback; - current = current[key]; + current = (current as JsonObject)[key]; } return current ?? fallback; } @@ -99,10 +127,9 @@ export function NitroEditorDialog({ open, onOpenChange, }: NitroEditorDialogProps) { - // biome-ignore lint/suspicious/noExplicitAny: explicitly chosen here, see surrounding code - const [metadata, setMetadata] = useState(null); - // biome-ignore lint/suspicious/noExplicitAny: explicitly chosen here, see surrounding code - const [originalMetadata, setOriginalMetadata] = useState(null); + const [metadata, setMetadata] = useState(null); + const [originalMetadata, setOriginalMetadata] = + useState(null); const [jsonText, setJsonText] = useState(""); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); @@ -120,10 +147,9 @@ export function NitroEditorDialog({ const [jsonError, setJsonError] = useState(null); const patchMeta = (path: string, value: unknown) => { - // Explicit any avoids noImplicitAny on updater params and dodges - // Next build's stricter JSON inference that disagreed with tsc. - // biome-ignore lint/suspicious/noExplicitAny: metadata tree is untyped JSON - setMetadata((prev: any) => updateNested(prev, path, value)); + setMetadata((prev) => + prev == null ? prev : updateNested(prev, path, value), + ); }; const fetchMetadata = useCallback(async () => { @@ -258,12 +284,9 @@ export function NitroEditorDialog({ }; // Get visualization with size=64 for layer/color editing - // biome-ignore lint/suspicious/noExplicitAny: explicitly chosen here, see surrounding code - const viz64 = metadata?.visualizations?.find((v: any) => v.size === 64); + const viz64 = metadata?.visualizations?.find((v) => v.size === 64); const viz64Index = - metadata?.visualizations?.findIndex( - (v: { size: number }) => v.size === 64, - ) ?? -1; + metadata?.visualizations?.findIndex((v) => v.size === 64) ?? -1; const layerCount = viz64?.layers ? Object.keys(viz64.layers).length : 0; const colorCount = viz64?.colors ? Object.keys(viz64.colors).length : 0;