From d147e6af50ddc7b4f4b568cc75bbc56a8ec56ec2 Mon Sep 17 00:00:00 2001 From: Florian <45694132+flo-bit@users.noreply.github.com> Date: Sun, 7 Jun 2026 16:18:37 +0200 Subject: [PATCH] small improvements --- apps/web/.gitignore | 1 + apps/web/wrangler.jsonc | 3 +- .../src/schedule/ConferenceTimetable.svelte | 144 +++++++++++++++++- .../ui/src/schedule/ScheduleEventCell.svelte | 11 +- 4 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 apps/web/.gitignore diff --git a/apps/web/.gitignore b/apps/web/.gitignore new file mode 100644 index 0000000..9a2e320 --- /dev/null +++ b/apps/web/.gitignore @@ -0,0 +1 @@ +scripts/demo-talks/ \ No newline at end of file diff --git a/apps/web/wrangler.jsonc b/apps/web/wrangler.jsonc index 569456c..58f93b5 100644 --- a/apps/web/wrangler.jsonc +++ b/apps/web/wrangler.jsonc @@ -25,8 +25,7 @@ { "binding": "DB", "database_name": "atmo-events-v5", - "database_id": "3f862040-84ee-4216-8ba7-ef3146058de4", - "remote": true + "database_id": "3f862040-84ee-4216-8ba7-ef3146058de4" } ], "triggers": { diff --git a/packages/ui/src/schedule/ConferenceTimetable.svelte b/packages/ui/src/schedule/ConferenceTimetable.svelte index 6e7fed5..82d09c5 100644 --- a/packages/ui/src/schedule/ConferenceTimetable.svelte +++ b/packages/ui/src/schedule/ConferenceTimetable.svelte @@ -2,14 +2,17 @@ import { Button, ToggleGroup, ToggleGroupItem } from '@foxui/core'; import { onMount } from 'svelte'; import DaySchedule from './DaySchedule.svelte'; + import ScheduleEventCell from './ScheduleEventCell.svelte'; import { type ScheduleEvent, + type GridEvent, getDayKey, getDayLabel, getDayShortLabel, getRooms, buildGrid, - isoToMinutes + isoToMinutes, + formatTime } from './schedule-utils.js'; import type { EditorAdapter, EditorViewer } from '../editor/adapter.js'; @@ -92,6 +95,61 @@ return now >= new Date(first) && now <= new Date(lastEnd); }); + // The viewer's RSVP'd talks (going/interested) that haven't ended yet, shown + // a few at a time via "Show more". + let visibleCount = $state(3); + let upNextAll = $derived.by(() => { + if (!loggedIn) return []; + const nowMs = now.getTime(); + return scheduleEvents + .filter((e) => { + const status = rsvpStatuses[e.uri]; + if (status !== 'going' && status !== 'interested') return false; + const endMs = new Date(e.end ?? e.start).getTime(); + return endMs >= nowMs; + }) + .sort((a, b) => a.start.localeCompare(b.start)); + }); + let upNext = $derived(upNextAll.slice(0, visibleCount)); + + function whenLabel(ev: ScheduleEvent): { text: string; soon: boolean } { + const start = new Date(ev.start); + const end = new Date(ev.end ?? ev.start); + if (now >= start && now <= end) return { text: 'Now', soon: true }; + const diffMin = Math.round((start.getTime() - now.getTime()) / 60_000); + if (diffMin >= 0 && diffMin < 60) return { text: `in ${diffMin} min`, soon: true }; + const time = formatTime(ev.start, tz); + const sameDay = getDayKey(ev.start, tz) === nowKey; + return { text: sameDay ? time : `${getDayShortLabel(ev.start, tz)} ${time}`, soon: false }; + } + + function subtitle(ev: ScheduleEvent): string { + return [ + ev.room && ev.room !== 'none' ? ev.room : null, + ev.speakers?.length ? ev.speakers.map((s) => s.name).join(', ') : null + ] + .filter(Boolean) + .join(' ยท '); + } + + // Wrap a schedule event as a GridEvent so ScheduleEventCell can own its modal + // (the layout fields are unused; only the start/end-minute span matters). + function toGridEvent(ev: ScheduleEvent): GridEvent { + const durMin = ev.end + ? Math.max(0, Math.round((new Date(ev.end).getTime() - new Date(ev.start).getTime()) / 60_000)) + : 30; + return { + ...ev, + startMin: 0, + endMin: durMin, + startRow: 1, + spanRows: 1, + colStart: 1, + colSpan: 1, + zIndex: 0 + }; + } + let tzLabel = $derived.by(() => { try { const parts = new Intl.DateTimeFormat('en-US', { @@ -150,6 +208,90 @@

All times shown in {tzLabel}.

+ {#if upNextAll.length > 0} +
+

Up next

+
+ {#each upNext as ev (ev.uri)} + {@const w = whenLabel(ev)} + + {#snippet trigger({ open })} + + {/snippet} + + {/each} +
+ {#if upNextAll.length > upNext.length} + + {/if} +
+ {/if} + {#if dayGroups.length > 1 || loggedIn}
{#if dayGroups.length > 1} diff --git a/packages/ui/src/schedule/ScheduleEventCell.svelte b/packages/ui/src/schedule/ScheduleEventCell.svelte index 34cf396..77f49ae 100644 --- a/packages/ui/src/schedule/ScheduleEventCell.svelte +++ b/packages/ui/src/schedule/ScheduleEventCell.svelte @@ -10,6 +10,7 @@ import EventRsvp from '../EventRsvp.svelte'; import VodPlayer from '../VodPlayer.svelte'; import type { EditorAdapter, EditorViewer } from '../editor/adapter.js'; + import type { Snippet } from 'svelte'; let { event, @@ -21,7 +22,8 @@ rsvpRkeys = {}, vodPlaylistUrl, vodSubtitlesUrl, - onrsvpchange + onrsvpchange, + trigger }: { event: GridEvent; /** Timezone the schedule is laid out in. */ @@ -35,6 +37,9 @@ vodPlaylistUrl?: string; vodSubtitlesUrl?: string; onrsvpchange?: (uri: string, status: string | null, rkey?: string) => void; + /** Custom clickable trigger; receives `open` to launch the modal. Defaults + * to the standard colored grid-cell button. */ + trigger?: Snippet<[{ open: () => void }]>; } = $props(); let modalOpen = $state(false); @@ -50,6 +55,9 @@ {#if linkableTypes.has(event.type) && event.rkey} + {#if trigger} + {@render trigger({ open: () => (modalOpen = true) })} + {:else} + {/if}
-- 2.51.2