feat: RCON API transport, full furnidata translation with LibreTranslate fallback, remove organize feature, bump per-page limit to 500
CI / check (push) Failing after 7s
CI / release (push) Skipped
CI / deploy (push) Skipped

- Add EMULATOR_MODE=api option with HTTP transport mirroring RCON payload
- buildLocalizedFurniDataFiles now falls back to LibreTranslate for customs (incl. pl/ru/ar/ja)
- Remove Organiseer import button from studio and Reorganize Catalog from import page
- Delete organize API route and PUT handler; remove organizeCatalogAll from catalog-repair.ts
- Increase imported-furni list perPage from 50 to 500
This commit is contained in:
openhands committed 2026-08-22 14:28:06 +02:00
1 parent 5668493485
commit 85b5792d40
8 files changed
+146 -768

No files matched your search

@@ -10,7 +10,6 @@ import {
Download,
ExternalLink,
FileWarning,
FolderSync,
Globe2,
ImageDown,
LayoutGrid,
@@ -141,8 +140,6 @@ export function ImportFurniClient({ source }: { source: FurniImportSource }) {
// Nitro editor
const [editingClassname, setEditingClassname] = useState<string | null>(null);
// Reorganize catalog
const [reorganizing, setReorganizing] = useState(false);
const [confirmRecentResync, setConfirmRecentResync] = useState(false);
const [recentResyncing, setRecentResyncing] = useState(false);
const [recentResyncResult, setRecentResyncResult] =
@@ -157,13 +154,6 @@ export function ImportFurniClient({ source }: { source: FurniImportSource }) {
// Confirmation dialogs
const [confirmBatch, setConfirmBatch] = useState(false);
const [confirmDelete, setConfirmDelete] = useState<FurniItem | null>(null);
const [reorgPreview, setReorgPreview] = useState<{
moved: number;
repriced: number;
skipped: number;
total: number;
categoryCounts: Record<string, number>;
} | null>(null);
const fetchStats = useCallback(async () => {
try {
@@ -699,31 +689,6 @@ export function ImportFurniClient({ source }: { source: FurniImportSource }) {
fetchStats();
}
async function startReorganize() {
setReorganizing(true);
try {
const previewRes = await adminFetch("/api/admin/import/furni?dryrun=1", {
method: "PUT",
});
const previewData = await previewRes.json();
if (!previewRes.ok) {
toast.error(previewData.error || "Preview failed");
return;
}
if (previewData.moved === 0 && previewData.repriced === 0) {
toast.info(
previewData.message || "Everything is already correctly organized",
);
return;
}
setReorgPreview(previewData);
} catch {
toast.error("Network error");
} finally {
setReorganizing(false);
}
}
async function resyncRecentImports() {
setConfirmRecentResync(false);
setRecentResyncing(true);
@@ -809,32 +774,6 @@ export function ImportFurniClient({ source }: { source: FurniImportSource }) {
}
}
async function applyReorganize() {
setReorgPreview(null);
setReorganizing(true);
try {
const res = await adminFetch("/api/admin/import/furni", {
method: "PUT",
});
const d = await res.json();
if (res.ok) {
const parts: string[] = [];
if (d.moved > 0) parts.push(`${d.moved} moved`);
if (d.repriced > 0) parts.push(`${d.repriced} repriced`);
if (d.skipped > 0) parts.push(`${d.skipped} already ok`);
if (d.pagesDeleted > 0)
parts.push(`${d.pagesDeleted} empty pages cleaned`);
toast.success(`Done! ${parts.join(", ")} (${d.total} total)`);
} else {
toast.error(d.error || "Failed");
}
} catch {
toast.error("Network error");
} finally {
setReorganizing(false);
}
}
async function doDeleteItem(item: FurniItem) {
setConfirmDelete(null);
setDeletingItem(item.classname);
@@ -1581,10 +1520,6 @@ export function ImportFurniClient({ source }: { source: FurniImportSource }) {
? "Updating imported furni..."
: "Update imported furni"}
</DropdownMenuItem>
<DropdownMenuItem onClick={startReorganize} disabled={reorganizing}>
<FolderSync className="w-4 h-4 mr-2" />
{reorganizing ? "Reorganizing..." : "Reorganize Catalog"}
</DropdownMenuItem>
<DropdownMenuItem onClick={addSize32All} disabled={addingSize32}>
<ImageDown className="w-4 h-4 mr-2" />
{addingSize32 ? "Adding size 32..." : "Add size 32 to all .nitro"}
@@ -2082,82 +2017,6 @@ export function ImportFurniClient({ source }: { source: FurniImportSource }) {
</DialogFooter>
</DialogContent>
</Dialog>
{/* Reorganize Preview */}
<Dialog open={!!reorgPreview} onOpenChange={() => setReorgPreview(null)}>
<DialogContent>
<DialogHeader>
<DialogTitle>Reorganize Catalog</DialogTitle>
<DialogDescription>
Preview of changes to {reorgPreview?.total} imported items:
</DialogDescription>
</DialogHeader>
{reorgPreview && (
<div className="space-y-3 text-sm">
<div className="grid grid-cols-3 gap-3">
{reorgPreview.moved > 0 && (
<div className="rounded-lg border p-3 text-center">
<p className="text-lg font-bold text-primary">
{reorgPreview.moved}
</p>
<p className="text-xs text-muted-foreground">
Items to move
</p>
</div>
)}
{reorgPreview.repriced > 0 && (
<div className="rounded-lg border p-3 text-center">
<p className="text-lg font-bold text-[var(--admin-warning)]">
{reorgPreview.repriced}
</p>
<p className="text-xs text-muted-foreground">
Prices to update
</p>
</div>
)}
{reorgPreview.skipped > 0 && (
<div className="rounded-lg border p-3 text-center">
<p className="text-lg font-bold text-muted-foreground">
{reorgPreview.skipped}
</p>
<p className="text-xs text-muted-foreground">
Already correct
</p>
</div>
)}
</div>
{Object.keys(reorgPreview.categoryCounts || {}).length > 0 && (
<div>
<p className="text-xs font-medium text-muted-foreground mb-1">
Categories:
</p>
<div className="flex flex-wrap gap-1">
{Object.entries(reorgPreview.categoryCounts)
.sort(([, a], [, b]) => b - a)
.map(([cat, count]) => (
<Badge
key={cat}
variant="secondary"
className="text-xs"
>
{cat}: {count}
</Badge>
))}
</div>
</div>
)}
</div>
)}
<DialogFooter>
<Button variant="outline" onClick={() => setReorgPreview(null)}>
Cancel
</Button>
<Button onClick={applyReorganize}>
<FolderSync className="w-4 h-4 mr-1" /> Apply Changes
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}