Skip to content

Commit e701c00

Browse files
committed
Restore full details sheet, minus the theme card
Bring back the event, firmware-build and links cards (and the date-range util they use) alongside the radio settings — only the theme name/tagline/palette card stays removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWbctVTeY3iySKcELxhcR5
1 parent 66e95de commit e701c00

4 files changed

Lines changed: 167 additions & 9 deletions

File tree

components/EventDetails.vue

Lines changed: 101 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
class="btn-secondary"
77
@click="openPanel"
88
>
9-
{{ $t('event.details_button') }} <Settings class="h-4 w-4 shrink-0" />
9+
{{ $t('event.details_button') }} <Info class="h-4 w-4 shrink-0" />
1010
</button>
1111

1212
<Teleport to="body">
@@ -29,7 +29,7 @@
2929
id="event-details-title"
3030
class="flex items-center gap-2 text-lg font-semibold text-theme"
3131
>
32-
<Settings class="h-5 w-5 text-meshtastic" />
32+
<Info class="h-5 w-5 text-meshtastic" />
3333
{{ $t('event.details_title') }}
3434
</h3>
3535
<button
@@ -43,9 +43,72 @@
4343
</button>
4444
</div>
4545

46-
<!-- Body: radio settings from the event branch's userPrefs.jsonc -->
46+
<!-- Body -->
4747
<div class="flex-1 space-y-4 overflow-y-auto p-4">
48-
<!-- LoRa -->
48+
<!-- Event -->
49+
<div class="rounded-lg border border-theme bg-surface-primary p-3 text-sm">
50+
<p class="mb-2 text-xs font-semibold uppercase tracking-wide text-theme-muted">
51+
{{ $t('event.section_event') }}
52+
</p>
53+
<dl class="grid grid-cols-[auto,1fr] gap-x-3 gap-y-1">
54+
<dt class="text-theme-muted">
55+
{{ $t('event.label_name') }}
56+
</dt>
57+
<dd>{{ eventMode.eventName }}</dd>
58+
<template v-if="dateRange">
59+
<dt class="text-theme-muted">
60+
{{ $t('event.label_dates') }}
61+
</dt>
62+
<dd>{{ dateRange }}</dd>
63+
</template>
64+
<template v-if="edition?.location">
65+
<dt class="text-theme-muted">
66+
{{ $t('event.label_location') }}
67+
</dt>
68+
<dd>{{ edition.location }}</dd>
69+
</template>
70+
<template v-if="edition?.timeZone">
71+
<dt class="text-theme-muted">
72+
{{ $t('event.label_timezone') }}
73+
</dt>
74+
<dd>{{ edition.timeZone }}</dd>
75+
</template>
76+
</dl>
77+
<p
78+
v-if="edition?.welcomeMessage"
79+
class="mt-2 text-theme-muted"
80+
>
81+
{{ edition.welcomeMessage }}
82+
</p>
83+
</div>
84+
85+
<!-- Firmware -->
86+
<div
87+
v-if="eventMode.firmware.title || firmwareVersion"
88+
class="rounded-lg border border-theme bg-surface-primary p-3 text-sm"
89+
>
90+
<p class="mb-2 text-xs font-semibold uppercase tracking-wide text-theme-muted">
91+
{{ $t('event.section_firmware') }}
92+
</p>
93+
<dl class="grid grid-cols-[auto,1fr] gap-x-3 gap-y-1">
94+
<template v-if="eventMode.firmware.title">
95+
<dt class="text-theme-muted">
96+
{{ $t('event.label_build') }}
97+
</dt>
98+
<dd>{{ eventMode.firmware.title }}</dd>
99+
</template>
100+
<template v-if="firmwareVersion">
101+
<dt class="text-theme-muted">
102+
{{ $t('event.label_version') }}
103+
</dt>
104+
<dd class="break-all font-mono text-theme-accent">
105+
{{ firmwareVersion }}
106+
</dd>
107+
</template>
108+
</dl>
109+
</div>
110+
111+
<!-- LoRa (from the event branch's userPrefs.jsonc) -->
49112
<div
50113
v-if="hasLora"
51114
class="rounded-lg border border-theme bg-surface-primary p-3 text-sm"
@@ -182,15 +245,15 @@
182245
</dl>
183246
</div>
184247

185-
<!-- Fetch status + source link -->
248+
<!-- Settings fetch status + source link -->
186249
<p
187250
v-if="prefsState === 'loading'"
188251
class="text-sm text-theme-muted"
189252
>
190253
{{ $t('event.settings_loading') }}
191254
</p>
192255
<p
193-
v-else-if="prefsState === 'error' || !firmwareSlug"
256+
v-else-if="prefsState === 'error'"
194257
class="text-sm text-theme-muted"
195258
>
196259
{{ $t('event.settings_unavailable') }}
@@ -205,6 +268,32 @@
205268
{{ $t('event.view_userprefs') }}
206269
<ExternalLink class="h-3.5 w-3.5 shrink-0" />
207270
</a>
271+
272+
<!-- Links -->
273+
<div
274+
v-if="edition?.links?.length"
275+
class="rounded-lg border border-theme bg-surface-primary p-3 text-sm"
276+
>
277+
<p class="mb-2 text-xs font-semibold uppercase tracking-wide text-theme-muted">
278+
{{ $t('event.section_links') }}
279+
</p>
280+
<ul class="space-y-1">
281+
<li
282+
v-for="link in edition.links"
283+
:key="link.url"
284+
>
285+
<a
286+
:href="link.url"
287+
target="_blank"
288+
rel="noopener noreferrer"
289+
class="inline-flex items-center gap-1.5 text-meshtastic underline underline-offset-2"
290+
>
291+
{{ link.label }}
292+
<ExternalLink class="h-3.5 w-3.5 shrink-0" />
293+
</a>
294+
</li>
295+
</ul>
296+
</div>
208297
</div>
209298
</div>
210299
</div>
@@ -216,18 +305,23 @@
216305
<script lang="ts" setup>
217306
import { computed, nextTick, ref, watch } from 'vue'
218307
import { onKeyStroke } from '@vueuse/core'
219-
import { ExternalLink, Settings, X } from 'lucide-vue-next'
308+
import { ExternalLink, Info, X } from 'lucide-vue-next'
309+
import { useI18n } from 'vue-i18n'
220310
import type { EventUserPrefs } from '~/utils/eventUserPrefs'
221311
import { useSerialMonitorStore } from '~/stores/serialMonitorStore'
222312
223313
const { eventMode } = useEventMode()
224314
const edition = useEventEdition()
225315
const serialMonitorStore = useSerialMonitorStore()
316+
const { locale } = useI18n()
226317
227318
const open = ref(false)
228319
const triggerButton = ref<HTMLButtonElement | null>(null)
229320
const closeButton = ref<HTMLButtonElement | null>(null)
230321
322+
const dateRange = computed(() => formatEventDateRange(edition.value?.eventStart, edition.value?.eventEnd, locale.value))
323+
const firmwareVersion = computed(() => edition.value?.firmware?.version || eventMode.value.firmware.id)
324+
231325
// Radio settings from the event branch's userPrefs.jsonc, fetched lazily on
232326
// first open so non-event visitors and closed sheets cost nothing.
233327
const prefs = ref<EventUserPrefs | null>(null)

i18n/locales/en.json

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,20 @@
128128
"contribute": "Contribute on GitHub"
129129
},
130130
"event": {
131-
"details_button": "Firmware Settings",
132-
"details_title": "Event Firmware Settings",
131+
"details_button": "Event Details",
132+
"details_title": "Event Firmware Details",
133+
"section_event": "Event",
134+
"section_firmware": "Firmware",
133135
"section_lora": "LoRa",
134136
"section_channels": "Channels",
135137
"section_mqtt": "MQTT",
138+
"section_links": "Links",
139+
"label_name": "Name",
140+
"label_dates": "Dates",
141+
"label_location": "Location",
142+
"label_timezone": "Time zone",
143+
"label_build": "Build",
144+
"label_version": "Version",
136145
"label_region": "Region",
137146
"label_modem_preset": "Modem preset",
138147
"label_frequency_slot": "Frequency slot",

utils/eventDates.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import { formatEventDateRange } from './eventDates'
4+
5+
describe('formatEventDateRange', () => {
6+
it('formats a start/end range', () => {
7+
expect(formatEventDateRange('2026-08-06', '2026-08-09')).toBe('Aug 6, 2026 – Aug 9, 2026')
8+
})
9+
10+
it('formats a start-only date', () => {
11+
expect(formatEventDateRange('2026-08-06', null)).toBe('Aug 6, 2026')
12+
})
13+
14+
it('collapses a same-day range to a single date', () => {
15+
expect(formatEventDateRange('2026-08-06', '2026-08-06')).toBe('Aug 6, 2026')
16+
})
17+
18+
it('returns an empty string without a valid start date', () => {
19+
expect(formatEventDateRange(null, '2026-08-09')).toBe('')
20+
expect(formatEventDateRange(undefined)).toBe('')
21+
expect(formatEventDateRange('', '')).toBe('')
22+
expect(formatEventDateRange('not-a-date')).toBe('')
23+
expect(formatEventDateRange('2026-8-6')).toBe('')
24+
})
25+
26+
it('keeps the calendar day regardless of the local time zone', () => {
27+
// A UTC-midnight parse would render Aug 6 as Aug 5 anywhere west of UTC.
28+
expect(formatEventDateRange('2026-08-06')).toContain('6')
29+
})
30+
31+
it('respects the requested locale', () => {
32+
expect(formatEventDateRange('2026-08-06', '2026-08-09', 'de')).toContain('Aug')
33+
})
34+
})

utils/eventDates.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Event dates in the eventFirmware manifest are date-only strings
2+
// ("2026-08-06"). Parsing those with `new Date(str)` yields UTC midnight,
3+
// which renders as the previous day in negative-offset time zones — so parse
4+
// the parts explicitly into a local Date instead.
5+
function parseDateOnly(value?: string | null): Date | null {
6+
if (!value) return null
7+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim())
8+
if (!match) return null
9+
const date = new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]))
10+
return Number.isNaN(date.getTime()) ? null : date
11+
}
12+
13+
export function formatEventDateRange(start?: string | null, end?: string | null, locale = 'en'): string {
14+
const startDate = parseDateOnly(start)
15+
if (!startDate) return ''
16+
const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'short', day: 'numeric' }
17+
const startText = startDate.toLocaleDateString(locale, options)
18+
const endDate = parseDateOnly(end)
19+
if (!endDate || endDate.getTime() === startDate.getTime()) return startText
20+
return `${startText}${endDate.toLocaleDateString(locale, options)}`
21+
}

0 commit comments

Comments
 (0)