combine the 2 poll components, do the footer
This commit is contained in:
parent
6bc1d10ae9
commit
d7b75ba037
|
@ -0,0 +1,90 @@
|
||||||
|
import Timeago from '../timeago/timeago.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Poll',
|
||||||
|
props: ['poll', 'statusId'],
|
||||||
|
components: { Timeago },
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
multipleChoices: [],
|
||||||
|
singleChoiceIndex: null,
|
||||||
|
refreshInterval: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.refreshInterval = setTimeout(this.refreshPoll, 30 * 1000)
|
||||||
|
},
|
||||||
|
destroyed () {
|
||||||
|
clearTimeout(this.refreshInterval)
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
expired () {
|
||||||
|
return Date.now() > Date.parse(this.poll.expires_at)
|
||||||
|
},
|
||||||
|
showResults () {
|
||||||
|
return this.poll.voted || this.expired
|
||||||
|
},
|
||||||
|
totalVotesCount () {
|
||||||
|
return this.poll.votes_count
|
||||||
|
},
|
||||||
|
expiresAt () {
|
||||||
|
return Date.parse(this.poll.expires_at).toLocaleString()
|
||||||
|
},
|
||||||
|
containerClass () {
|
||||||
|
return {
|
||||||
|
loading: this.loading
|
||||||
|
}
|
||||||
|
},
|
||||||
|
choiceIndices () {
|
||||||
|
return this.multipleChoices
|
||||||
|
.map((entry, index) => index)
|
||||||
|
.filter(value => typeof value === 'number')
|
||||||
|
},
|
||||||
|
isDisabled () {
|
||||||
|
const noChoice = this.poll.multiple
|
||||||
|
? this.choiceIndices.length === 0
|
||||||
|
: this.singleChoiceIndex === undefined
|
||||||
|
return this.loading || noChoice
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
refreshPoll () {
|
||||||
|
if (this.expired) return
|
||||||
|
this.fetchPoll()
|
||||||
|
this.refreshInterval = setTimeout(this.refreshPoll, 30 * 1000)
|
||||||
|
},
|
||||||
|
percentageForOption (count) {
|
||||||
|
return this.totalVotesCount === 0 ? 0 : Math.round((count + 5) / (this.totalVotesCount + 10) * 100)
|
||||||
|
},
|
||||||
|
resultTitle (option) {
|
||||||
|
return `${option.votes_count}/${this.totalVotesCount} ${this.$t('polls.votes')}`
|
||||||
|
},
|
||||||
|
fetchPoll () {
|
||||||
|
this.$store.dispatch('refreshPoll', { id: this.statusId, pollId: this.poll.id })
|
||||||
|
},
|
||||||
|
optionId (index) {
|
||||||
|
return `poll${this.poll.id}-${index}`
|
||||||
|
},
|
||||||
|
vote () {
|
||||||
|
this.loading = true
|
||||||
|
if (this.poll.multiple) {
|
||||||
|
if (this.choiceIndices.length === 0) return
|
||||||
|
this.$store.dispatch(
|
||||||
|
'votePoll',
|
||||||
|
{ id: this.statusId, pollId: this.poll.id, choices: this.choiceIndices }
|
||||||
|
).then(poll => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (this.singleChoiceIndex === undefined) return
|
||||||
|
this.$store.dispatch(
|
||||||
|
'votePoll',
|
||||||
|
{ id: this.statusId, pollId: this.poll.id, choices: [this.singleChoiceIndex] }
|
||||||
|
).then(poll => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,34 +1,119 @@
|
||||||
<template>
|
<template>
|
||||||
<poll-results
|
<div class="poll" v-bind:class="containerClass">
|
||||||
v-if="currentUserHasVoted"
|
<div
|
||||||
:poll="poll"
|
class="poll-option"
|
||||||
:status-id="statusId"
|
v-for="(option, index) in poll.options"
|
||||||
/>
|
:key="index"
|
||||||
<poll-vote
|
>
|
||||||
|
<div v-if="showResults" :title="resultTitle(option)" class="option-result">
|
||||||
|
<div class="option-result-label">
|
||||||
|
<span class="result-percentage">
|
||||||
|
{{percentageForOption(option.votes_count)}}%
|
||||||
|
</span>
|
||||||
|
<span>{{option.title}}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="result-fill"
|
||||||
|
:style="{ 'width': `${percentageForOption(option.votes_count)}%` }"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<input
|
||||||
|
v-if="poll.multiple"
|
||||||
|
type="checkbox"
|
||||||
|
:id="optionId(index)"
|
||||||
|
:disabled="loading"
|
||||||
|
:value="option.title"
|
||||||
|
v-model="multipleChoices[index]"
|
||||||
|
>
|
||||||
|
<input
|
||||||
v-else
|
v-else
|
||||||
:poll="poll"
|
type="radio"
|
||||||
:status-id="statusId"
|
:id="optionId(index)"
|
||||||
/>
|
:disabled="loading"
|
||||||
|
:value="index"
|
||||||
|
v-model="singleChoiceIndex"
|
||||||
|
>
|
||||||
|
<label :for="optionId(index)">
|
||||||
|
{{option.title}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer faint">
|
||||||
|
<button
|
||||||
|
v-if="!showResults"
|
||||||
|
class="btn btn-default poll-vote-button"
|
||||||
|
type="button"
|
||||||
|
@click="vote"
|
||||||
|
:disabled="isDisabled"
|
||||||
|
>
|
||||||
|
{{$t('polls.vote')}}
|
||||||
|
</button>
|
||||||
|
<div class="total">
|
||||||
|
{{totalVotesCount}} {{ $t("polls.votes") }} ·
|
||||||
|
</div>
|
||||||
|
<i18n :path="expired ? 'polls.expired' : 'polls.expires_in'">
|
||||||
|
<Timeago :time="this.poll.expires_at" :auto-update="60" />
|
||||||
|
</i18n>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script src="./poll.js"></script>
|
||||||
import PollResults from './poll_results/poll_results.vue'
|
|
||||||
import PollVote from './poll_vote/poll_vote.vue'
|
|
||||||
|
|
||||||
export default {
|
<style lang="scss">
|
||||||
name: 'Poll',
|
@import '../../_variables.scss';
|
||||||
props: ['poll', 'statusId'],
|
|
||||||
components: {
|
.poll {
|
||||||
PollResults,
|
.votes {
|
||||||
PollVote
|
display: flex;
|
||||||
},
|
flex-direction: column;
|
||||||
computed: {
|
margin: 0 0 0.5em;
|
||||||
currentUserHasVoted () {
|
}
|
||||||
return this.poll.voted
|
.poll-option {
|
||||||
},
|
margin: 0.5em 0;
|
||||||
voted () {
|
height: 1.5em;
|
||||||
return this.poll.voted
|
}
|
||||||
|
.option-result {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.option-result-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.1em 0.25em;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.result-percentage {
|
||||||
|
width: 3.5em;
|
||||||
|
}
|
||||||
|
.result-fill {
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
background-color: $fallback--lightBg;
|
||||||
|
background-color: var(--faintLink, $fallback--lightBg);
|
||||||
|
border-radius: $fallback--panelRadius;
|
||||||
|
border-radius: var(--panelRadius, $fallback--panelRadius);
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
transition: width 0.5s;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
width: 3.5em;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
&.loading * {
|
||||||
|
cursor: progress;
|
||||||
|
}
|
||||||
|
.poll-vote-button {
|
||||||
|
padding: 0 0.5em;
|
||||||
|
margin-right: 0.5em;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
</style>
|
||||||
|
|
|
@ -66,8 +66,8 @@ export default {
|
||||||
data: () => ({
|
data: () => ({
|
||||||
pollType: 'single',
|
pollType: 'single',
|
||||||
options: ['', ''],
|
options: ['', ''],
|
||||||
expiryAmount: 1,
|
expiryAmount: 2,
|
||||||
expiryUnit: 'minutes',
|
expiryUnit: 'hours',
|
||||||
expiryUnits: ['minutes', 'hours', 'days']
|
expiryUnits: ['minutes', 'hours', 'days']
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
|
|
|
@ -1,15 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<form class="poll-vote" v-bind:class="containerClass">
|
<div class="poll-vote" v-bind:class="containerClass">
|
||||||
<div
|
<div
|
||||||
class="poll-choice"
|
class="poll-choice"
|
||||||
v-for="(option, index) in poll.options"
|
v-for="(option, index) in poll.options"
|
||||||
:key="index"
|
:key="index"
|
||||||
>
|
>
|
||||||
|
<div v-if="showResults" :title="resultTitle(option)">
|
||||||
|
<div class="vote-label">
|
||||||
|
<span>{{percentageForOption(option.votes_count)}}%</span>
|
||||||
|
<span>{{option.title}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="fill" :style="{ 'width': `${percentageForOption(option.votes_count)}%` }"></div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
<input
|
<input
|
||||||
v-if="poll.multiple"
|
v-if="poll.multiple"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="choice"
|
:id="optionId(index)"
|
||||||
:id="index"
|
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
:value="option.title"
|
:value="option.title"
|
||||||
v-model="multipleChoices[index]"
|
v-model="multipleChoices[index]"
|
||||||
|
@ -17,16 +24,16 @@
|
||||||
<input
|
<input
|
||||||
v-else
|
v-else
|
||||||
type="radio"
|
type="radio"
|
||||||
name="choice"
|
:id="optionId(index)"
|
||||||
:id="index"
|
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
:value="index"
|
:value="index"
|
||||||
v-model="singleChoiceIndex"
|
v-model="singleChoiceIndex"
|
||||||
>
|
>
|
||||||
<label :for="index">
|
<label :for="optionId(index)">
|
||||||
{{option.title}}
|
{{option.title}}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<button
|
<button
|
||||||
class="btn btn-default poll-vote-button"
|
class="btn btn-default poll-vote-button"
|
||||||
|
@ -38,7 +45,7 @@
|
||||||
</button>
|
</button>
|
||||||
<Timeago :time="this.poll.expires_at" :auto-update="1"></Timeago>
|
<Timeago :time="this.poll.expires_at" :auto-update="1"></Timeago>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -59,6 +66,12 @@ export default {
|
||||||
expired () {
|
expired () {
|
||||||
return new Date() > this.poll.expires_at
|
return new Date() > this.poll.expires_at
|
||||||
},
|
},
|
||||||
|
showResults () {
|
||||||
|
return this.poll.voted || this.expired
|
||||||
|
},
|
||||||
|
totalVotesCount () {
|
||||||
|
return this.poll.votes_count
|
||||||
|
},
|
||||||
timeleft () {
|
timeleft () {
|
||||||
const expiresAt = new Date(this.poll.expires_at)
|
const expiresAt = new Date(this.poll.expires_at)
|
||||||
return expiresAt
|
return expiresAt
|
||||||
|
@ -80,6 +93,18 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
percentageForOption (count) {
|
||||||
|
return this.totalVotesCount === 0 ? 0 : Math.round(count / this.totalVotesCount * 100)
|
||||||
|
},
|
||||||
|
resultTitle (option) {
|
||||||
|
return `${option.votes_count}/${this.totalVotesCount} ${this.$t('polls.votes')}`
|
||||||
|
},
|
||||||
|
fetchPoll () {
|
||||||
|
this.$store.dispatch('refreshPoll', { id: this.statusId, pollId: this.poll.id })
|
||||||
|
},
|
||||||
|
optionId (index) {
|
||||||
|
return `poll${this.poll.id}-${index}`
|
||||||
|
},
|
||||||
vote () {
|
vote () {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
if (this.poll.multiple) {
|
if (this.poll.multiple) {
|
||||||
|
@ -105,6 +130,46 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import '../../../_variables.scss';
|
||||||
|
|
||||||
|
.poll-results {
|
||||||
|
.votes {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0 0 0.5em;
|
||||||
|
}
|
||||||
|
.poll-option {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin-top: 0.25em;
|
||||||
|
margin-bottom: 0.25em;
|
||||||
|
}
|
||||||
|
.fill {
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
background-color: $fallback--lightBg;
|
||||||
|
background-color: var(--faintLink, $fallback--lightBg);
|
||||||
|
border-radius: $fallback--panelRadius;
|
||||||
|
border-radius: var(--panelRadius, $fallback--panelRadius);
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
transition: width 0.5s;
|
||||||
|
}
|
||||||
|
.vote-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.1em 0.25em;
|
||||||
|
z-index: 1;
|
||||||
|
span {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.poll-vote {
|
.poll-vote {
|
||||||
margin: 0.7em 0 0;
|
margin: 0.7em 0 0;
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,10 @@
|
||||||
"votes": "votes",
|
"votes": "votes",
|
||||||
"vote": "Vote",
|
"vote": "Vote",
|
||||||
"single_choice": "Single choice",
|
"single_choice": "Single choice",
|
||||||
"multiple_choices": "Multiple choices"
|
"multiple_choices": "Multiple choices",
|
||||||
|
"expiry": "Poll age",
|
||||||
|
"expires_in": "Ends in {0}",
|
||||||
|
"expired": "Poll ended {0} ago"
|
||||||
},
|
},
|
||||||
"interactions": {
|
"interactions": {
|
||||||
"favs_repeats": "Repeats and Favorites",
|
"favs_repeats": "Repeats and Favorites",
|
||||||
|
|
Loading…
Reference in New Issue