feat: polls (#210)
This commit is contained in:
parent
0e7076b991
commit
9af2956b1e
|
@ -85,6 +85,7 @@ const timeago = useTimeAgo(() => status.createdAt, timeAgoOptions)
|
|||
{{ status.spoilerText }}
|
||||
</template>
|
||||
<StatusBody :status="status" />
|
||||
<StatusPoll v-if="status.poll" :poll="status.poll" />
|
||||
<StatusMedia
|
||||
v-if="status.mediaAttachments?.length"
|
||||
:status="status"
|
||||
|
|
|
@ -30,6 +30,7 @@ const visibility = $computed(() => STATUS_VISIBILITIES.find(v => v.value === sta
|
|||
{{ status.spoilerText }}
|
||||
</template>
|
||||
<StatusBody :status="status" :with-action="false" text-2xl />
|
||||
<StatusPoll v-if="status.poll" :poll="status.poll" />
|
||||
<StatusMedia
|
||||
v-if="status.mediaAttachments?.length"
|
||||
:status="status"
|
||||
|
|
63
components/status/StatusPoll.vue
Normal file
63
components/status/StatusPoll.vue
Normal file
|
@ -0,0 +1,63 @@
|
|||
<script setup lang="ts">
|
||||
import type { Poll } from 'masto'
|
||||
|
||||
const { poll: _poll } = defineProps<{
|
||||
poll: Poll
|
||||
}>()
|
||||
const poll = reactive({ ..._poll })
|
||||
|
||||
function toPercentage(num: number) {
|
||||
const percentage = 100 * num
|
||||
return `${percentage.toFixed(1).replace(/\.?0+$/, '')}%`
|
||||
}
|
||||
const expiredTimeAgo = useTimeAgo(poll.expiresAt!)
|
||||
|
||||
const masto = useMasto()
|
||||
async function vote(e: Event) {
|
||||
const formData = new FormData(e.target as HTMLFormElement)
|
||||
const choices = formData.getAll('choices') as string[]
|
||||
await masto.poll.vote(poll.id, { choices })
|
||||
|
||||
// Update the poll optimistically
|
||||
for (const [index, option] of poll.options.entries()) {
|
||||
if (choices.includes(String(index)))
|
||||
option.votesCount = (option.votesCount || 0) + 1
|
||||
}
|
||||
poll.voted = true
|
||||
poll.votesCount++
|
||||
poll.votersCount = (poll.votersCount || 0) + 1
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div flex flex-col w-full items-stretch gap-3>
|
||||
<form v-if="!poll.voted && !poll.expired" flex flex-col gap-4 accent-primary @submit.prevent="vote">
|
||||
<label v-for="(option, index) of poll.options" :key="index" flex items-center gap-2 px-2>
|
||||
<input name="choices" :value="index" :type="poll.multiple ? 'checkbox' : 'radio'">
|
||||
{{ option.title }}
|
||||
</label>
|
||||
<button btn-solid>
|
||||
Vote
|
||||
</button>
|
||||
</form>
|
||||
<template v-else>
|
||||
<div v-for="(option, index) of poll.options" :key="index" flex justify-between p-1 relative :style="{ '--bar-width': toPercentage((option.votesCount || 0) / poll.votesCount) }">
|
||||
<div absolute top-0 left-0 bottom-0 bg-primary-active rounded-lg h-full class="w-[var(--bar-width)]" />
|
||||
<div z-1 flex items-center gap-1 px-1>
|
||||
<div>
|
||||
{{ option.title }}
|
||||
</div>
|
||||
<div v-if="poll.voted && poll.ownVotes?.includes(index)">
|
||||
<div i-ri:checkbox-circle-line />
|
||||
</div>
|
||||
</div>
|
||||
<div z-1>
|
||||
{{ poll.votesCount ? toPercentage((option.votesCount || 0) / (poll.votesCount)) : '0%' }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div text-sm>
|
||||
{{ poll.votersCount }} votes · {{ poll.expired ? 'finished' : 'ends' }} {{ expiredTimeAgo }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
Loading…
Reference in a new issue