154 lines
5.2 KiB
Svelte
154 lines
5.2 KiB
Svelte
<script module lang="ts">
|
|
type EventTab = "about" | "discussion" | "people"
|
|
</script>
|
|
|
|
<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"
|
|
import {getCommentFiltersForRoot} from "@welshman/util"
|
|
import {TimeEvent} from "@welshman/domain"
|
|
import PageContent from "@lib/components/PageContent.svelte"
|
|
import Spinner from "@lib/components/Spinner.svelte"
|
|
import Button from "@lib/components/Button.svelte"
|
|
import SpaceBar from "@app/components/SpaceBar.svelte"
|
|
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"
|
|
|
|
const {params}: PageProps = $props()
|
|
|
|
const {relay, address} = params
|
|
const url = decodeRelay(relay)
|
|
const context = makeFeedContext({relays: [url]})
|
|
const event = $events.one(address, [url]).$
|
|
const timeEvent = derived(event, $event => ($event ? reader(TimeEvent)($event) : undefined))
|
|
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))
|
|
|
|
const back = () => history.back()
|
|
|
|
const showTab = (target: EventTab) => () => {
|
|
tab = target
|
|
}
|
|
|
|
const tabClass = (target: EventTab) =>
|
|
cx("button button-sm join-item grow", tab === target ? "button-primary" : "button-neutral")
|
|
|
|
onDestroy(context.cleanup)
|
|
|
|
let tab = $state<EventTab>("about")
|
|
|
|
$effect(() => {
|
|
if (filters.length > 0) {
|
|
const controller = new AbortController()
|
|
|
|
$network.request({relays: [url], filters, signal: controller.signal})
|
|
|
|
return () => controller.abort()
|
|
}
|
|
})
|
|
|
|
$effect(() => {
|
|
if ($event) {
|
|
const controller = new AbortController()
|
|
|
|
$network.request({
|
|
relays: [url],
|
|
filters: [makeRsvpFilter($event)],
|
|
signal: controller.signal,
|
|
})
|
|
|
|
return () => controller.abort()
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<SpaceBar {back}>
|
|
{#snippet title()}
|
|
<h1 class="text-xl">{$timeEvent?.title() ?? ""}</h1>
|
|
{/snippet}
|
|
</SpaceBar>
|
|
|
|
<PageContent class="flex flex-col gap-2 p-2 sm:gap-4 sm:p-4">
|
|
{#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} />
|
|
</div>
|
|
</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}
|
|
</div>
|
|
{:else if tab === "discussion"}
|
|
<EventComments {url} event={$event} {context} />
|
|
{:else}
|
|
<CalendarEventPeople {url} event={$event} rsvps={$rsvps} />
|
|
{/if}
|
|
{:else}
|
|
<div class="flex justify-center py-20">
|
|
{#await sleep(5000)}
|
|
<Spinner loading>Loading event...</Spinner>
|
|
{:then}
|
|
<p>Failed to load event.</p>
|
|
{/await}
|
|
</div>
|
|
{/if}
|
|
</PageContent>
|