From 0eed2ccca8a0980c161bb5a52b211c507e0ffef5 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 18 Jun 2019 20:28:31 +0000 Subject: Feature/polls attempt 2 --- src/components/poll/poll.js | 107 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/components/poll/poll.js (limited to 'src/components/poll/poll.js') diff --git a/src/components/poll/poll.js b/src/components/poll/poll.js new file mode 100644 index 00000000..ecacbc35 --- /dev/null +++ b/src/components/poll/poll.js @@ -0,0 +1,107 @@ +import Timeago from '../timeago/timeago.vue' +import { forEach, map } from 'lodash' + +export default { + name: 'Poll', + props: ['poll', 'statusId'], + components: { Timeago }, + data () { + return { + loading: false, + choices: [], + refreshInterval: null + } + }, + created () { + this.refreshInterval = setTimeout(this.refreshPoll, 30 * 1000) + // Initialize choices to booleans and set its length to match options + this.choices = this.poll.options.map(_ => false) + }, + destroyed () { + clearTimeout(this.refreshInterval) + }, + computed: { + expired () { + return Date.now() > Date.parse(this.poll.expires_at) + }, + loggedIn () { + return this.$store.state.users.currentUser + }, + showResults () { + return this.poll.voted || this.expired || !this.loggedIn + }, + totalVotesCount () { + return this.poll.votes_count + }, + expiresAt () { + return Date.parse(this.poll.expires_at).toLocaleString() + }, + containerClass () { + return { + loading: this.loading + } + }, + choiceIndices () { + // Convert array of booleans into an array of indices of the + // items that were 'true', so [true, false, false, true] becomes + // [0, 3]. + return this.choices + .map((entry, index) => entry && index) + .filter(value => typeof value === 'number') + }, + isDisabled () { + const noChoice = this.choiceIndices.length === 0 + 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 / 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 }) + }, + activateOption (index) { + // forgive me father: doing checking the radio/checkboxes + // in code because of customized input elements need either + // a) an extra element for the actual graphic, or b) use a + // pseudo element for the label. We use b) which mandates + // using "for" and "id" matching which isn't nice when the + // same poll appears multiple times on the site (notifs and + // timeline for example). With code we can make sure it just + // works without altering the pseudo element implementation. + const allElements = this.$el.querySelectorAll('input') + const clickedElement = this.$el.querySelector(`input[value="${index}"]`) + if (this.poll.multiple) { + // Checkboxes, toggle only the clicked one + clickedElement.checked = !clickedElement.checked + } else { + // Radio button, uncheck everything and check the clicked one + forEach(allElements, element => { element.checked = false }) + clickedElement.checked = true + } + this.choices = map(allElements, e => e.checked) + }, + optionId (index) { + return `poll${this.poll.id}-${index}` + }, + vote () { + if (this.choiceIndices.length === 0) return + this.loading = true + this.$store.dispatch( + 'votePoll', + { id: this.statusId, pollId: this.poll.id, choices: this.choiceIndices } + ).then(poll => { + this.loading = false + }) + } + } +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From e5e9bb27f3a0825b695684a503ea7e5e75073734 Mon Sep 17 00:00:00 2001 From: Shpuld Shpludson Date: Thu, 20 Jun 2019 13:02:04 +0000 Subject: Move poll state handling to its own module --- src/components/poll/poll.js | 35 ++++++++++--------- src/components/poll/poll.vue | 4 +-- src/components/status/status.vue | 2 +- src/main.js | 4 ++- src/modules/polls.js | 73 ++++++++++++++++++++++++++++++++++++++++ src/modules/statuses.js | 12 ------- 6 files changed, 96 insertions(+), 34 deletions(-) create mode 100644 src/modules/polls.js (limited to 'src/components/poll/poll.js') diff --git a/src/components/poll/poll.js b/src/components/poll/poll.js index ecacbc35..9988070e 100644 --- a/src/components/poll/poll.js +++ b/src/components/poll/poll.js @@ -3,26 +3,33 @@ import { forEach, map } from 'lodash' export default { name: 'Poll', - props: ['poll', 'statusId'], + props: ['pollId'], components: { Timeago }, data () { return { loading: false, - choices: [], - refreshInterval: null + choices: [] } }, - created () { - this.refreshInterval = setTimeout(this.refreshPoll, 30 * 1000) - // Initialize choices to booleans and set its length to match options - this.choices = this.poll.options.map(_ => false) + mounted () { + this.$store.dispatch('trackPoll', this.pollId) }, destroyed () { - clearTimeout(this.refreshInterval) + this.$store.dispatch('untrackPoll', this.pollId) }, computed: { + poll () { + const storePoll = this.$store.state.polls.pollsObject[this.pollId] + return storePoll || {} + }, + options () { + return (this.poll && this.poll.options) || [] + }, + expiresAt () { + return (this.poll && this.poll.expires_at) || 0 + }, expired () { - return Date.now() > Date.parse(this.poll.expires_at) + return Date.now() > Date.parse(this.expiresAt) }, loggedIn () { return this.$store.state.users.currentUser @@ -33,9 +40,6 @@ export default { totalVotesCount () { return this.poll.votes_count }, - expiresAt () { - return Date.parse(this.poll.expires_at).toLocaleString() - }, containerClass () { return { loading: this.loading @@ -55,11 +59,6 @@ export default { } }, 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 / this.totalVotesCount * 100) }, @@ -104,4 +103,4 @@ export default { }) } } -} \ No newline at end of file +} diff --git a/src/components/poll/poll.vue b/src/components/poll/poll.vue index 4c765ae2..bb67101a 100644 --- a/src/components/poll/poll.vue +++ b/src/components/poll/poll.vue @@ -2,7 +2,7 @@
@@ -50,7 +50,7 @@ {{totalVotesCount}} {{ $t("polls.votes") }} ยท 
- +
diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 58402f7e..821a7a83 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -124,7 +124,7 @@
- +
diff --git a/src/main.js b/src/main.js index d0f2674b..3287fa2b 100644 --- a/src/main.js +++ b/src/main.js @@ -14,6 +14,7 @@ import authFlowModule from './modules/auth_flow.js' import mediaViewerModule from './modules/media_viewer.js' import oauthTokensModule from './modules/oauth_tokens.js' import reportsModule from './modules/reports.js' +import pollsModule from './modules/polls.js' import VueI18n from 'vue-i18n' @@ -72,7 +73,8 @@ const persistedStateOptions = { authFlow: authFlowModule, mediaViewer: mediaViewerModule, oauthTokens: oauthTokensModule, - reports: reportsModule + reports: reportsModule, + polls: pollsModule }, plugins: [persistedState, pushNotifications], strict: false // Socket modifies itself, let's ignore this for now. diff --git a/src/modules/polls.js b/src/modules/polls.js new file mode 100644 index 00000000..0f0964aa --- /dev/null +++ b/src/modules/polls.js @@ -0,0 +1,73 @@ +import { each, merge } from 'lodash' +import { set } from 'vue' + +const polls = { + state: { + // Contains key = id, value = number of trackers for this poll + trackedPolls: {}, + pollsObject: {} + }, + mutations: { + addNewStatuses (state, { statuses }) { + each(statuses, status => { + if (status.poll) { + set(state.pollsObject, status.poll.id, status.poll) + } + }) + }, + mergePoll (state, poll) { + state.pollsObject[poll.id] = merge(state.pollsObject[poll.id], poll) + }, + trackPoll (state, pollId) { + const currentValue = state.trackedPolls[pollId] + if (currentValue) { + set(state.trackedPolls, pollId, currentValue + 1) + } else { + set(state.trackedPolls, pollId, 1) + } + }, + untrackPoll (state, pollId) { + const currentValue = state.trackedPolls[pollId] + if (currentValue) { + set(state.trackedPolls, pollId, currentValue - 1) + } else { + set(state.trackedPolls, pollId, 0) + } + } + }, + actions: { + updatePoll ({ rootState, commit }, pollId) { + return rootState.api.backendInteractor.fetchPoll(pollId).then(poll => { + commit('mergePoll', poll) + return poll + }) + }, + updateTrackedPoll ({ rootState, dispatch, commit }, pollId) { + rootState.api.backendInteractor.fetchPoll(pollId).then(poll => { + setTimeout(() => { + if (rootState.polls.trackedPolls[pollId]) { + dispatch('updateTrackedPoll', pollId) + } + }, 30 * 1000) + commit('mergePoll', poll) + }) + }, + trackPoll ({ rootState, commit, dispatch }, pollId) { + if (!rootState.polls.trackedPolls[pollId]) { + setTimeout(() => dispatch('updateTrackedPoll', pollId), 30 * 1000) + } + commit('trackPoll', pollId) + }, + untrackPoll ({ commit }, pollId) { + commit('untrackPoll', pollId) + }, + votePoll ({ rootState, commit }, { id, pollId, choices }) { + return rootState.api.backendInteractor.vote(pollId, choices).then(poll => { + commit('mergePoll', poll) + return poll + }) + } + } +} + +export default polls diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 5f09b8f5..235098d3 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -582,18 +582,6 @@ const statuses = { ]).then(([favoritedByUsers, rebloggedByUsers]) => commit('addFavsAndRepeats', { id, favoritedByUsers, rebloggedByUsers }) ) - }, - votePoll ({ rootState, commit }, { id, pollId, choices }) { - return rootState.api.backendInteractor.vote(pollId, choices).then(poll => { - commit('updateStatusWithPoll', { id, poll }) - return poll - }) - }, - refreshPoll ({ rootState, commit }, { id, pollId }) { - return rootState.api.backendInteractor.fetchPoll(pollId).then(poll => { - commit('updateStatusWithPoll', { id, poll }) - return poll - }) } }, mutations -- cgit v1.2.3-70-g09d2 From 947f69a9531a7d99e79df3fc05f5e795de717dc6 Mon Sep 17 00:00:00 2001 From: Shpuld Shpludson Date: Sat, 22 Jun 2019 14:01:36 +0000 Subject: Fix: problems with polls state --- src/components/poll/poll.js | 12 +++++++++--- src/components/status/status.vue | 2 +- src/modules/polls.js | 31 ++++++++++++++----------------- 3 files changed, 24 insertions(+), 21 deletions(-) (limited to 'src/components/poll/poll.js') diff --git a/src/components/poll/poll.js b/src/components/poll/poll.js index 9988070e..98db5582 100644 --- a/src/components/poll/poll.js +++ b/src/components/poll/poll.js @@ -3,7 +3,7 @@ import { forEach, map } from 'lodash' export default { name: 'Poll', - props: ['pollId'], + props: ['basePoll'], components: { Timeago }, data () { return { @@ -11,13 +11,19 @@ export default { choices: [] } }, - mounted () { + created () { + if (!this.$store.state.polls.pollsObject[this.pollId]) { + this.$store.dispatch('mergeOrAddPoll', this.basePoll) + } this.$store.dispatch('trackPoll', this.pollId) }, destroyed () { this.$store.dispatch('untrackPoll', this.pollId) }, computed: { + pollId () { + return this.basePoll.id + }, poll () { const storePoll = this.$store.state.polls.pollsObject[this.pollId] return storePoll || {} @@ -29,7 +35,7 @@ export default { return (this.poll && this.poll.expires_at) || 0 }, expired () { - return Date.now() > Date.parse(this.expiresAt) + return (this.poll && this.poll.expired) || false }, loggedIn () { return this.$store.state.users.currentUser diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 821a7a83..440e1957 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -124,7 +124,7 @@
- +
diff --git a/src/modules/polls.js b/src/modules/polls.js index 0f0964aa..e6158b63 100644 --- a/src/modules/polls.js +++ b/src/modules/polls.js @@ -1,4 +1,4 @@ -import { each, merge } from 'lodash' +import { merge } from 'lodash' import { set } from 'vue' const polls = { @@ -8,15 +8,15 @@ const polls = { pollsObject: {} }, mutations: { - addNewStatuses (state, { statuses }) { - each(statuses, status => { - if (status.poll) { - set(state.pollsObject, status.poll.id, status.poll) - } - }) - }, - mergePoll (state, poll) { - state.pollsObject[poll.id] = merge(state.pollsObject[poll.id], poll) + mergeOrAddPoll (state, poll) { + const existingPoll = state.pollsObject[poll.id] + // Make expired-state change trigger re-renders properly + poll.expired = Date.now() > Date.parse(poll.expires_at) + if (existingPoll) { + set(state.pollsObject, poll.id, merge(existingPoll, poll)) + } else { + set(state.pollsObject, poll.id, poll) + } }, trackPoll (state, pollId) { const currentValue = state.trackedPolls[pollId] @@ -36,11 +36,8 @@ const polls = { } }, actions: { - updatePoll ({ rootState, commit }, pollId) { - return rootState.api.backendInteractor.fetchPoll(pollId).then(poll => { - commit('mergePoll', poll) - return poll - }) + mergeOrAddPoll ({ commit }, poll) { + commit('mergeOrAddPoll', poll) }, updateTrackedPoll ({ rootState, dispatch, commit }, pollId) { rootState.api.backendInteractor.fetchPoll(pollId).then(poll => { @@ -49,7 +46,7 @@ const polls = { dispatch('updateTrackedPoll', pollId) } }, 30 * 1000) - commit('mergePoll', poll) + commit('mergeOrAddPoll', poll) }) }, trackPoll ({ rootState, commit, dispatch }, pollId) { @@ -63,7 +60,7 @@ const polls = { }, votePoll ({ rootState, commit }, { id, pollId, choices }) { return rootState.api.backendInteractor.vote(pollId, choices).then(poll => { - commit('mergePoll', poll) + commit('mergeOrAddPoll', poll) return poll }) } -- cgit v1.2.3-70-g09d2