-
-
Notifications
You must be signed in to change notification settings - Fork 616
Expand file tree
/
Copy pathStatusPoll.vue
More file actions
121 lines (109 loc) · 4.19 KB
/
StatusPoll.vue
File metadata and controls
121 lines (109 loc) · 4.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<script setup lang="ts">
import type { mastodon } from 'masto'
const { status } = defineProps<{
status: mastodon.v1.Status
}>()
const poll = reactive({ ...status.poll! })
function toPercentage(num: number) {
const percentage = 100 * num
return `${percentage.toFixed(1).replace(/\.?0+$/, '')}%`
}
const timeAgoOptions = useTimeAgoOptions()
const expiredTimeAgo = useTimeAgo(poll.expiresAt!, timeAgoOptions)
const expiredTimeFormatted = useFormattedDateTime(poll.expiresAt!)
const { formatPercentage } = useHumanReadableNumber()
const isLoading = ref(false)
const { client } = useMasto()
async function vote(e: Event) {
const formData = new FormData(e.target as HTMLFormElement)
const choices = formData.getAll('choices').map(i => +i) as number[]
// Update the poll optimistically
for (const [index, option] of poll.options.entries()) {
if (choices.includes(index))
option.votesCount = (option.votesCount || 0) + 1
}
poll.voted = true
poll.votesCount++
if (!poll.votersCount && poll.votesCount)
poll.votesCount = poll.votesCount + 1
else
poll.votersCount = (poll.votersCount || 0) + 1
cacheStatus({ ...status, poll }, undefined, true)
await client.value.v1.polls.$select(poll.id).votes.create({ choices })
}
async function refresh() {
if (isLoading.value) {
return
}
isLoading.value = true
try {
const newPoll = await client.value.v1.polls.$select(poll.id).fetch()
Object.assign(poll, newPoll)
cacheStatus({ ...status, poll: newPoll }, undefined, true)
}
catch (e) {
console.error(e)
}
finally {
isLoading.value = false
}
}
const votersCount = computed(() => poll.votersCount ?? poll.votesCount ?? 0)
</script>
<template>
<div flex flex-col w-full items-stretch gap-2 py3 dir="auto" class="poll-wrapper">
<form v-if="!poll.voted && !poll.expired" flex="~ col gap3" accent-primary @click.stop="noop" @submit.prevent="vote">
<label v-for="(option, index) of poll.options" :key="index" flex="~ gap2" items-center>
<input name="choices" :value="index" :type="poll.multiple ? 'checkbox' : 'radio'" cursor-pointer>
{{ option.title }}
</label>
<button btn-solid mt-1>
{{ $t('action.vote') }}
</button>
</form>
<template v-else>
<div
v-for="(option, index) of poll.options"
:key="index" py-1 relative
:style="{ '--bar-width': toPercentage(votersCount === 0 ? 0 : (option.votesCount ?? 0) / votersCount) }"
>
<div flex justify-between pb-2 w-full>
<span inline-flex align-items>
{{ option.title }}
<span v-if="poll.voted && poll.ownVotes?.includes(index)" ms-2 mt-1 inline-block i-ri:checkbox-circle-line />
</span>
<span text-primary-active> {{ formatPercentage(votersCount > 0 ? (option.votesCount || 0) / votersCount : 0) }}</span>
</div>
<div class="bg-gray/40" rounded-l-sm rounded-r-lg h-5px w-full>
<div bg-primary-active h-full min-w="1%" class="w-[var(--bar-width)]" />
</div>
</div>
</template>
<div text-sm text-secondary flex justify-between items-center gap-3>
<div flex gap-x-1 flex-wrap>
<div inline-block>
<CommonLocalizedNumber
keypath="status.poll.count"
:count="poll.votesCount"
/>
</div>
·
<div inline-block>
<CommonTooltip v-if="poll.expiresAt" :content="expiredTimeFormatted" class="inline-block" placement="right">
<time :datetime="poll.expiresAt!">{{ $t(poll.expired ? 'status.poll.finished' : 'status.poll.ends', [expiredTimeAgo]) }}</time>
</CommonTooltip>
</div>
</div>
<div v-if="!poll.expired" flex gap-x-2>
<button v-if="!poll.voted" whitespace-nowrap flex gap-1 items-center hover:text-primary @click="refresh">
<div text-xs i-ri:information-line />
{{ $t('status.poll.view_results') }}
</button>
<button whitespace-nowrap flex gap-1 items-center hover:text-primary @click="refresh">
<div text-xs :class="isLoading ? 'animate-spin' : ''" i-ri:loop-right-line />
{{ $t('status.poll.update') }}
</button>
</div>
</div>
</div>
</template>