fix: refine poll rendering and inline voting UX
This commit is contained in:
@@ -2,11 +2,11 @@
|
||||
import type {ComponentProps} from "svelte"
|
||||
import {derived} from "svelte/store"
|
||||
import {PollResponse} from "nostr-tools/kinds"
|
||||
import ContentMinimal from "@app/components/ContentMinimal.svelte"
|
||||
import Content from "@app/components/Content.svelte"
|
||||
import {deriveEvents} from "@app/core/state"
|
||||
import {getPollResults} from "@app/util/polls"
|
||||
|
||||
const props: ComponentProps<typeof ContentMinimal> = $props()
|
||||
const props: ComponentProps<typeof Content> = $props()
|
||||
|
||||
const responses = deriveEvents([{kinds: [PollResponse], "#e": [props.event.id]}])
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-0">
|
||||
<span class="text-sm">{props.event.content || "Poll"}</span>
|
||||
<Content event={props.event} url={props.url} />
|
||||
<span class="text-xs opacity-50">{$results.voters} voter{$results.voters === 1 ? "" : "s"}</span>
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<p class="text-xl">{props.event.content || "Poll"}</p>
|
||||
<Content event={props.event} showEntire url={props.url} />
|
||||
|
||||
{#if props.url}
|
||||
<PollVotes url={props.url} event={props.event} />
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<script lang="ts">
|
||||
import {onDestroy} from "svelte"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {pubkey} from "@welshman/app"
|
||||
import {pubkey, publishThunk, abortThunk} from "@welshman/app"
|
||||
import {PollResponse} from "nostr-tools/kinds"
|
||||
import {formatTimestampRelative} from "@welshman/lib"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import {noop} from "@welshman/lib"
|
||||
import {stopPropagation} from "@lib/html"
|
||||
import {deriveEvents} from "@app/core/state"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
import {publishPollResponse} from "@app/core/commands"
|
||||
import {makePollResponse} from "@app/core/commands"
|
||||
import {
|
||||
getPollEndsAt,
|
||||
getPollOptions,
|
||||
@@ -29,6 +31,7 @@
|
||||
const options = getPollOptions(event)
|
||||
const closed = isPollClosed(event)
|
||||
const endsAt = getPollEndsAt(event)
|
||||
const publishDelay = pollType === "multiplechoice" ? 10_000 : undefined
|
||||
|
||||
const getOwnResponse = (responses: TrustedEvent[]) => {
|
||||
let latest: TrustedEvent | undefined
|
||||
@@ -46,43 +49,66 @@
|
||||
return latest
|
||||
}
|
||||
|
||||
const results = $derived(getPollResults(event, $responses))
|
||||
const ownResponse = $derived(getOwnResponse($responses))
|
||||
|
||||
const submit = async () => {
|
||||
if (closed) {
|
||||
return pushToast({theme: "error", message: "This poll is closed."})
|
||||
const publishSelection = (selection: string[]) => {
|
||||
if (activeThunk) {
|
||||
abortThunk(activeThunk)
|
||||
}
|
||||
|
||||
const selection = pollType === "singlechoice" ? [selectedIds[0]].filter(Boolean) : selectedIds
|
||||
if (selection.length === 0) {
|
||||
activeThunk = undefined
|
||||
return
|
||||
}
|
||||
|
||||
activeThunk = publishThunk({
|
||||
relays: [url],
|
||||
event: makePollResponse({event, selectedIds: selection}),
|
||||
delay: publishDelay,
|
||||
})
|
||||
}
|
||||
|
||||
const publishCurrentSelection = () => {
|
||||
const selection = pollType === "singlechoice" ? selectedIds.slice(0, 1) : selectedIds
|
||||
|
||||
if (selection.length === 0) {
|
||||
return pushToast({theme: "error", message: "Please select at least one option."})
|
||||
}
|
||||
|
||||
publishPollResponse({relays: [url], event, selectedIds: selection})
|
||||
publishSelection(selection)
|
||||
}
|
||||
|
||||
const results = $derived(getPollResults(event, $responses))
|
||||
const ownResponse = $derived(getOwnResponse($responses))
|
||||
|
||||
const setSingleChoice = (id: string) => {
|
||||
selectedIds = [id]
|
||||
publishCurrentSelection()
|
||||
}
|
||||
|
||||
const toggleMultipleChoice = (id: string) => {
|
||||
selectedIds = selectedIds.includes(id)
|
||||
? selectedIds.filter(selectedId => selectedId !== id)
|
||||
: [...selectedIds, id]
|
||||
|
||||
publishCurrentSelection()
|
||||
}
|
||||
|
||||
let selectedIds = $state<string[]>([])
|
||||
let activeThunk: ReturnType<typeof publishThunk> | undefined
|
||||
|
||||
$effect(() => {
|
||||
if (ownResponse) {
|
||||
selectedIds = getPollResponseSelections(ownResponse, pollType)
|
||||
}
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
if (activeThunk) {
|
||||
abortThunk(activeThunk)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-3 rounded-box bg-base-200 p-4">
|
||||
<div class="card2 bg-alt p-4">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<div class="text-sm opacity-75">
|
||||
{pollType === "multiplechoice" ? "Multiple choice" : "Single choice"}
|
||||
@@ -111,12 +137,14 @@
|
||||
type="radio"
|
||||
class="radio radio-primary radio-sm"
|
||||
checked={selectedIds[0] === option.id}
|
||||
onclick={stopPropagation(noop)}
|
||||
onchange={() => setSingleChoice(option.id)} />
|
||||
{:else}
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-primary checkbox-sm"
|
||||
checked={selectedIds.includes(option.id)}
|
||||
onclick={stopPropagation(noop)}
|
||||
onchange={() => toggleMultipleChoice(option.id)} />
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -130,10 +158,4 @@
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if !closed}
|
||||
<div class="flex justify-end">
|
||||
<Button class="btn btn-primary btn-sm" onclick={submit}>Cast vote</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user