Unify UI workflows and streamline fleet management
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { beforeNavigate } from '$app/navigation';
|
||||
import { confirmAction } from '$ui/confirm.js';
|
||||
import { beforeNavigate, goto } from '$app/navigation';
|
||||
import ConfigTextEditor from './ConfigTextEditor.svelte';
|
||||
import ConfigInspector from './ConfigInspector.svelte';
|
||||
import Button from './primitives/Button.svelte';
|
||||
@@ -30,7 +31,16 @@
|
||||
if (saved && typeof saved.text === 'string' && typeof saved.version === 'string' && typeof saved.notes === 'string') recovered = saved;
|
||||
} catch { /* Draft storage is optional. */ }
|
||||
});
|
||||
beforeNavigate(({ cancel }) => { if (dirty && !confirm('Leave this editor? Your draft is kept in this browser when storage is available.')) cancel(); });
|
||||
let allowNavigation = false;
|
||||
beforeNavigate(({ cancel, to, type }) => {
|
||||
if (!dirty || allowNavigation || type === 'leave' || !to) return;
|
||||
cancel();
|
||||
void confirmAction({title:'Leave this editor?', message:'You have unsaved changes. A draft is kept in this browser when storage is available.', confirmLabel:'Leave editor'}).then(async accepted => {
|
||||
if (!accepted) return;
|
||||
allowNavigation = true;
|
||||
try { await goto(to.url); } finally { allowNavigation = false; }
|
||||
});
|
||||
});
|
||||
function beforeUnload(event: BeforeUnloadEvent) { if (dirty) { event.preventDefault(); event.returnValue = ''; } }
|
||||
$effect(() => {
|
||||
const version = selected;
|
||||
@@ -54,10 +64,11 @@
|
||||
try { localStorage.setItem(storageKey, JSON.stringify({ version: loadedVersion, text, notes })); } catch { /* Editing still works without storage. */ }
|
||||
});
|
||||
function clearDraft() { try { localStorage.removeItem(storageKey); } catch { /* Storage may be disabled. */ } recovered = null; }
|
||||
function changeVersion(event: Event) {
|
||||
async function changeVersion(event: Event) {
|
||||
const target = event.currentTarget as HTMLSelectElement;
|
||||
if (dirty && !confirm('Discard your current edits and open this version?')) { target.value = selected; return; }
|
||||
clearDraft(); selected = target.value; success = '';
|
||||
const next = target.value; target.value = selected;
|
||||
if (dirty && !await confirmAction({title:'Discard current edits?',message:'Open the selected version and discard your current changes.',confirmLabel:'Discard edits',danger:true})) return;
|
||||
clearDraft(); selected = next; success = '';
|
||||
}
|
||||
function restoreDraft() {
|
||||
if (!recovered) return;
|
||||
@@ -65,7 +76,7 @@
|
||||
// recovered draft was started from a version that has since been deleted.
|
||||
text = recovered.text; notes = recovered.notes; recovered = null;
|
||||
}
|
||||
function discard() { if (confirm('Discard your unsaved changes?')) { text = original; notes = ''; clearDraft(); } }
|
||||
async function discard() { if (await confirmAction({title:'Discard unsaved changes?',message:'Restore the version you opened in this editor.',confirmLabel:'Discard changes',danger:true})) { text = original; notes = ''; clearDraft(); } }
|
||||
async function importFile(event: Event) {
|
||||
const input = event.currentTarget as HTMLInputElement;
|
||||
const file = input.files?.[0];
|
||||
@@ -100,13 +111,13 @@
|
||||
<label class="file-button">Import .cfg<input type="file" accept=".cfg,text/plain" onchange={importFile} disabled={saving || loading || runningBetaId !== null} /></label>
|
||||
</div>
|
||||
{#if recovered && !loading}<div class="recovery"><span>A previous draft is available in this browser.</span><Button size="sm" onclick={restoreDraft} disabled={runningBetaId !== null}>Restore draft</Button><Button size="sm" variant="ghost" onclick={clearDraft}>Dismiss</Button></div>{/if}
|
||||
{#if success}<div class="success" role="status">{success} <a href="/betas">Open betas →</a></div>{/if}
|
||||
{#if success}<div class="success" role="status">{success} <a href={"/betas/new?artifact=" + id + ":" + selected}>Test this version →</a></div>{/if}
|
||||
{#if error}<div class="error" role="alert">{error} {#if !loadedVersion}<Button size="sm" onclick={() => retry++}>Retry</Button>{/if}</div>{/if}
|
||||
{#if loading}<p class="muted" role="status">Loading config contents…</p>
|
||||
{:else if loadedVersion}
|
||||
<ConfigTextEditor bind:text overlay={kind === 'config'} disabled={saving || runningBetaId !== null} />
|
||||
<label class="notes-label">Version notes<input bind:value={notes} placeholder="What changed, and why?" disabled={saving || runningBetaId !== null} /></label>
|
||||
<div class="actions"><span class="muted">{runningBetaId !== null ? 'Finish the active beta to save another version.' : 'A new version must pass through a beta before promotion.'}</span>
|
||||
<div class="actions ui-sticky-actions"><span class="muted">{runningBetaId !== null ? 'Finish the active beta to save another version.' : 'A new version must pass through a beta before promotion.'}</span>
|
||||
<Button variant="ghost" size="sm" onclick={discard} disabled={!dirty || saving}>Discard</Button>
|
||||
<Button variant="primary" onclick={save} loading={saving} disabled={!dirty || !text.trim() || runningBetaId !== null}>Save new version</Button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user