Add timezone preference across time displays

This commit is contained in:
2026-09-20 22:12:27 -04:00
parent 1e6ff3abbf
commit 5c5e75526d
28 changed files with 297 additions and 52 deletions
+23 -5
View File
@@ -1,4 +1,6 @@
<script lang="ts">
import { timeFormat, timeZone, timeZonePersistenceError } from '$ui/stores/time-zone.js';
import { availableTimeZones, DEFAULT_TIME_ZONE } from '$lib/time-zone.js';
import { onMount } from 'svelte';
import type { SystemStatus } from '$lib/system-status.js';
import SectionHeader from '$ui/components/primitives/SectionHeader.svelte';
@@ -7,6 +9,7 @@
let data = $state<SystemStatus | null>(null);
let loading = $state(false);
let error = $state('');
const zones = $derived([...new Set([...availableTimeZones(), $timeZone])].sort());
async function refresh(probe = false) {
if (loading) return;
loading = true; error = '';
@@ -21,14 +24,27 @@
const labels = { ready: 'Ready', attention: 'Needs attention', unconfigured: 'Not configured', unverified: 'Not verified' };
</script>
<svelte:head><title>System status · Electric Cherry</title></svelte:head>
<svelte:head><title>Settings · Electric Cherry</title></svelte:head>
<section class="page">
<SectionHeader title="System status" subtitle="Check your connections and the prerequisites for a playable server.">
{#snippet actions()}<Button loading={loading} onclick={() => refresh(true)}>Check connections</Button>{/snippet}
</SectionHeader>
<SectionHeader title="Settings" subtitle="Choose how dates and times appear across Electric Cherry." />
<section class="guide" aria-labelledby="display-heading">
<h2 id="display-heading">Display preferences</h2>
<label class="timezone">Timezone
<select bind:value={$timeZone} aria-describedby="timezone-help">
{#each zones as zone}<option value={zone}>{zone.replaceAll('_', ' ')}{zone === DEFAULT_TIME_ZONE ? ' (default)' : ''}</option>{/each}
</select>
</label>
<p id="timezone-help">Dates and times throughout the app use this timezone, including daylight saving changes. Saved automatically in this browser.</p>
{#if data}<p>Example: {$timeFormat.dateTime(data.checkedAt)}</p>{/if}
{#if $timeZonePersistenceError}<p role="status">Browser storage is unavailable. Your timezone applies for this visit but cannot be saved.</p>{/if}
</section>
<div class="heading">
<div><h2>System status</h2><p>Check your connections and the prerequisites for a playable server.</p></div>
<Button loading={loading} onclick={() => refresh(true)}>Check connections</Button>
</div>
{#if error}<p class="ui-error" role="alert">{error}<Button size="sm" onclick={() => refresh()}>Retry</Button></p>{/if}
{#if data}
<p class="checked">Checked {new Date(data.checkedAt).toLocaleString()} · {data.readyServers} / {data.totalServers} servers ready</p>
<p class="checked">Checked {$timeFormat.dateTime(data.checkedAt)} · {data.readyServers} / {data.totalServers} servers ready</p>
<ol class="checks">{#each data.checks as check}<li><div class="heading"><h2>{check.title}</h2><Badge variant={check.state === 'ready' ? 'online' : check.state === 'unverified' ? 'neutral' : 'warning'}>{labels[check.state]}</Badge></div><p>{check.detail}</p>{#if check.href}<a href={check.href}>{check.action} →</a>{/if}</li>{/each}</ol>
{:else if loading}<p role="status">Checking system setup…</p>{/if}
<section id="setup-guide" class="guide"><h2>Connection setup</h2><p>Set these variables on the Electric Cherry container in Komodo, then restart the app to load the changes. Credentials are kept on the server and are never returned by this page.</p><dl><dt>Komodo</dt><dd><code>KOMODO_ADDRESS</code>, <code>KOMODO_API_KEY</code>, <code>KOMODO_API_SECRET</code>, and <code>KOMODO_TARGET_SERVER</code>. The target must match the host name in Komodo.</dd><dt>Game servers</dt><dd><code>RCON_PASSWORD</code> must match the game-server password. Set <code>RCON_HOST</code> if the default <code>host.docker.internal</code> does not reach the game host. Forward the allocated UDP game ports.</dd></dl></section>
@@ -48,5 +64,7 @@
dd { margin: 5px 0 0; }
code { font-size: var(--text-xs); }
.guide { scroll-margin-top: 90px; }
.timezone { display: grid; gap: 8px; margin-top: 16px; font-size: var(--text-sm); max-width: 420px; }
.timezone select { width: 100%; padding: 10px 12px; font: inherit; color: var(--text-primary); background: var(--surface-sunken); border: 1px solid var(--border-control); border-radius: var(--radius-sm); }
@media (max-width: 700px) { .checks { grid-template-columns: 1fr; } }
</style>