combine the 2 poll components, do the footer

This commit is contained in:
shpuld 2019-06-10 22:35:33 +03:00
parent 6bc1d10ae9
commit d7b75ba037
5 changed files with 298 additions and 55 deletions

View File

@ -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
})
}
}
}
}

View File

@ -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") }}&nbsp;·&nbsp;
</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>

View File

@ -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: {

View File

@ -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;

View File

@ -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",