Unify UI workflows and streamline fleet management

This commit is contained in:
2026-09-18 14:00:28 -04:00
parent 9cf0bd87f4
commit f54cac8c50
63 changed files with 1706 additions and 1286 deletions
+12 -4
View File
@@ -1,5 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import ListFooter from '$ui/components/primitives/ListFooter.svelte';
import StatusBadge from '$ui/components/primitives/StatusBadge.svelte';
import Badge from '$ui/components/primitives/Badge.svelte';
import Button from '$ui/components/primitives/Button.svelte';
import SectionHeader from '$ui/components/primitives/SectionHeader.svelte';
@@ -8,6 +10,8 @@
import { betaSelectionHref, canTest, mapLabel, scopeLabel, scriptStatus, statusLabels, type LibraryScript, type ScriptStatus } from '$lib/script-library.js';
import { zombieMaps } from '$lib/map-aliases.js';
let limit = $state(25);
$effect(() => { search; status; game; map; sort; limit = 25; });
let scripts = $state<LibraryScript[]>([]);
let loading = $state(true);
let error = $state('');
@@ -38,7 +42,7 @@
return a.name.localeCompare(b.name);
}));
const selectedScripts = $derived(scripts.filter(s => selected.includes(s.id) && canTest(s)));
const selectable = $derived(visible.filter(canTest));
const selectable = $derived(visible.slice(0,limit).filter(canTest));
const allSelected = $derived(selectable.length > 0 && selectable.every(s => selected.includes(s.id)));
async function load() {
error = '';
@@ -71,7 +75,7 @@
ondragleave={() => dragDepth = Math.max(0, dragDepth - 1)} ondragend={() => dragDepth = 0} />
<div class="page">
<SectionHeader title="Scripts" eyebrow="Your script library" subtitle="Review versions, prepare a beta, and keep releases moving.">
<SectionHeader title="Scripts" subtitle="Review versions, prepare a beta, and keep releases moving.">
{#snippet actions()}<Button variant="primary" onclick={() => uploader.open()}>Upload scripts</Button>{/snippet}
</SectionHeader>
<div class="status-grid" aria-label="Filter by script status">
@@ -102,12 +106,12 @@
<EmptyState title="No scripts match these filters" description="Try another name, game, map, or status." />
{:else}
<div class="rows">
{#each visible as s (s.id)}
{#each visible.slice(0,limit) as s (s.id)}
{@const state = scriptStatus(s)}
<article class="script-row" class:selected={selected.includes(s.id)}>
<input type="checkbox" aria-label={`Select ${s.name} for a beta`} checked={selected.includes(s.id)} disabled={!canTest(s)} onchange={() => toggle(s.id)} />
<div class="script-info">
<div class="name-row"><a class="name" href="/scripts/{s.id}">{s.name}</a><Badge variant={state === 'failed' ? 'danger' : state === 'testing' ? 'warning' : state === 'promoted' || state === 'tested' ? 'online' : 'neutral'} size="sm">{statusLabels[state]}</Badge></div>
<div class="name-row"><a class="name" href="/scripts/{s.id}">{s.name}</a><StatusBadge state={state} /></div>
<div class="path">{s.target_rel_path}</div>
<div class="meta"><span title={s.scope_value ?? 'Choose targets explicitly when opening a beta'}>{scopeLabel(s)}</span><span>{s.scope_type ? `${s.servers.length} scope matches` : 'Explicit beta targets'}</span><span>{s.versionCount} version{s.versionCount === 1 ? '' : 's'}</span></div>
</div>
@@ -125,6 +129,7 @@
{/each}
</div>
{/if}
<ListFooter total={visible.length} bind:limit label="scripts" />
<p class="hint">Drop files anywhere to add scripts or recognize new versions. Scope matches describe eligible servers, not installed versions.</p>
</section>
{#if selectedScripts.length}
@@ -164,4 +169,7 @@
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
@media (max-width: 1100px) { .status-grid { grid-template-columns: repeat(3, 1fr); } .script-row { grid-template-columns: auto minmax(0, 1fr) auto; } .versions { grid-column: 2; } .row-action { grid-column: 3; grid-row: 1 / 3; } }
@media (max-width: 640px) { .status-grid { grid-template-columns: repeat(2, 1fr); } .status-grid button { padding: 12px; gap: 6px; } .status-grid strong { font-size: 24px; } .script-row { gap: 12px; padding: 16px; grid-template-columns: auto minmax(0, 1fr); } .row-action { grid-column: 2; grid-row: auto; } .toolbar label { flex: 1 1 140px; } .toolbar .search { flex-basis: 100%; } .selection-bar span { display: none; } }
.status-grid { display:flex; gap:8px; overflow-x:auto; }
.status-grid button { flex-direction:row; align-items:center; flex-shrink:0; padding:8px 12px; gap:8px; }
.status-grid strong { font-size:13px; }
</style>