flotilla/src/routes/spaces/[relay]/calendar/[address]/+page.svelte

155 lines
5.2 KiB
Svelte
Raw Normal View History

<script module lang="ts">
type EventTab = "about" | "discussion" | "people"
</script>
2025-02-06 17:29:30 +00:00
<script lang="ts">
import {onDestroy} from "svelte"
import cx from "classnames"
import {derived, readable} from "svelte/store"
import {sleep} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
2026-08-17 15:42:14 +00:00
import {getCommentFiltersForRoot} from "@welshman/util"
2026-07-28 16:16:26 +00:00
import {TimeEvent} from "@welshman/domain"
2025-03-19 17:30:36 +00:00
import PageContent from "@lib/components/PageContent.svelte"
2025-02-06 17:29:30 +00:00
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
import SpaceBar from "@app/components/SpaceBar.svelte"
2026-02-06 17:15:03 +00:00
import Content from "@app/components/Content.svelte"
import ContentActions from "@app/components/ContentActions.svelte"
import CalendarEventEdit from "@app/components/CalendarEventEdit.svelte"
import CalendarEventHeader from "@app/components/CalendarEventHeader.svelte"
import CalendarEventMeta from "@app/components/CalendarEventMeta.svelte"
import CalendarEventDate from "@app/components/CalendarEventDate.svelte"
import EventComments from "@app/components/EventComments.svelte"
import CalendarEventPeople from "@app/components/CalendarEventPeople.svelte"
import CalendarRsvp from "@app/components/CalendarRsvp.svelte"
import {deriveRsvps, getRsvpsByStatus, makeRsvpFilter} from "@app/calendar"
import {events, network, reader} from "@app/core"
import {makeFeedContext} from "@app/feeds"
import {decodeRelay} from "@app/relays"
import type {PageProps} from "./$types"
2025-02-06 17:29:30 +00:00
const {params}: PageProps = $props()
const {relay, address} = params
2025-02-06 17:29:30 +00:00
const url = decodeRelay(relay)
const context = makeFeedContext({relays: [url]})
const event = $events.one(address, [url]).$
2026-07-28 16:16:26 +00:00
const timeEvent = derived(event, $event => ($event ? reader(TimeEvent)($event) : undefined))
2026-08-17 15:42:14 +00:00
const filters = $derived($event ? getCommentFiltersForRoot([$event]) : [])
const comments = $derived($events.asc(filters).$)
const rsvps = $derived($event ? deriveRsvps($event) : readable<TrustedEvent[]>([]))
const people = $derived(getRsvpsByStatus($rsvps))
2025-02-06 17:29:30 +00:00
const back = () => history.back()
const showTab = (target: EventTab) => () => {
tab = target
2025-02-06 17:29:30 +00:00
}
const tabClass = (target: EventTab) =>
cx("button button-sm join-item grow", tab === target ? "button-primary" : "button-neutral")
2025-02-06 17:29:30 +00:00
onDestroy(context.cleanup)
let tab = $state<EventTab>("about")
2025-02-06 17:29:30 +00:00
2026-08-17 15:42:14 +00:00
$effect(() => {
if (filters.length > 0) {
const controller = new AbortController()
2025-04-11 16:27:19 +00:00
2026-08-17 15:42:14 +00:00
$network.request({relays: [url], filters, signal: controller.signal})
2025-02-06 17:29:30 +00:00
2026-08-17 15:42:14 +00:00
return () => controller.abort()
2025-02-06 17:29:30 +00:00
}
})
$effect(() => {
if ($event) {
const controller = new AbortController()
$network.request({
relays: [url],
filters: [makeRsvpFilter($event)],
signal: controller.signal,
})
return () => controller.abort()
}
})
2025-02-06 17:29:30 +00:00
</script>
<SpaceBar {back}>
2025-03-19 17:30:36 +00:00
{#snippet title()}
2026-07-28 16:16:26 +00:00
<h1 class="text-xl">{$timeEvent?.title() ?? ""}</h1>
2025-03-19 17:30:36 +00:00
{/snippet}
</SpaceBar>
2025-03-19 17:30:36 +00:00
2026-06-22 20:36:57 +00:00
<PageContent class="flex flex-col gap-2 p-2 sm:gap-4 sm:p-4">
2025-02-06 17:29:30 +00:00
{#if $event}
<div class="card z-feature flex items-start gap-4">
<CalendarEventDate event={$event} />
<div class="flex min-w-0 grow flex-col gap-2">
<CalendarEventHeader event={$event} />
<CalendarEventMeta event={$event} {url} />
<CalendarRsvp {url} event={$event} rsvps={$rsvps} onShowPeople={showTab("people")} />
<ContentActions
showRoom
noun="Event"
editForm={CalendarEventEdit}
{url}
event={$event}
{context} />
2025-02-06 17:29:30 +00:00
</div>
2025-02-06 19:12:15 +00:00
</div>
<!-- One event carries three unrelated conversations, so give each its own tab instead of
stacking them all down the page -->
<div class="join w-full sm:w-auto sm:self-start">
<Button class={tabClass("about")} aria-pressed={tab === "about"} onclick={showTab("about")}>
About
</Button>
<Button
class={tabClass("discussion")}
aria-pressed={tab === "discussion"}
onclick={showTab("discussion")}>
Discussion
{#if $comments.length > 0}
<span class="opacity-75">{$comments.length}</span>
{/if}
</Button>
<Button
class={tabClass("people")}
aria-pressed={tab === "people"}
onclick={showTab("people")}>
People
{#if people.latest.length > 0}
<span class="opacity-75">{people.latest.length}</span>
{/if}
</Button>
</div>
{#if tab === "about"}
<div class="card z-feature flex flex-col gap-3">
{#if $event.content.trim()}
<Content showEntire event={$event} {url} />
{:else}
<p class="flex items-center justify-center py-6 opacity-75">
The host hasn't added a description.
</p>
{/if}
2025-03-19 17:30:36 +00:00
</div>
{:else if tab === "discussion"}
<EventComments {url} event={$event} {context} />
2025-03-19 17:30:36 +00:00
{:else}
<CalendarEventPeople {url} event={$event} rsvps={$rsvps} />
2025-03-19 17:30:36 +00:00
{/if}
2025-02-06 17:29:30 +00:00
{:else}
2026-06-04 21:31:07 +00:00
<div class="flex justify-center py-20">
{#await sleep(5000)}
<Spinner loading>Loading event...</Spinner>
{:then}
<p>Failed to load event.</p>
{/await}
</div>
2025-02-06 17:29:30 +00:00
{/if}
2025-03-19 17:30:36 +00:00
</PageContent>