@atmo-dev/kit 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/README.md +35 -0
  2. package/dist/events/DatePicker.svelte +222 -0
  3. package/dist/events/DatePicker.svelte.d.ts +12 -0
  4. package/dist/events/DateTimePicker.svelte +112 -0
  5. package/dist/events/DateTimePicker.svelte.d.ts +10 -0
  6. package/dist/events/EventAttendees.svelte +199 -0
  7. package/dist/events/EventAttendees.svelte.d.ts +19 -0
  8. package/dist/events/EventCard.svelte +148 -0
  9. package/dist/events/EventCard.svelte.d.ts +8 -0
  10. package/dist/events/EventComments.svelte +98 -0
  11. package/dist/events/EventComments.svelte.d.ts +8 -0
  12. package/dist/events/EventEditor.svelte +540 -0
  13. package/dist/events/EventEditor.svelte.d.ts +18 -0
  14. package/dist/events/EventRsvp.svelte +178 -0
  15. package/dist/events/EventRsvp.svelte.d.ts +16 -0
  16. package/dist/events/EventView.svelte +488 -0
  17. package/dist/events/EventView.svelte.d.ts +23 -0
  18. package/dist/events/ImageDropper.svelte +66 -0
  19. package/dist/events/ImageDropper.svelte.d.ts +7 -0
  20. package/dist/events/Map.svelte +36 -0
  21. package/dist/events/Map.svelte.d.ts +8 -0
  22. package/dist/events/PostToBlueskyModal.svelte +246 -0
  23. package/dist/events/PostToBlueskyModal.svelte.d.ts +22 -0
  24. package/dist/events/ShareModal.svelte +174 -0
  25. package/dist/events/ShareModal.svelte.d.ts +23 -0
  26. package/dist/events/StarterPackButton.svelte +72 -0
  27. package/dist/events/StarterPackButton.svelte.d.ts +10 -0
  28. package/dist/events/ThemeApply.svelte +30 -0
  29. package/dist/events/ThemeApply.svelte.d.ts +7 -0
  30. package/dist/events/ThemeBackground.svelte +33 -0
  31. package/dist/events/ThemeBackground.svelte.d.ts +7 -0
  32. package/dist/events/ThemePicker.svelte +104 -0
  33. package/dist/events/ThemePicker.svelte.d.ts +7 -0
  34. package/dist/events/ThumbnailPresets.svelte +71 -0
  35. package/dist/events/ThumbnailPresets.svelte.d.ts +11 -0
  36. package/dist/events/TimePicker.svelte +117 -0
  37. package/dist/events/TimePicker.svelte.d.ts +10 -0
  38. package/dist/events/TimezonePicker.svelte +67 -0
  39. package/dist/events/TimezonePicker.svelte.d.ts +6 -0
  40. package/dist/events/VodPlayer.svelte +154 -0
  41. package/dist/events/VodPlayer.svelte.d.ts +14 -0
  42. package/dist/events/VodTranscript.svelte +75 -0
  43. package/dist/events/VodTranscript.svelte.d.ts +8 -0
  44. package/dist/events/atproto-helpers.d.ts +21 -0
  45. package/dist/events/atproto-helpers.js +61 -0
  46. package/dist/events/cal/helper.d.ts +1 -0
  47. package/dist/events/cal/helper.js +20 -0
  48. package/dist/events/cal/ical.d.ts +22 -0
  49. package/dist/events/cal/ical.js +188 -0
  50. package/dist/events/cal/sanitize.d.ts +3 -0
  51. package/dist/events/cal/sanitize.js +25 -0
  52. package/dist/events/conference.d.ts +65 -0
  53. package/dist/events/conference.js +61 -0
  54. package/dist/events/contrail.d.ts +49 -0
  55. package/dist/events/contrail.js +12 -0
  56. package/dist/events/date-format.d.ts +22 -0
  57. package/dist/events/date-format.js +43 -0
  58. package/dist/events/editor/LinkedRecordsPicker.svelte +104 -0
  59. package/dist/events/editor/LinkedRecordsPicker.svelte.d.ts +12 -0
  60. package/dist/events/editor/LinksSection.svelte +154 -0
  61. package/dist/events/editor/LinksSection.svelte.d.ts +13 -0
  62. package/dist/events/editor/LocationSection.svelte +163 -0
  63. package/dist/events/editor/LocationSection.svelte.d.ts +8 -0
  64. package/dist/events/editor/RecurringModal.svelte +313 -0
  65. package/dist/events/editor/RecurringModal.svelte.d.ts +30 -0
  66. package/dist/events/editor/ThemeSection.svelte +24 -0
  67. package/dist/events/editor/ThemeSection.svelte.d.ts +7 -0
  68. package/dist/events/editor/ThumbnailSection.svelte +181 -0
  69. package/dist/events/editor/ThumbnailSection.svelte.d.ts +13 -0
  70. package/dist/events/editor/adapter.d.ts +73 -0
  71. package/dist/events/editor/adapter.js +9 -0
  72. package/dist/events/editor/location.d.ts +16 -0
  73. package/dist/events/editor/location.js +50 -0
  74. package/dist/events/editor/save.d.ts +43 -0
  75. package/dist/events/editor/save.js +168 -0
  76. package/dist/events/editor/types.d.ts +39 -0
  77. package/dist/events/editor/types.js +9 -0
  78. package/dist/events/event-media.d.ts +4 -0
  79. package/dist/events/event-media.js +4 -0
  80. package/dist/events/event-types.d.ts +79 -0
  81. package/dist/events/event-types.js +11 -0
  82. package/dist/events/event-view/AddToCalendarButton.svelte +31 -0
  83. package/dist/events/event-view/AddToCalendarButton.svelte.d.ts +9 -0
  84. package/dist/events/event-view/EventBadges.svelte +20 -0
  85. package/dist/events/event-view/EventBadges.svelte.d.ts +7 -0
  86. package/dist/events/event-view/EventDateBlock.svelte +43 -0
  87. package/dist/events/event-view/EventDateBlock.svelte.d.ts +7 -0
  88. package/dist/events/event-view/EventHostedBy.svelte +61 -0
  89. package/dist/events/event-view/EventHostedBy.svelte.d.ts +16 -0
  90. package/dist/events/event-view/EventLinksList.svelte +24 -0
  91. package/dist/events/event-view/EventLinksList.svelte.d.ts +9 -0
  92. package/dist/events/event-view/EventLocationBlock.svelte +62 -0
  93. package/dist/events/event-view/EventLocationBlock.svelte.d.ts +7 -0
  94. package/dist/events/event-view/EventLocationMap.svelte +66 -0
  95. package/dist/events/event-view/EventLocationMap.svelte.d.ts +7 -0
  96. package/dist/events/event-view/ExternalRsvpNotice.svelte +34 -0
  97. package/dist/events/event-view/ExternalRsvpNotice.svelte.d.ts +6 -0
  98. package/dist/events/event-view/StreamPlacePlayer.svelte +209 -0
  99. package/dist/events/event-view/StreamPlacePlayer.svelte.d.ts +8 -0
  100. package/dist/events/event-view/format.d.ts +10 -0
  101. package/dist/events/event-view/format.js +112 -0
  102. package/dist/events/event-view/locations.d.ts +36 -0
  103. package/dist/events/event-view/locations.js +243 -0
  104. package/dist/events/event-view/plyr-icons.d.ts +3 -0
  105. package/dist/events/event-view/plyr-icons.js +46 -0
  106. package/dist/events/linked-records.d.ts +18 -0
  107. package/dist/events/linked-records.js +11 -0
  108. package/dist/events/profile-url.d.ts +3 -0
  109. package/dist/events/profile-url.js +11 -0
  110. package/dist/events/schedule/ConferenceTimetable.svelte +340 -0
  111. package/dist/events/schedule/ConferenceTimetable.svelte.d.ts +22 -0
  112. package/dist/events/schedule/DaySchedule.svelte +298 -0
  113. package/dist/events/schedule/DaySchedule.svelte.d.ts +26 -0
  114. package/dist/events/schedule/ScheduleEventCell.svelte +166 -0
  115. package/dist/events/schedule/ScheduleEventCell.svelte.d.ts +25 -0
  116. package/dist/events/schedule/schedule-utils.d.ts +44 -0
  117. package/dist/events/schedule/schedule-utils.js +166 -0
  118. package/dist/events/starter-pack-url.d.ts +3 -0
  119. package/dist/events/starter-pack-url.js +16 -0
  120. package/dist/events/theme.d.ts +9 -0
  121. package/dist/events/theme.js +36 -0
  122. package/dist/events/themes/Blobs.svelte +64 -0
  123. package/dist/events/themes/Blobs.svelte.d.ts +18 -0
  124. package/dist/events/themes/Butterflies.svelte +183 -0
  125. package/dist/events/themes/Butterflies.svelte.d.ts +3 -0
  126. package/dist/events/themes/Fireflies.svelte +134 -0
  127. package/dist/events/themes/Fireflies.svelte.d.ts +3 -0
  128. package/dist/events/themes/Kaleidoscope.svelte +183 -0
  129. package/dist/events/themes/Kaleidoscope.svelte.d.ts +3 -0
  130. package/dist/events/themes/Matrix.svelte +153 -0
  131. package/dist/events/themes/Matrix.svelte.d.ts +3 -0
  132. package/dist/events/themes/Stars.svelte +98 -0
  133. package/dist/events/themes/Stars.svelte.d.ts +3 -0
  134. package/dist/events/thumbnails/designs.d.ts +18 -0
  135. package/dist/events/thumbnails/designs.js +316 -0
  136. package/dist/events/view-data.d.ts +38 -0
  137. package/dist/events/view-data.js +1 -0
  138. package/dist/events-data.d.ts +15 -0
  139. package/dist/events-data.js +9 -0
  140. package/dist/events-editor.d.ts +2 -0
  141. package/dist/events-editor.js +2 -0
  142. package/dist/events.d.ts +19 -0
  143. package/dist/events.js +17 -0
  144. package/package.json +46 -4
@@ -0,0 +1,22 @@
1
+ import { type ScheduleEvent } from './schedule-utils.js';
2
+ import type { EditorAdapter, EditorViewer } from '../editor/adapter.js';
3
+ type VodInfo = {
4
+ playlistUrl: string;
5
+ subtitlesUrl?: string;
6
+ };
7
+ type $$ComponentProps = {
8
+ scheduleEvents: ScheduleEvent[];
9
+ tz: string;
10
+ eventActor: string;
11
+ adapter: EditorAdapter;
12
+ viewer: EditorViewer;
13
+ /** Explicit room ordering from the conference event, if declared. */
14
+ rooms?: string[];
15
+ rsvpStatuses?: Record<string, string>;
16
+ rsvpRkeys?: Record<string, string>;
17
+ eventVods?: Record<string, VodInfo>;
18
+ loggedIn?: boolean;
19
+ };
20
+ declare const ConferenceTimetable: import("svelte").Component<$$ComponentProps, {}, "">;
21
+ type ConferenceTimetable = ReturnType<typeof ConferenceTimetable>;
22
+ export default ConferenceTimetable;
@@ -0,0 +1,298 @@
1
+ <script lang="ts">
2
+ import { Icon } from '../../index.js';
3
+ import { Check, ChevronDown } from '@sketchyicons/svelte';
4
+ import ScheduleEventCell from './ScheduleEventCell.svelte';
5
+ import {
6
+ type ScheduleEvent,
7
+ type GridData,
8
+ SLOT,
9
+ linkableTypes,
10
+ formatHour,
11
+ getDayKey,
12
+ getNowGridRow
13
+ } from './schedule-utils.js';
14
+ import type { EditorAdapter, EditorViewer } from '../editor/adapter.js';
15
+
16
+ type VodInfo = { playlistUrl: string; subtitlesUrl?: string };
17
+
18
+ let {
19
+ grid,
20
+ rooms,
21
+ dayEvents,
22
+ tz,
23
+ eventActor,
24
+ adapter,
25
+ viewer,
26
+ activeRoom = $bindable(0),
27
+ nowKey,
28
+ nowMinutes,
29
+ rsvpStatuses = {},
30
+ rsvpRkeys = {},
31
+ eventVods = {},
32
+ dimUnattended = false,
33
+ onrsvpchange
34
+ }: {
35
+ grid: GridData;
36
+ rooms: string[];
37
+ dayEvents: ScheduleEvent[];
38
+ tz: string;
39
+ eventActor: string;
40
+ adapter: EditorAdapter;
41
+ viewer: EditorViewer;
42
+ activeRoom: number;
43
+ nowKey: string;
44
+ nowMinutes: number;
45
+ rsvpStatuses?: Record<string, string>;
46
+ rsvpRkeys?: Record<string, string>;
47
+ eventVods?: Record<string, VodInfo>;
48
+ dimUnattended?: boolean;
49
+ onrsvpchange?: (uri: string, status: string | null, rkey?: string) => void;
50
+ } = $props();
51
+
52
+ let dayKey = $derived(dayEvents[0]?.start ? getDayKey(dayEvents[0].start, tz) : '');
53
+ let nowRow = $derived(getNowGridRow(grid, dayKey, nowKey, nowMinutes));
54
+
55
+ // Mobile shows one room at a time, picked from a dropdown (there can be many).
56
+ let roomMenuOpen = $state(false);
57
+
58
+ function isDimmed(event: { uri: string; type: string }): boolean {
59
+ if (dimUnattended) {
60
+ if (event.type === 'info') return true;
61
+ const status = rsvpStatuses[event.uri];
62
+ return !status || status === 'notgoing';
63
+ }
64
+ return false;
65
+ }
66
+ </script>
67
+
68
+ <div
69
+ class="overflow-hidden rounded-xl border border-base-200 bg-base-100/60 dark:border-base-800 dark:bg-base-950/50"
70
+ >
71
+ <!-- Mobile: room selector (one room shown at a time) -->
72
+ <div class="relative border-b border-base-200 md:hidden dark:border-base-800">
73
+ <button
74
+ type="button"
75
+ onclick={() => (roomMenuOpen = !roomMenuOpen)}
76
+ class="flex w-full items-center justify-between gap-2 px-3 py-2.5"
77
+ >
78
+ <span
79
+ class="truncate text-xs font-semibold tracking-wide text-accent-700 uppercase dark:text-accent-300"
80
+ >
81
+ {rooms[activeRoom] ?? ''}
82
+ </span>
83
+ <span class="flex shrink-0 items-center gap-2 text-base-400 dark:text-base-500">
84
+ <span class="text-xs tabular-nums">{activeRoom + 1}/{rooms.length}</span>
85
+ <Icon
86
+ img={ChevronDown}
87
+ size={16}
88
+ class="transition-transform {roomMenuOpen ? 'rotate-180' : ''}"
89
+ />
90
+ </span>
91
+ </button>
92
+ {#if roomMenuOpen}
93
+ <button
94
+ type="button"
95
+ class="fixed inset-0 z-[9990] cursor-default"
96
+ aria-label="Close room menu"
97
+ onclick={() => (roomMenuOpen = false)}
98
+ ></button>
99
+ <ul
100
+ class="absolute inset-x-0 top-full z-[10000] max-h-64 overflow-auto border-b border-line bg-page"
101
+ >
102
+ {#each rooms as room, i (room)}
103
+ <li>
104
+ <button
105
+ type="button"
106
+ onclick={() => {
107
+ activeRoom = i;
108
+ roomMenuOpen = false;
109
+ }}
110
+ class="flex w-full items-center justify-between gap-2 px-3 py-2 text-left text-xs transition-colors {activeRoom ===
111
+ i
112
+ ? 'bg-accent-100 font-semibold text-accent-700 dark:bg-accent-900/30 dark:text-accent-300'
113
+ : 'text-base-600 dark:text-base-300'}"
114
+ >
115
+ <span class="truncate">{room}</span>
116
+ {#if activeRoom === i}
117
+ <Icon img={Check} size={16} class="shrink-0" />
118
+ {/if}
119
+ </button>
120
+ </li>
121
+ {/each}
122
+ </ul>
123
+ {/if}
124
+ </div>
125
+
126
+ <!-- Mobile: single room view -->
127
+ {#each rooms as room, roomIndex (room)}
128
+ {@const roomEvents = grid.gridEvents.filter((e) => e.type === 'info' || e.room === room)}
129
+ <div class="md:hidden {activeRoom !== roomIndex ? 'hidden' : ''}">
130
+ <div class="flex">
131
+ <div class="w-14 shrink-0 border-r border-base-200 dark:border-base-800"></div>
132
+ <div class="relative grid flex-auto grid-cols-1 grid-rows-1">
133
+ <div
134
+ class="col-start-1 col-end-2 row-start-1 grid divide-y divide-base-200/40 dark:divide-base-800/40"
135
+ style={grid.timeGridRows}
136
+ >
137
+ <div></div>
138
+ <div></div>
139
+ {#each Array(grid.totalSlots).keys() as i (i)}
140
+ <div>
141
+ {#if (grid.minTime + i * SLOT) % 60 === 0}
142
+ <div
143
+ class="sticky left-0 z-[1500] -mt-2 -ml-14 w-14 pr-2 text-right text-[0.65rem] leading-none text-base-400 dark:text-base-500"
144
+ >
145
+ {formatHour(grid.minTime + i * SLOT)}
146
+ </div>
147
+ {/if}
148
+ </div>
149
+ {/each}
150
+ </div>
151
+ <ol
152
+ class="col-start-1 col-end-2 row-start-1 grid divide-y divide-base-200/40 dark:divide-base-800/40"
153
+ style="grid-template-columns: 1fr; {grid.timeGridRows}"
154
+ >
155
+ {#each roomEvents as event (event.uri)}
156
+ <li
157
+ class="relative flex min-h-5 p-0.5 transition-opacity {isDimmed(event)
158
+ ? linkableTypes.has(event.type) && event.rkey
159
+ ? 'opacity-30 hover:opacity-80'
160
+ : 'opacity-30'
161
+ : ''}"
162
+ style="grid-row: {event.startRow} / span {event.spanRows}; grid-column: 1; z-index: {event.zIndex}"
163
+ >
164
+ <ScheduleEventCell
165
+ {event}
166
+ {tz}
167
+ {eventActor}
168
+ {adapter}
169
+ {viewer}
170
+ {rsvpStatuses}
171
+ {rsvpRkeys}
172
+ vodPlaylistUrl={eventVods[event.uri]?.playlistUrl}
173
+ vodSubtitlesUrl={eventVods[event.uri]?.subtitlesUrl}
174
+ {onrsvpchange}
175
+ />
176
+ </li>
177
+ {/each}
178
+ {#if nowRow}
179
+ <li
180
+ data-now-line
181
+ class="pointer-events-none relative"
182
+ style="z-index: 9999; grid-row: {nowRow.row}; grid-column: 1"
183
+ >
184
+ <div
185
+ class="absolute inset-x-0 flex items-center"
186
+ style="top: {nowRow.offsetPercent}%"
187
+ >
188
+ <div class="size-2 rounded-full bg-red-500"></div>
189
+ <div class="h-0.5 flex-1 bg-red-500"></div>
190
+ </div>
191
+ </li>
192
+ {/if}
193
+ </ol>
194
+ </div>
195
+ </div>
196
+ </div>
197
+ {/each}
198
+
199
+ <!-- Desktop: full grid -->
200
+ <div class="hidden overflow-x-auto md:block">
201
+ <div style="min-width: {rooms.length * 10 + 4}rem">
202
+ <div
203
+ class="flex border-b border-base-200 bg-base-100/60 dark:border-base-800 dark:bg-base-950/50"
204
+ >
205
+ <div class="w-14 shrink-0 border-r border-base-200 dark:border-base-800"></div>
206
+ <div
207
+ class="grid flex-auto divide-x divide-base-200 dark:divide-base-800"
208
+ style="grid-template-columns: repeat({rooms.length}, minmax(0, 1fr))"
209
+ >
210
+ {#each rooms as room (room)}
211
+ <div
212
+ class="px-2 py-2.5 text-center text-xs font-semibold tracking-wide text-muted uppercase"
213
+ >
214
+ {room}
215
+ </div>
216
+ {/each}
217
+ </div>
218
+ </div>
219
+
220
+ <div class="flex">
221
+ <div class="w-14 shrink-0 border-r border-base-200 dark:border-base-800"></div>
222
+ <div class="relative grid flex-auto grid-cols-1 grid-rows-1">
223
+ <div
224
+ class="col-start-1 col-end-2 row-start-1 grid divide-y divide-base-200/40 dark:divide-base-800/40"
225
+ style={grid.timeGridRows}
226
+ >
227
+ <div></div>
228
+ <div></div>
229
+ {#each Array(grid.totalSlots).keys() as i (i)}
230
+ <div>
231
+ {#if (grid.minTime + i * SLOT) % 60 === 0}
232
+ <div
233
+ class="sticky left-0 z-[1500] -mt-2 -ml-14 w-14 pr-2 text-right text-[0.65rem] leading-none text-base-400 dark:text-base-500"
234
+ >
235
+ {formatHour(grid.minTime + i * SLOT)}
236
+ </div>
237
+ {/if}
238
+ </div>
239
+ {/each}
240
+ </div>
241
+
242
+ <div
243
+ class="col-start-1 col-end-2 row-start-1 grid grid-rows-1 divide-x divide-base-200/40 dark:divide-base-800/40"
244
+ style="grid-template-columns: repeat({rooms.length}, minmax(0, 1fr))"
245
+ >
246
+ {#each rooms as room (room)}
247
+ <div class="row-span-full"></div>
248
+ {/each}
249
+ </div>
250
+
251
+ <ol
252
+ class="col-start-1 col-end-2 row-start-1 grid divide-y divide-base-200/40 dark:divide-base-800/40"
253
+ style="grid-template-columns: repeat({rooms.length}, minmax(0, 1fr)); {grid.timeGridRows}"
254
+ >
255
+ {#each grid.gridEvents as event (event.uri)}
256
+ <li
257
+ class="relative flex min-h-5 p-0.5 transition-opacity {isDimmed(event)
258
+ ? linkableTypes.has(event.type) && event.rkey
259
+ ? 'opacity-30 hover:opacity-80'
260
+ : 'opacity-30'
261
+ : ''}"
262
+ style="grid-row: {event.startRow} / span {event.spanRows}; grid-column: {event.colStart} / span {event.colSpan}; z-index: {event.zIndex}"
263
+ >
264
+ <ScheduleEventCell
265
+ {event}
266
+ {tz}
267
+ {eventActor}
268
+ {adapter}
269
+ {viewer}
270
+ {rsvpStatuses}
271
+ {rsvpRkeys}
272
+ vodPlaylistUrl={eventVods[event.uri]?.playlistUrl}
273
+ vodSubtitlesUrl={eventVods[event.uri]?.subtitlesUrl}
274
+ {onrsvpchange}
275
+ />
276
+ </li>
277
+ {/each}
278
+ {#if nowRow}
279
+ <li
280
+ data-now-line
281
+ class="pointer-events-none relative"
282
+ style="z-index: 9999; grid-row: {nowRow.row}; grid-column: 1 / span {rooms.length}"
283
+ >
284
+ <div
285
+ class="absolute inset-x-0 flex items-center"
286
+ style="top: {nowRow.offsetPercent}%"
287
+ >
288
+ <div class="size-2 rounded-full bg-red-500"></div>
289
+ <div class="h-0.5 flex-1 bg-red-500"></div>
290
+ </div>
291
+ </li>
292
+ {/if}
293
+ </ol>
294
+ </div>
295
+ </div>
296
+ </div>
297
+ </div>
298
+ </div>
@@ -0,0 +1,26 @@
1
+ import { type ScheduleEvent, type GridData } from './schedule-utils.js';
2
+ import type { EditorAdapter, EditorViewer } from '../editor/adapter.js';
3
+ type VodInfo = {
4
+ playlistUrl: string;
5
+ subtitlesUrl?: string;
6
+ };
7
+ type $$ComponentProps = {
8
+ grid: GridData;
9
+ rooms: string[];
10
+ dayEvents: ScheduleEvent[];
11
+ tz: string;
12
+ eventActor: string;
13
+ adapter: EditorAdapter;
14
+ viewer: EditorViewer;
15
+ activeRoom: number;
16
+ nowKey: string;
17
+ nowMinutes: number;
18
+ rsvpStatuses?: Record<string, string>;
19
+ rsvpRkeys?: Record<string, string>;
20
+ eventVods?: Record<string, VodInfo>;
21
+ dimUnattended?: boolean;
22
+ onrsvpchange?: (uri: string, status: string | null, rkey?: string) => void;
23
+ };
24
+ declare const DaySchedule: import("svelte").Component<$$ComponentProps, {}, "activeRoom">;
25
+ type DaySchedule = ReturnType<typeof DaySchedule>;
26
+ export default DaySchedule;
@@ -0,0 +1,166 @@
1
+ <script lang="ts">
2
+ import {
3
+ type GridEvent,
4
+ linkableTypes,
5
+ isCompact,
6
+ getEventColor,
7
+ formatTime
8
+ } from './schedule-utils.js';
9
+ import { Check, Star, Video } from '@sketchyicons/svelte';
10
+ import { Modal, Button, Icon } from '../../index.js';
11
+ import EventRsvp from '../EventRsvp.svelte';
12
+ import VodPlayer from '../VodPlayer.svelte';
13
+ import type { EditorAdapter, EditorViewer } from '../editor/adapter.js';
14
+ import type { Snippet } from 'svelte';
15
+
16
+ let {
17
+ event,
18
+ tz,
19
+ eventActor,
20
+ adapter,
21
+ viewer,
22
+ rsvpStatuses = {},
23
+ rsvpRkeys = {},
24
+ vodPlaylistUrl,
25
+ vodSubtitlesUrl,
26
+ onrsvpchange,
27
+ trigger
28
+ }: {
29
+ event: GridEvent;
30
+ /** Timezone the schedule is laid out in. */
31
+ tz: string;
32
+ /** Handle/DID used to build the talk's event-page link. */
33
+ eventActor: string;
34
+ adapter: EditorAdapter;
35
+ viewer: EditorViewer;
36
+ rsvpStatuses?: Record<string, string>;
37
+ rsvpRkeys?: Record<string, string>;
38
+ vodPlaylistUrl?: string;
39
+ vodSubtitlesUrl?: string;
40
+ onrsvpchange?: (uri: string, status: string | null, rkey?: string) => void;
41
+ /** Custom clickable trigger; receives `open` to launch the modal. Defaults
42
+ * to the standard colored grid-cell button. */
43
+ trigger?: Snippet<[{ open: () => void }]>;
44
+ } = $props();
45
+
46
+ let modalOpen = $state(false);
47
+
48
+ let durationMin = $derived(event.endMin - event.startMin);
49
+ let compact = $derived(isCompact(durationMin, event.type));
50
+ let initialRsvpStatus = $derived(
51
+ (rsvpStatuses[event.uri] as 'going' | 'interested' | 'notgoing' | undefined) ?? null
52
+ );
53
+ let initialRsvpRkey = $derived(rsvpRkeys[event.uri] ?? null);
54
+ let isPast = $derived(event.end ? new Date(event.end) < new Date() : false);
55
+ let eventHref = $derived(`/p/${eventActor}/e/${event.rkey}`);
56
+ </script>
57
+
58
+ {#if linkableTypes.has(event.type) && event.rkey}
59
+ {#if trigger}
60
+ {@render trigger({ open: () => (modalOpen = true) })}
61
+ {:else}
62
+ <button
63
+ onclick={() => (modalOpen = true)}
64
+ class="relative flex-1 cursor-pointer overflow-hidden rounded-md leading-tight transition-[filter] hover:brightness-95 {getEventColor(
65
+ event.type
66
+ )} {event.type === 'info'
67
+ ? 'flex flex-col items-center justify-center px-2 py-1.5 text-center text-xs'
68
+ : ''} {compact ? 'px-1.5 py-0 text-[0.6rem]' : 'px-2 py-1.5 text-xs'}"
69
+ >
70
+ <p class="font-semibold {durationMin <= 30 ? 'line-clamp-1' : ''}">
71
+ {event.title}
72
+ </p>
73
+ {#if event.speakers?.length && !compact}
74
+ <p class="mt-0.5 opacity-75 {durationMin < 60 ? 'line-clamp-1' : ''}">
75
+ {event.speakers.map((s) => s.name).join(', ')}
76
+ </p>
77
+ {/if}
78
+ {#if vodPlaylistUrl}
79
+ <Icon img={Video} size={12} class="absolute top-1 right-1 opacity-60" />
80
+ {/if}
81
+ {#if initialRsvpStatus === 'going'}
82
+ <Icon img={Check} size={12} class="absolute right-1 bottom-1 opacity-60" />
83
+ {:else if initialRsvpStatus === 'interested'}
84
+ <Icon img={Star} size={12} class="absolute right-1 bottom-1 opacity-60" />
85
+ {/if}
86
+ </button>
87
+ {/if}
88
+
89
+ <Modal bind:open={modalOpen} title={event.title}>
90
+ <div class="overflow-hidden">
91
+ {#if event.start}
92
+ <p class="mt-1 text-sm text-muted">
93
+ {formatTime(event.start, tz)}{event.end ? ` – ${formatTime(event.end, tz)}` : ''}
94
+ </p>
95
+ {/if}
96
+
97
+ {#if event.speakers?.length}
98
+ <p class="mt-1 text-sm text-base-600 dark:text-base-300">
99
+ {#each event.speakers as s, i (s.id ?? s.name)}{#if s.id}<a
100
+ href="https://bsky.app/profile/{s.id}"
101
+ target="_blank"
102
+ rel="noopener noreferrer"
103
+ class="text-accent-600 hover:underline dark:text-accent-400">{s.name}</a
104
+ >{:else}{s.name}{/if}{#if i < event.speakers.length - 1},
105
+ {/if}{/each}
106
+ </p>
107
+ {/if}
108
+
109
+ {#if event.description}
110
+ <p class="mt-3 line-clamp-3 text-sm break-words text-muted">
111
+ {event.description}
112
+ </p>
113
+ {/if}
114
+
115
+ {#if !isPast}
116
+ <EventRsvp
117
+ eventUri={event.uri}
118
+ eventCid={event.cid ?? null}
119
+ rsvpExpected={event.rsvpExpected}
120
+ {initialRsvpStatus}
121
+ {initialRsvpRkey}
122
+ {adapter}
123
+ {viewer}
124
+ onlogin={() => (modalOpen = false)}
125
+ onrsvp={(status, key) => {
126
+ onrsvpchange?.(event.uri, status, key);
127
+ modalOpen = false;
128
+ }}
129
+ oncancel={() => {
130
+ onrsvpchange?.(event.uri, null);
131
+ }}
132
+ />
133
+ {/if}
134
+
135
+ {#if vodPlaylistUrl}
136
+ <div class="mt-3">
137
+ <VodPlayer
138
+ playlistUrl={vodPlaylistUrl}
139
+ title={event.title}
140
+ subtitlesUrl={vodSubtitlesUrl}
141
+ />
142
+ </div>
143
+ {/if}
144
+ <Button href={eventHref} variant="secondary" class="mt-2 w-full">Go to event</Button>
145
+ </div>
146
+ </Modal>
147
+ {:else}
148
+ <div
149
+ class="flex-1 overflow-hidden rounded-md leading-tight {getEventColor(
150
+ event.type
151
+ )} {event.type === 'info'
152
+ ? durationMin <= 30
153
+ ? 'flex items-center justify-center gap-2 px-2 py-0.5 text-center text-xs'
154
+ : 'flex flex-col items-center justify-center px-2 py-1.5 text-center text-xs'
155
+ : ''} {compact ? 'px-1.5 py-0 text-[0.6rem]' : 'px-2 py-1.5 text-xs'}"
156
+ >
157
+ <p class="font-semibold {durationMin <= 30 ? 'line-clamp-1' : ''}">
158
+ {event.title}
159
+ </p>
160
+ {#if event.start}
161
+ <p class="{durationMin <= 30 ? '' : 'mt-0.5'} shrink-0 opacity-75">
162
+ {formatTime(event.start, tz)}{event.end ? ` – ${formatTime(event.end, tz)}` : ''}
163
+ </p>
164
+ {/if}
165
+ </div>
166
+ {/if}
@@ -0,0 +1,25 @@
1
+ import { type GridEvent } from './schedule-utils.js';
2
+ import type { EditorAdapter, EditorViewer } from '../editor/adapter.js';
3
+ import type { Snippet } from 'svelte';
4
+ type $$ComponentProps = {
5
+ event: GridEvent;
6
+ /** Timezone the schedule is laid out in. */
7
+ tz: string;
8
+ /** Handle/DID used to build the talk's event-page link. */
9
+ eventActor: string;
10
+ adapter: EditorAdapter;
11
+ viewer: EditorViewer;
12
+ rsvpStatuses?: Record<string, string>;
13
+ rsvpRkeys?: Record<string, string>;
14
+ vodPlaylistUrl?: string;
15
+ vodSubtitlesUrl?: string;
16
+ onrsvpchange?: (uri: string, status: string | null, rkey?: string) => void;
17
+ /** Custom clickable trigger; receives `open` to launch the modal. Defaults
18
+ * to the standard colored grid-cell button. */
19
+ trigger?: Snippet<[{
20
+ open: () => void;
21
+ }]>;
22
+ };
23
+ declare const ScheduleEventCell: import("svelte").Component<$$ComponentProps, {}, "">;
24
+ type ScheduleEventCell = ReturnType<typeof ScheduleEventCell>;
25
+ export default ScheduleEventCell;
@@ -0,0 +1,44 @@
1
+ import type { ScheduleEvent } from '../conference.js';
2
+ export type { ScheduleEvent };
3
+ export interface GridEvent extends ScheduleEvent {
4
+ startMin: number;
5
+ endMin: number;
6
+ startRow: number;
7
+ spanRows: number;
8
+ colStart: number;
9
+ colSpan: number;
10
+ zIndex: number;
11
+ }
12
+ export interface GridData {
13
+ gridEvents: GridEvent[];
14
+ minTime: number;
15
+ totalSlots: number;
16
+ timeGridRows: string;
17
+ }
18
+ export declare const SLOT = 5;
19
+ export declare const SLOT_HEIGHT = "0.6rem";
20
+ export declare const linkableTypes: Set<string>;
21
+ export declare function isLightning(type: string): boolean;
22
+ export declare function isCompact(durationMin: number, type: string): boolean;
23
+ /**
24
+ * Wall-clock components of an instant as seen in `tz`, returned as a Date built
25
+ * from those local parts (so getHours()/getMinutes() read the tz time, not the
26
+ * runtime's). Used to lay the grid out in the conference's own timezone.
27
+ */
28
+ export declare function toTzDate(iso: string, tz: string): Date;
29
+ export declare function isoToMinutes(iso: string, tz: string): number;
30
+ export declare function formatTime(iso: string, tz: string): string;
31
+ /** Hour label for a "minutes since midnight" value (0–1439). Timezone-agnostic. */
32
+ export declare function formatHour(minutes: number): string;
33
+ export declare function getDayKey(iso: string, tz: string): string;
34
+ export declare function getDayLabel(iso: string, tz: string): string;
35
+ export declare function getDayShortLabel(iso: string, tz: string): string;
36
+ export declare function getEventColor(type: string): string;
37
+ export declare function durationMinutes(start: string, end: string | undefined, tz: string): number;
38
+ /** Rooms present in a set of events, in display order. */
39
+ export declare function getRooms(events: ScheduleEvent[], explicitOrder?: string[]): string[];
40
+ export declare function buildGrid(events: ScheduleEvent[], rooms: string[], tz: string): GridData;
41
+ export declare function getNowGridRow(grid: GridData, dayKey: string, nowKey: string, nowMinutes: number): {
42
+ row: number;
43
+ offsetPercent: number;
44
+ } | null;