style: format code with prettier
Local Build and Deploy / deploy (push) Successful in 49s

This commit is contained in:
openhands committed 2026-07-12 21:07:34 +02:00
1 parent e85e4d74ea
commit 2e4ed76121
378 files changed
+22404 -20686

No files matched your search

+116 -124
View File
@@ -1,4 +1,4 @@
'use client'
"use client";
import {
ChevronLeft,
@@ -9,62 +9,56 @@ import {
RotateCcw,
Save,
Search,
} from 'lucide-react'
import { useRouter, useSearchParams } from 'next/navigation'
import { useEffect, useMemo, useState, useTransition } from 'react'
import { toast } from 'sonner'
import { saveEmulatorSettings } from '@/actions/emulator'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { useServerAction } from '@/hooks/use-server-action'
} from "lucide-react";
import { useRouter, useSearchParams } from "next/navigation";
import { useEffect, useMemo, useState, useTransition } from "react";
import { toast } from "sonner";
import { saveEmulatorSettings } from "@/actions/emulator";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useServerAction } from "@/hooks/use-server-action";
interface Setting {
key: string
value: string
key: string;
value: string;
}
interface GroupSummary {
name: string
count: number
name: string;
count: number;
}
interface EmulatorSettingsFormProps {
settings: Setting[]
pagination: { total: number; page: number; perPage: number; lastPage: number }
search: string
allGroups?: GroupSummary[]
activeGroup?: string
settings: Setting[];
pagination: { total: number; page: number; perPage: number; lastPage: number };
search: string;
allGroups?: GroupSummary[];
activeGroup?: string;
}
const PER_PAGE_OPTIONS = [25, 50, 100, 200] as const
const PER_PAGE_OPTIONS = [25, 50, 100, 200] as const;
function getSettingGroup(key: string): string {
const parts = key.split('.')
return parts.length > 1 ? parts[0] : 'general'
const parts = key.split(".");
return parts.length > 1 ? parts[0] : "general";
}
const GROUP_BORDER_COLORS: Record<string, string> = {
hotel: 'border-l-blue-500',
navigator: 'border-l-green-500',
catalog: 'border-l-purple-500',
room: 'border-l-orange-500',
camera: 'border-l-pink-500',
commands: 'border-l-cyan-500',
wordfilter: 'border-l-red-500',
pet: 'border-l-yellow-500',
matchmaking: 'border-l-indigo-500',
message: 'border-l-emerald-500',
notification: 'border-l-fuchsia-500',
general: 'border-l-gray-400',
}
hotel: "border-l-blue-500",
navigator: "border-l-green-500",
catalog: "border-l-purple-500",
room: "border-l-orange-500",
camera: "border-l-pink-500",
commands: "border-l-cyan-500",
wordfilter: "border-l-red-500",
pet: "border-l-yellow-500",
matchmaking: "border-l-indigo-500",
message: "border-l-emerald-500",
notification: "border-l-fuchsia-500",
general: "border-l-gray-400",
};
export function EmulatorSettingsForm({
settings,
@@ -73,130 +67,130 @@ export function EmulatorSettingsForm({
allGroups,
activeGroup,
}: EmulatorSettingsFormProps) {
const router = useRouter()
const searchParams = useSearchParams()
const router = useRouter();
const searchParams = useSearchParams();
// `values` only contains overrides typed by the user. Display falls back
// to `setting.value`. `originalValues` accumulates the on-disk values for
// every setting we have ever rendered (across pages) so we can detect when
// an edit returns to its original state.
const [values, setValues] = useState<Record<string, string>>({})
const [originalValues, setOriginalValues] = useState<Record<string, string>>({})
const [changedKeys, setChangedKeys] = useState<Set<string>>(new Set())
const [isNavigating, startTransition] = useTransition()
const { run, isPending } = useServerAction()
const [searchValue, setSearchValue] = useState(search)
const [values, setValues] = useState<Record<string, string>>({});
const [originalValues, setOriginalValues] = useState<Record<string, string>>({});
const [changedKeys, setChangedKeys] = useState<Set<string>>(new Set());
const [isNavigating, startTransition] = useTransition();
const { run, isPending } = useServerAction();
const [searchValue, setSearchValue] = useState(search);
useEffect(() => {
setOriginalValues((prev) => {
let dirty = false
const next = { ...prev }
let dirty = false;
const next = { ...prev };
for (const s of settings) {
if (!(s.key in next)) {
next[s.key] = s.value
dirty = true
next[s.key] = s.value;
dirty = true;
}
}
return dirty ? next : prev
})
}, [settings])
return dirty ? next : prev;
});
}, [settings]);
// Prefer the canonical list passed by the server (covers all groups, not
// just those visible on the current page). Fall back to deriving from the
// current page when the prop is omitted.
const groupSummary = useMemo<GroupSummary[]>(() => {
if (allGroups && allGroups.length > 0) return allGroups
const map = new Map<string, number>()
if (allGroups && allGroups.length > 0) return allGroups;
const map = new Map<string, number>();
for (const s of settings) {
const g = getSettingGroup(s.key)
map.set(g, (map.get(g) ?? 0) + 1)
const g = getSettingGroup(s.key);
map.set(g, (map.get(g) ?? 0) + 1);
}
return Array.from(map.entries())
.map(([name, count]) => ({ name, count }))
.sort((a, b) => a.name.localeCompare(b.name))
}, [allGroups, settings])
.sort((a, b) => a.name.localeCompare(b.name));
}, [allGroups, settings]);
function toggleGroup(name: string) {
updateParams({
group: activeGroup === name ? '' : name,
page: '1',
})
group: activeGroup === name ? "" : name,
page: "1",
});
}
function getDisplayValue(setting: Setting): string {
return values[setting.key] ?? setting.value
return values[setting.key] ?? setting.value;
}
function updateValue(key: string, val: string) {
setValues((prev) => ({ ...prev, [key]: val }))
setValues((prev) => ({ ...prev, [key]: val }));
setChangedKeys((prev) => {
const next = new Set(prev)
const next = new Set(prev);
if (originalValues[key] !== undefined && val === originalValues[key]) {
next.delete(key)
next.delete(key);
} else {
next.add(key)
next.add(key);
}
return next
})
return next;
});
}
function revertChange(key: string) {
setValues((prev) => {
const next = { ...prev }
delete next[key]
return next
})
const next = { ...prev };
delete next[key];
return next;
});
setChangedKeys((prev) => {
const next = new Set(prev)
next.delete(key)
return next
})
const next = new Set(prev);
next.delete(key);
return next;
});
}
function updateParams(updates: Record<string, string>) {
const params = new URLSearchParams(searchParams.toString())
const params = new URLSearchParams(searchParams.toString());
for (const [key, value] of Object.entries(updates)) {
if (value) params.set(key, value)
else params.delete(key)
if (value) params.set(key, value);
else params.delete(key);
}
startTransition(() => {
router.push(`?${params.toString()}`)
})
router.push(`?${params.toString()}`);
});
}
function handleSearch(e: React.FormEvent) {
e.preventDefault()
updateParams({ search: searchValue, page: '1' })
e.preventDefault();
updateParams({ search: searchValue, page: "1" });
}
function handleSaveAll() {
if (changedKeys.size === 0) {
toast.info('No changes to save.')
return
toast.info("No changes to save.");
return;
}
const changedSettings: Record<string, string> = {}
const changedSettings: Record<string, string> = {};
for (const key of changedKeys) {
changedSettings[key] = values[key]
changedSettings[key] = values[key];
}
const count = changedKeys.size
const count = changedKeys.size;
run(() => saveEmulatorSettings({ settings: changedSettings }), {
successMessage: `${count} setting(s) saved and synced with emulator.`,
onSuccess: () => {
setChangedKeys(new Set())
setValues({})
setChangedKeys(new Set());
setValues({});
// Refresh server data so the on-disk values reflect the save and the
// originalValues snapshot rebuilds on next render.
setOriginalValues({})
router.refresh()
setOriginalValues({});
router.refresh();
},
})
});
}
function jumpToPage(raw: string) {
const n = parseInt(raw, 10)
if (!Number.isFinite(n)) return
const clamped = Math.max(1, Math.min(pagination.lastPage, n))
if (clamped !== pagination.page) updateParams({ page: String(clamped) })
const n = parseInt(raw, 10);
if (!Number.isFinite(n)) return;
const clamped = Math.max(1, Math.min(pagination.lastPage, n));
if (clamped !== pagination.page) updateParams({ page: String(clamped) });
}
return (
@@ -249,11 +243,11 @@ export function EmulatorSettingsForm({
</button>
)}
{groupSummary.map(({ name, count }) => {
const isActive = activeGroup === name
const isActive = activeGroup === name;
const color = (GROUP_BORDER_COLORS[name] || GROUP_BORDER_COLORS.general).replace(
'border-l-',
'bg-',
)
"border-l-",
"bg-",
);
return (
<button
key={name}
@@ -261,8 +255,8 @@ export function EmulatorSettingsForm({
onClick={() => toggleGroup(name)}
className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md border text-[10px] font-mono transition-colors ${
isActive
? 'border-primary bg-primary/10 text-foreground'
: 'border-border bg-background text-muted-foreground hover:text-foreground hover:bg-accent'
? "border-primary bg-primary/10 text-foreground"
: "border-border bg-background text-muted-foreground hover:text-foreground hover:bg-accent"
}`}
title={isActive ? `Rimuovi filtro: ${name}` : `Filtra: ${name}`}
disabled={isNavigating}
@@ -271,7 +265,7 @@ export function EmulatorSettingsForm({
{name}
<span className="text-muted-foreground">{count}</span>
</button>
)
);
})}
</div>
)}
@@ -279,20 +273,18 @@ export function EmulatorSettingsForm({
{/* Settings list — flat rows with always-on inline editing */}
<div className="rounded-md border bg-card divide-y">
{settings.length === 0 ? (
<div className="px-4 py-8 text-center text-sm text-muted-foreground">
No settings found.
</div>
<div className="px-4 py-8 text-center text-sm text-muted-foreground">No settings found.</div>
) : (
settings.map((setting) => {
const group = getSettingGroup(setting.key)
const borderColor = GROUP_BORDER_COLORS[group] || GROUP_BORDER_COLORS.general
const isChanged = changedKeys.has(setting.key)
const display = getDisplayValue(setting)
const group = getSettingGroup(setting.key);
const borderColor = GROUP_BORDER_COLORS[group] || GROUP_BORDER_COLORS.general;
const isChanged = changedKeys.has(setting.key);
const display = getDisplayValue(setting);
return (
<div
key={setting.key}
className={`flex items-center gap-3 px-3 py-1.5 border-l-4 ${borderColor} ${
isChanged ? 'bg-[var(--admin-warning-subtle)] dark:bg-[var(--admin-warning-subtle)]' : ''
isChanged ? "bg-[var(--admin-warning-subtle)] dark:bg-[var(--admin-warning-subtle)]" : ""
}`}
>
<code
@@ -305,7 +297,7 @@ export function EmulatorSettingsForm({
value={display}
onChange={(e) => updateValue(setting.key, e.target.value)}
className={`flex-1 h-8 text-xs font-mono ${
isChanged ? 'border-[var(--admin-warning-border)]' : ''
isChanged ? "border-[var(--admin-warning-border)]" : ""
}`}
/>
{isChanged && (
@@ -320,7 +312,7 @@ export function EmulatorSettingsForm({
</Button>
)}
</div>
)
);
})
)}
</div>
@@ -331,7 +323,7 @@ export function EmulatorSettingsForm({
<span>Per page</span>
<Select
value={String(pagination.perPage)}
onValueChange={(v) => updateParams({ perPage: v, page: '1' })}
onValueChange={(v) => updateParams({ perPage: v, page: "1" })}
>
<SelectTrigger size="sm" className="w-[80px]">
<SelectValue />
@@ -346,7 +338,7 @@ export function EmulatorSettingsForm({
</Select>
<span className="ml-2">
{pagination.total === 0
? '0 results'
? "0 results"
: `${(pagination.page - 1) * pagination.perPage + 1}–${Math.min(
pagination.page * pagination.perPage,
pagination.total,
@@ -359,7 +351,7 @@ export function EmulatorSettingsForm({
variant="outline"
size="icon"
disabled={pagination.page <= 1 || isNavigating}
onClick={() => updateParams({ page: '1' })}
onClick={() => updateParams({ page: "1" })}
title="First page"
>
<ChevronsLeft className="h-4 w-4" />
@@ -381,7 +373,7 @@ export function EmulatorSettingsForm({
key={pagination.page}
onBlur={(e) => jumpToPage(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') jumpToPage((e.target as HTMLInputElement).value)
if (e.key === "Enter") jumpToPage((e.target as HTMLInputElement).value);
}}
className="h-8 w-14 text-center text-xs"
/>
@@ -407,5 +399,5 @@ export function EmulatorSettingsForm({
</div>
</div>
</div>
)
);
}