revert fb8e77bb68
Local Build and Deploy / deploy (push) Successful in 1m11s

revert style: clean up code with prettier and eslint
This commit is contained in:
remco committed 2026-07-12 21:02:03 +02:00
1 parent 27de078f54
commit e85e4d74ea
378 files changed
+20710 -22428

No files matched your search

File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -1,4 +1,4 @@
import { Skeleton } from "@/components/ui/skeleton";
import { Skeleton } from '@/components/ui/skeleton'
export default function ImportFurniLoading() {
return (
@@ -17,5 +17,5 @@ export default function ImportFurniLoading() {
<Skeleton className="h-10 w-24 mt-4" />
</div>
</div>
);
)
}
+215 -184
View File
@@ -1,4 +1,4 @@
"use client";
'use client'
import {
Box,
@@ -14,65 +14,71 @@ import {
Ruler,
Save,
Sparkles,
} from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { toast } from "sonner";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea";
} from 'lucide-react'
import { useCallback, useEffect, useState } from 'react'
import { toast } from 'sonner'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Switch } from '@/components/ui/switch'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Textarea } from '@/components/ui/textarea'
interface NitroEditorDialogProps {
classname: string;
open: boolean;
onOpenChange: (open: boolean) => void;
classname: string
open: boolean
onOpenChange: (open: boolean) => void
}
// biome-ignore lint/suspicious/noExplicitAny: explicitly chosen here, see surrounding code
function updateNested(obj: any, path: string, value: unknown): any {
const clone = JSON.parse(JSON.stringify(obj));
const keys = path.split(".");
let current = clone;
const clone = JSON.parse(JSON.stringify(obj))
const keys = path.split('.')
let current = clone
for (let i = 0; i < keys.length - 1; i++) {
if (!current[keys[i]]) current[keys[i]] = {};
current = current[keys[i]];
if (!current[keys[i]]) current[keys[i]] = {}
current = current[keys[i]]
}
current[keys[keys.length - 1]] = value;
return clone;
current[keys[keys.length - 1]] = value
return clone
}
// biome-ignore lint/suspicious/noExplicitAny: explicitly chosen here, see surrounding code
function getNested(obj: any, path: string, fallback?: unknown): any {
const keys = path.split(".");
let current = obj;
const keys = path.split('.')
let current = obj
for (const key of keys) {
if (current == null || typeof current !== "object") return fallback;
current = current[key];
if (current == null || typeof current !== 'object') return fallback
current = current[key]
}
return current ?? fallback;
return current ?? fallback
}
const ALL_DIRECTIONS = [0, 1, 2, 3, 4, 5, 6, 7];
const DIR_LABELS = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"];
const ALL_DIRECTIONS = [0, 1, 2, 3, 4, 5, 6, 7]
const DIR_LABELS = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']
function hexToColor(hex: string): string {
return `#${hex.padStart(6, "0")}`;
return `#${hex.padStart(6, '0')}`
}
function CopyClassname({ value }: { value: string }) {
const [copied, setCopied] = useState(false);
const [copied, setCopied] = useState(false)
return (
<button
type="button"
className="flex items-center gap-1.5 font-mono text-sm text-muted-foreground mt-0.5 hover:text-foreground transition-colors"
onClick={() => {
navigator.clipboard.writeText(value);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
navigator.clipboard.writeText(value)
setCopied(true)
setTimeout(() => setCopied(false), 1500)
}}
>
<span className="truncate">{value}</span>
@@ -82,177 +88,178 @@ function CopyClassname({ value }: { value: string }) {
<Copy className="w-3 h-3 shrink-0" />
)}
</button>
);
)
}
export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditorDialogProps) {
// biome-ignore lint/suspicious/noExplicitAny: explicitly chosen here, see surrounding code
const [metadata, setMetadata] = useState<any>(null);
const [metadata, setMetadata] = useState<any>(null)
// biome-ignore lint/suspicious/noExplicitAny: explicitly chosen here, see surrounding code
const [originalMetadata, setOriginalMetadata] = useState<any>(null);
const [jsonText, setJsonText] = useState("");
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [activeTab, setActiveTab] = useState<string>("visual");
const [flags, setFlags] = useState({ canstandon: false, cansiton: false, canlayon: false });
const [originalMetadata, setOriginalMetadata] = useState<any>(null)
const [jsonText, setJsonText] = useState('')
const [loading, setLoading] = useState(false)
const [saving, setSaving] = useState(false)
const [activeTab, setActiveTab] = useState<string>('visual')
const [flags, setFlags] = useState({ canstandon: false, cansiton: false, canlayon: false })
const [originalFlags, setOriginalFlags] = useState({
canstandon: false,
cansiton: false,
canlayon: false,
});
const [jsonError, setJsonError] = useState<string | null>(null);
})
const [jsonError, setJsonError] = useState<string | null>(null)
const fetchMetadata = useCallback(async () => {
if (!classname) return;
setLoading(true);
setJsonError(null);
if (!classname) return
setLoading(true)
setJsonError(null)
try {
const res = await fetch(
`/api/admin/import/furni/nitro-editor?classname=${encodeURIComponent(classname)}`,
);
)
if (!res.ok) {
const err = await res.json().catch(() => ({}));
toast.error(err.error || "Failed to load .nitro metadata");
onOpenChange(false);
return;
const err = await res.json().catch(() => ({}))
toast.error(err.error || 'Failed to load .nitro metadata')
onOpenChange(false)
return
}
const data = await res.json();
setMetadata(data.metadata);
setOriginalMetadata(JSON.parse(JSON.stringify(data.metadata)));
setJsonText(JSON.stringify(data.metadata, null, 2));
setFlags(data.flags);
setOriginalFlags({ ...data.flags });
const data = await res.json()
setMetadata(data.metadata)
setOriginalMetadata(JSON.parse(JSON.stringify(data.metadata)))
setJsonText(JSON.stringify(data.metadata, null, 2))
setFlags(data.flags)
setOriginalFlags({ ...data.flags })
} catch {
toast.error("Network error loading .nitro metadata");
onOpenChange(false);
toast.error('Network error loading .nitro metadata')
onOpenChange(false)
} finally {
setLoading(false);
setLoading(false)
}
}, [classname, onOpenChange]);
}, [classname, onOpenChange])
useEffect(() => {
if (open && classname) {
setActiveTab("visual");
fetchMetadata();
setActiveTab('visual')
fetchMetadata()
}
}, [open, classname, fetchMetadata]);
}, [open, classname, fetchMetadata])
const handleTabChange = (newTab: string) => {
if (activeTab === "json" && newTab === "visual") {
if (activeTab === 'json' && newTab === 'visual') {
try {
const parsed = JSON.parse(jsonText);
setMetadata(parsed);
setJsonError(null);
const parsed = JSON.parse(jsonText)
setMetadata(parsed)
setJsonError(null)
} catch {
toast.error("Invalid JSON — fix it before switching to Visual tab");
return;
toast.error('Invalid JSON — fix it before switching to Visual tab')
return
}
} else if (activeTab === "visual" && newTab === "json") {
setJsonText(JSON.stringify(metadata, null, 2));
} else if (activeTab === 'visual' && newTab === 'json') {
setJsonText(JSON.stringify(metadata, null, 2))
}
setActiveTab(newTab);
};
setActiveTab(newTab)
}
const handleReset = () => {
if (!originalMetadata) return;
setMetadata(JSON.parse(JSON.stringify(originalMetadata)));
setFlags({ ...originalFlags });
setJsonText(JSON.stringify(originalMetadata, null, 2));
toast.info("Reset to original values");
};
if (!originalMetadata) return
setMetadata(JSON.parse(JSON.stringify(originalMetadata)))
setFlags({ ...originalFlags })
setJsonText(JSON.stringify(originalMetadata, null, 2))
toast.info('Reset to original values')
}
const handleSave = async () => {
let metadataToSave = metadata;
let metadataToSave = metadata
if (activeTab === "json") {
if (activeTab === 'json') {
try {
metadataToSave = JSON.parse(jsonText);
metadataToSave = JSON.parse(jsonText)
} catch {
toast.error("Invalid JSON");
return;
toast.error('Invalid JSON')
return
}
}
setSaving(true);
setSaving(true)
try {
const res = await fetch("/api/admin/import/furni/nitro-editor", {
method: "PUT",
headers: { "Content-Type": "application/json" },
const res = await fetch('/api/admin/import/furni/nitro-editor', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
classname,
metadata: metadataToSave,
...flags,
}),
});
})
if (!res.ok) {
const err = await res.json().catch(() => ({}));
toast.error(err.error || "Save failed");
return;
const err = await res.json().catch(() => ({}))
toast.error(err.error || 'Save failed')
return
}
toast.success("Nitro metadata salvato");
setMetadata(metadataToSave);
setOriginalMetadata(JSON.parse(JSON.stringify(metadataToSave)));
setOriginalFlags({ ...flags });
setJsonText(JSON.stringify(metadataToSave, null, 2));
toast.success('Nitro metadata salvato')
setMetadata(metadataToSave)
setOriginalMetadata(JSON.parse(JSON.stringify(metadataToSave)))
setOriginalFlags({ ...flags })
setJsonText(JSON.stringify(metadataToSave, null, 2))
} catch {
toast.error("Network error");
toast.error('Network error')
} finally {
setSaving(false);
setSaving(false)
}
};
}
// ── Visual tab helpers ──
const dims = metadata
? getNested(metadata, "logic.model.dimensions", { x: 1, y: 1, z: 0 })
: { x: 1, y: 1, z: 0 };
const directions: number[] = metadata ? getNested(metadata, "logic.model.directions", [0]) : [0];
? getNested(metadata, 'logic.model.dimensions', { x: 1, y: 1, z: 0 })
: { x: 1, y: 1, z: 0 }
const directions: number[] = metadata ? getNested(metadata, 'logic.model.directions', [0]) : [0]
const setDim = (axis: "x" | "y" | "z", value: string) => {
const num = axis === "z" ? parseFloat(value) || 0 : parseInt(value, 10) || 0;
setMetadata((prev: unknown) => updateNested(prev, `logic.model.dimensions.${axis}`, num));
};
const setDim = (axis: 'x' | 'y' | 'z', value: string) => {
const num = axis === 'z' ? parseFloat(value) || 0 : parseInt(value, 10) || 0
setMetadata((prev: unknown) => updateNested(prev, `logic.model.dimensions.${axis}`, num))
}
const toggleDirection = (dir: number) => {
const current: number[] = getNested(metadata, "logic.model.directions", [0]);
const current: number[] = getNested(metadata, 'logic.model.directions', [0])
const next = current.includes(dir)
? current.filter((d: number) => d !== dir)
: [...current, dir].sort((a: number, b: number) => a - b);
if (next.length === 0) return;
setMetadata((prev: unknown) => updateNested(prev, "logic.model.directions", next));
};
: [...current, dir].sort((a: number, b: number) => a - b)
if (next.length === 0) return
setMetadata((prev: unknown) => updateNested(prev, 'logic.model.directions', next))
}
// 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 viz64Index = metadata?.visualizations?.findIndex((v: { size: number }) => v.size === 64) ?? -1;
const viz64 = metadata?.visualizations?.find((v: any) => v.size === 64)
const viz64Index =
metadata?.visualizations?.findIndex((v: { size: number }) => v.size === 64) ?? -1
const layerCount = viz64?.layers ? Object.keys(viz64.layers).length : 0;
const colorCount = viz64?.colors ? Object.keys(viz64.colors).length : 0;
const animCount = viz64?.animations ? Object.keys(viz64.animations).length : 0;
const layerCount = viz64?.layers ? Object.keys(viz64.layers).length : 0
const colorCount = viz64?.colors ? Object.keys(viz64.colors).length : 0
const animCount = viz64?.animations ? Object.keys(viz64.animations).length : 0
const handleJsonTextChange = (value: string) => {
setJsonText(value);
setJsonText(value)
try {
JSON.parse(value);
setJsonError(null);
JSON.parse(value)
setJsonError(null)
} catch (e) {
setJsonError((e as Error).message);
setJsonError((e as Error).message)
}
};
}
// Check if there are unsaved changes
const hasChanges =
metadata &&
originalMetadata &&
(JSON.stringify(metadata) !== JSON.stringify(originalMetadata) ||
JSON.stringify(flags) !== JSON.stringify(originalFlags));
JSON.stringify(flags) !== JSON.stringify(originalFlags))
// Furni preview URL
const starIdx = classname.indexOf("*");
const baseClassname = starIdx !== -1 ? classname.substring(0, starIdx) : classname;
const starIdx = classname.indexOf('*')
const baseClassname = starIdx !== -1 ? classname.substring(0, starIdx) : classname
return (
<Dialog open={open} onOpenChange={onOpenChange}>
@@ -264,18 +271,18 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
<div
className="w-20 h-20 rounded-lg border bg-card flex items-center justify-center shrink-0 overflow-hidden"
style={{
backgroundImage: "url(/images/furni_floor.png)",
backgroundRepeat: "repeat",
backgroundPosition: "center",
backgroundImage: 'url(/images/furni_floor.png)',
backgroundRepeat: 'repeat',
backgroundPosition: 'center',
}}
>
<img
src={`/swf/dcr/hof_furni/icons/${baseClassname}_icon.png`}
alt={classname}
className="max-w-[60px] max-h-[60px] drop-shadow-md"
style={{ imageRendering: "pixelated" }}
style={{ imageRendering: 'pixelated' }}
onError={(e) => {
(e.target as HTMLImageElement).style.opacity = "0";
;(e.target as HTMLImageElement).style.opacity = '0'
}}
/>
</div>
@@ -311,7 +318,7 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
)}
<Badge variant="outline" className="text-[10px] gap-1">
<Eye className="w-2.5 h-2.5" />
{metadata.visualizationType || "?"}
{metadata.visualizationType || '?'}
</Badge>
</div>
)}
@@ -324,7 +331,11 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
<Loader2 className="w-6 h-6 animate-spin text-muted-foreground" />
</div>
) : metadata ? (
<Tabs value={activeTab} onValueChange={handleTabChange} className="flex-1 flex flex-col min-h-0">
<Tabs
value={activeTab}
onValueChange={handleTabChange}
className="flex-1 flex flex-col min-h-0"
>
<div className="px-6 pt-3 pb-0">
<TabsList className="w-fit">
<TabsTrigger value="visual" className="gap-1.5">
@@ -332,7 +343,7 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
Visual
</TabsTrigger>
<TabsTrigger value="json" className="gap-1.5">
{"{ }"}
{'{ }'}
JSON
</TabsTrigger>
</TabsList>
@@ -358,7 +369,7 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
type="number"
min={1}
value={dims.x}
onChange={(e) => setDim("x", e.target.value)}
onChange={(e) => setDim('x', e.target.value)}
className="mt-1"
/>
</div>
@@ -371,7 +382,7 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
type="number"
min={1}
value={dims.y}
onChange={(e) => setDim("y", e.target.value)}
onChange={(e) => setDim('y', e.target.value)}
className="mt-1"
/>
</div>
@@ -385,7 +396,7 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
step={0.01}
min={0}
value={dims.z}
onChange={(e) => setDim("z", e.target.value)}
onChange={(e) => setDim('z', e.target.value)}
className="mt-1"
/>
</div>
@@ -429,7 +440,9 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
<div className="flex items-center gap-2 mb-3">
<Compass className="w-4 h-4 text-muted-foreground" />
<h3 className="text-sm font-medium">Direzioni</h3>
<span className="text-xs text-muted-foreground ml-auto">{directions.length} attive</span>
<span className="text-xs text-muted-foreground ml-auto">
{directions.length} attive
</span>
</div>
<div className="flex items-center justify-center">
<div className="relative w-48 h-48">
@@ -438,19 +451,19 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
<div className="absolute inset-4 rounded-full border border-muted/50" />
{/* Direction buttons arranged in circle */}
{ALL_DIRECTIONS.map((dir) => {
const angle = (dir * 45 - 90) * (Math.PI / 180);
const r = 80;
const x = 96 + r * Math.cos(angle) - 18;
const y = 96 + r * Math.sin(angle) - 18;
const isActive = directions.includes(dir);
const angle = (dir * 45 - 90) * (Math.PI / 180)
const r = 80
const x = 96 + r * Math.cos(angle) - 18
const y = 96 + r * Math.sin(angle) - 18
const isActive = directions.includes(dir)
return (
<button
type="button"
key={dir}
className={`absolute w-9 h-9 rounded-full border-2 flex flex-col items-center justify-center text-xs font-medium transition-all ${
isActive
? "bg-primary text-primary-foreground border-primary shadow-md scale-110"
: "bg-card text-muted-foreground border-muted hover:border-foreground/30 hover:scale-105"
? 'bg-primary text-primary-foreground border-primary shadow-md scale-110'
: 'bg-card text-muted-foreground border-muted hover:border-foreground/30 hover:scale-105'
}`}
style={{ left: x, top: y }}
onClick={() => toggleDirection(dir)}
@@ -458,11 +471,13 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
<span className="text-[9px] leading-none">{DIR_LABELS[dir]}</span>
<span className="text-[8px] leading-none opacity-60">{dir}</span>
</button>
);
)
})}
{/* Center label */}
<div className="absolute inset-0 flex items-center justify-center">
<span className="text-xs text-muted-foreground font-medium">{directions.length}/8</span>
<span className="text-xs text-muted-foreground font-medium">
{directions.length}/8
</span>
</div>
</div>
</div>
@@ -481,10 +496,10 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
</Label>
<Input
id="viz-type"
value={metadata.visualizationType || ""}
value={metadata.visualizationType || ''}
onChange={(e) =>
setMetadata((prev: unknown) =>
updateNested(prev, "visualizationType", e.target.value),
updateNested(prev, 'visualizationType', e.target.value),
)
}
className="mt-1 font-mono text-sm"
@@ -496,9 +511,11 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
</Label>
<Input
id="logic-type"
value={metadata.logicType || ""}
value={metadata.logicType || ''}
onChange={(e) =>
setMetadata((prev: unknown) => updateNested(prev, "logicType", e.target.value))
setMetadata((prev: unknown) =>
updateNested(prev, 'logicType', e.target.value),
)
}
className="mt-1 font-mono text-sm"
/>
@@ -525,12 +542,12 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
<span>Ink</span>
</div>
{Object.entries(viz64.layers).map(([layerId, layerData], idx) => {
const layer = layerData as Record<string, unknown>;
const layer = layerData as Record<string, unknown>
return (
<div
key={layerId}
className={`grid grid-cols-4 gap-2 items-center text-sm px-3 py-1.5 ${
idx % 2 === 0 ? "bg-card" : "bg-muted/20"
idx % 2 === 0 ? 'bg-card' : 'bg-muted/20'
}`}
>
<span className="font-mono text-xs">
@@ -549,7 +566,7 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
`visualizations.${viz64Index}.layers.${layerId}.z`,
parseInt(e.target.value, 10) || 0,
),
);
)
}}
/>
<Input
@@ -565,12 +582,12 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
`visualizations.${viz64Index}.layers.${layerId}.alpha`,
parseInt(e.target.value, 10) || 0,
),
);
)
}}
/>
<Input
className="h-7 text-xs font-mono"
value={(layer.ink as string) ?? ""}
value={(layer.ink as string) ?? ''}
placeholder="—"
onChange={(e) => {
setMetadata((prev: unknown) =>
@@ -579,11 +596,11 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
`visualizations.${viz64Index}.layers.${layerId}.ink`,
e.target.value || undefined,
),
);
)
}}
/>
</div>
);
)
})}
</div>
</section>
@@ -606,20 +623,22 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
<span className="text-xs font-medium">Variante #{colorId}</span>
{/* Color preview dots */}
<div className="flex gap-1 ml-auto">
{Object.values(colorLayers as Record<string, { color: number }>).map((lc, i) => (
<div
// biome-ignore lint/suspicious/noArrayIndexKey: color preview dots, position is the identity
key={i}
className="w-4 h-4 rounded-full border border-border shadow-sm"
style={{
backgroundColor: hexToColor(
typeof lc.color === "number"
? lc.color.toString(16).padStart(6, "0")
: "000000",
),
}}
/>
))}
{Object.values(colorLayers as Record<string, { color: number }>).map(
(lc, i) => (
<div
// biome-ignore lint/suspicious/noArrayIndexKey: color preview dots, position is the identity
key={i}
className="w-4 h-4 rounded-full border border-border shadow-sm"
style={{
backgroundColor: hexToColor(
typeof lc.color === 'number'
? lc.color.toString(16).padStart(6, '0')
: '000000',
),
}}
/>
),
)}
</div>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
@@ -633,20 +652,25 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
<Input
className="h-7 text-xs font-mono pl-8"
value={
typeof layerColor.color === "number"
? layerColor.color.toString(16).padStart(6, "0").toUpperCase()
typeof layerColor.color === 'number'
? layerColor.color
.toString(16)
.padStart(6, '0')
.toUpperCase()
: String(layerColor.color)
}
onChange={(e) => {
const hex = e.target.value.replace(/[^0-9a-fA-F]/g, "").slice(0, 6);
const num = parseInt(hex, 16) || 0;
const hex = e.target.value
.replace(/[^0-9a-fA-F]/g, '')
.slice(0, 6)
const num = parseInt(hex, 16) || 0
setMetadata((prev: unknown) =>
updateNested(
prev,
`visualizations.${viz64Index}.colors.${colorId}.${layerId}.color`,
num,
),
);
)
}}
/>
{/* Color swatch inside input */}
@@ -654,9 +678,9 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
className="absolute left-1.5 top-1/2 -translate-y-1/2 w-4 h-4 rounded border border-border"
style={{
backgroundColor: hexToColor(
typeof layerColor.color === "number"
? layerColor.color.toString(16).padStart(6, "0")
: "000000",
typeof layerColor.color === 'number'
? layerColor.color.toString(16).padStart(6, '0')
: '000000',
),
}}
/>
@@ -694,7 +718,10 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
<DialogFooter className="px-6 py-4 border-t bg-muted/30">
<div className="flex items-center gap-2 w-full">
{hasChanges && (
<Badge variant="secondary" className="text-[10px] gap-1 animate-[pulse_1s_ease-in-out_1]">
<Badge
variant="secondary"
className="text-[10px] gap-1 animate-[pulse_1s_ease-in-out_1]"
>
Modifiche non salvate
</Badge>
)}
@@ -712,7 +739,11 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={saving}>
Annulla
</Button>
<Button onClick={handleSave} disabled={saving || loading || !metadata} className="gap-1.5">
<Button
onClick={handleSave}
disabled={saving || loading || !metadata}
className="gap-1.5"
>
{saving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
Salva
</Button>
@@ -720,5 +751,5 @@ export function NitroEditorDialog({ classname, open, onOpenChange }: NitroEditor
</DialogFooter>
</DialogContent>
</Dialog>
);
)
}
+6 -6
View File
@@ -1,12 +1,12 @@
import { redirect } from "next/navigation";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
import { ImportFurniClient } from "./import-furni-client";
import { redirect } from 'next/navigation'
import { canAccess, getAdminContext, PERMS } from '@/lib/permissions'
import { ImportFurniClient } from './import-furni-client'
export default async function ImportFurniPage(_props: { params: Promise<{ locale: string }> }) {
const { session, permissions } = await getAdminContext();
const { session, permissions } = await getAdminContext()
if (!canAccess(permissions, PERMS.ASSETS_IMPORT, session.user.rank)) {
redirect("/admin");
redirect('/admin')
}
return <ImportFurniClient />;
return <ImportFurniClient />
}