flotilla/src/app/components/PollVotes.svelte

122 lines
3.4 KiB
Svelte
Raw Normal View History

2026-04-03 17:54:50 +00:00
<script lang="ts">
import {onDestroy} from "svelte"
import {formatTimestampRelative} from "@welshman/lib"
2026-07-28 16:16:26 +00:00
import type {TrustedEvent} from "@welshman/util"
import {POLL_RESPONSE, tagSpec, tagValues} from "@welshman/util"
import {Poll, PollResponse} from "@welshman/domain"
import type {Thunk} from "@welshman/app"
2026-04-03 17:54:50 +00:00
import PollOption from "@app/components/PollOption.svelte"
2026-07-28 16:16:26 +00:00
import {command, reader, thunks, user, writer} from "@app/core"
import {deriveEvents} from "@app/repository"
2026-04-03 17:54:50 +00:00
type Props = {
url: string
event: TrustedEvent
}
const {url, event}: Props = $props()
2026-04-16 18:30:59 +00:00
const responses = deriveEvents([{kinds: [POLL_RESPONSE], "#e": [event.id]}])
2026-04-03 17:54:50 +00:00
const getOwnResponse = (responses: TrustedEvent[]) => {
let latest: TrustedEvent | undefined
for (const response of responses) {
2026-07-28 16:16:26 +00:00
if (
response.pubkey === $user.pubkey &&
(!latest || response.created_at > latest.created_at)
) {
2026-04-03 17:54:50 +00:00
latest = response
}
}
return latest
}
2026-07-28 16:16:26 +00:00
const publishSelection = async (selection: string[]) => {
activeThunk?.abort()
activeThunk = undefined
2026-04-03 17:54:50 +00:00
2026-07-28 16:16:26 +00:00
if (selection.length > 0) {
const eventWriter = writer(PollResponse).forceRelays(url).setPollId(event.id)
2026-04-03 17:54:50 +00:00
2026-07-28 16:16:26 +00:00
for (const id of selection) {
eventWriter.addSelection(id)
}
const responseCommand = await command(eventWriter)
// Give the user time to check more boxes before the vote actually goes out.
activeThunk = $thunks.publish({
event: responseCommand.event,
relays: responseCommand.relays,
delay: pollType === "multiplechoice" ? 1000 : undefined,
})
}
2026-04-03 17:54:50 +00:00
}
const publishCurrentSelection = () =>
publishSelection(pollType === "singlechoice" ? selectedIds.slice(0, 1) : selectedIds)
2026-04-03 17:54:50 +00:00
const setSingleChoice = (id: string) => {
selectedIds = [id]
publishCurrentSelection()
}
const toggleMultipleChoice = (id: string) => {
selectedIds = selectedIds.includes(id)
? selectedIds.filter(selectedId => selectedId !== id)
: [...selectedIds, id]
publishCurrentSelection()
}
2026-07-28 16:16:26 +00:00
const poll = reader(Poll)(event)
2026-04-03 17:54:50 +00:00
let selectedIds = $state<string[]>([])
2026-07-28 16:16:26 +00:00
let activeThunk: Thunk | undefined
const pollType = poll.pollType()
const results = $derived(poll.results($responses))
const ownResponse = $derived(getOwnResponse($responses))
2026-04-03 17:54:50 +00:00
$effect(() => {
if (ownResponse) {
2026-07-28 16:16:26 +00:00
const selections = tagValues(tagSpec("response"), ownResponse.tags)
selectedIds = pollType === "singlechoice" ? selections.slice(0, 1) : selections
2026-04-03 17:54:50 +00:00
}
})
onDestroy(() => {
2026-07-28 16:16:26 +00:00
activeThunk?.abort()
2026-04-03 17:54:50 +00:00
})
</script>
2026-07-28 16:16:26 +00:00
{#if poll && results}
{@const endsAt = poll.endsAt()}
<div class="flex flex-col gap-2">
{#each poll.options() as option (option.id)}
<PollOption
{event}
{option}
{results}
{selectedIds}
{setSingleChoice}
{toggleMultipleChoice} />
{/each}
<div class="flex flex-wrap items-center justify-between gap-2">
<div class="text-sm opacity-75">
{pollType === "multiplechoice" ? "Multiple choice" : "Single choice"}
{#if endsAt}
{#if poll.isClosed()}
• Ended {formatTimestampRelative(endsAt)}
{:else}
• Ends {formatTimestampRelative(endsAt)}
{/if}
2026-04-03 17:54:50 +00:00
{/if}
2026-07-28 16:16:26 +00:00
</div>
<div class="text-sm opacity-75">{results.voters} vote{results.voters === 1 ? "" : "s"}</div>
2026-04-03 17:54:50 +00:00
</div>
</div>
2026-07-28 16:16:26 +00:00
{/if}