diff options
author | René Gieling <github@dartcafe.de> | 2020-01-05 18:32:39 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-01-05 18:32:39 +0300 |
commit | 1582780a61dd47cbb30f3a3b3c584f40e025ca87 (patch) | |
tree | 2b499760d9fdf662b2f04ea95e1177b7be66019d | |
parent | b03f6651803f3db4a78b75f59530794c3e2ac3dd (diff) | |
parent | 81d0966566e1f2be3d0b8e364aa8d6ae85c31b37 (diff) |
Merge pull request #709 from nextcloud/voteAlternativev1.0-beta2
Vote alternative
20 files changed, 523 insertions, 134 deletions
diff --git a/src/js/components/Base/DatePollItem.vue b/src/js/components/Base/DatePollItem.vue index e58b5861..4791f672 100644 --- a/src/js/components/Base/DatePollItem.vue +++ b/src/js/components/Base/DatePollItem.vue @@ -21,15 +21,16 @@ --> <template> - <li> + <li class="poll-item date"> <div>{{ moment.unix(option.timestamp).format('LLLL') }}</div> <div> - <a class="icon-delete" @click="$emit('remove')" /> + <a v-if="acl.allowEdit" class="icon-delete" @click="$emit('remove')" /> </div> </li> </template> <script> +import { mapState } from 'vuex' export default { name: 'DatePollItem', @@ -39,24 +40,12 @@ export default { type: Object, default: undefined } - } + }, + computed: { + ...mapState({ + acl: state => state.acl + }) + } } </script> - -<style lang="scss" scoped> - li > div { - display: flex; - flex-grow: 1; - font-size: 1.2em; - opacity: 0.7; - white-space: normal; - padding-right: 4px; - } - - li > div:nth-last-child(1) { - justify-content: center; - flex-grow: 0; - flex-shrink: 0; - } -</style> diff --git a/src/js/components/Base/ParticipantsList.vue b/src/js/components/Base/ParticipantsList.vue new file mode 100644 index 00000000..1ae1d895 --- /dev/null +++ b/src/js/components/Base/ParticipantsList.vue @@ -0,0 +1,87 @@ +<!-- + - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de> + - + - @author René Gieling <github@dartcafe.de> + - + - @license GNU AGPL version 3 or any later version + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + - + --> + +<template> + <div class="participants-list"> + <div v-if="acl.allowSeeUsernames"> + <h2>{{ t('polls','Participants') }}</h2> + <div class="participants"> + <userDiv v-for="(participant) in participants" + :key="participant" + :hide-names="true" + :user-id="participant" + type="user" /> + </div> + </div> + + <div v-else> + <h2>{{ t('polls','Participants names are hidden, because this is an anoymous poll') }} </h2> + </div> + </div> +</template> + +<script> +import { mapState, mapGetters } from 'vuex' + +export default { + name: 'ParticipantsList', + + data() { + return { + voteSaved: false, + delay: 50, + newName: '' + } + }, + + computed: { + ...mapState({ + acl: state => state.acl + }), + + ...mapGetters([ + 'participants' + ]) + + } + +} +</script> + +<style lang="scss" scoped> + .participants-list { + margin: 8px 0; + } + + .participants { + display: flex; + justify-content: flex-start; + .user-row { + display: block; + flex: 0; + } + .user { + padding: 0; + } + } + +</style> diff --git a/src/js/components/Base/PollDescription.vue b/src/js/components/Base/PollDescription.vue new file mode 100644 index 00000000..66afd9e2 --- /dev/null +++ b/src/js/components/Base/PollDescription.vue @@ -0,0 +1,49 @@ +<!-- + - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de> + - + - @author René Gieling <github@dartcafe.de> + - + - @license GNU AGPL version 3 or any later version + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + - + --> + +<template> + <!-- eslint-disable-next-line vue/singleline-html-element-content-newline --> + <h3 class="poll-description">{{ poll.description }}</h3> +</template> + +<script> +import { mapState } from 'vuex' + +export default { + name: 'PollDescription', + + computed: { + ...mapState({ + poll: state => state.poll + }) + + } + +} +</script> + +<style lang="scss" scoped> + .poll-description { + white-space: break-spaces; + margin: 8px 0; + } +</style> diff --git a/src/js/components/Base/PollInformation.vue b/src/js/components/Base/PollInformation.vue new file mode 100644 index 00000000..e6de3786 --- /dev/null +++ b/src/js/components/Base/PollInformation.vue @@ -0,0 +1,52 @@ +<!-- + - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de> + - + - @author René Gieling <github@dartcafe.de> + - + - @license GNU AGPL version 3 or any later version + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + - + --> + +<template> + <div class="poll-information"> + <UserBubble :user="poll.owner" :display-name="poll.owner" /> + {{ t('polls', ' started this poll on %n. ', 1, moment.unix(poll.created).format('LLLL')) }} + <span v-if="expired">{{ t('polls', 'Voting is no more possible, because this poll expired since %n', 1, moment.unix(poll.expire).format('LLLL')) }}</span> + <span v-if="!expired && poll.expire && acl.allowVote">{{ t('polls', 'You can place your vote until %n. ',1, moment.unix(poll.expire).format('LLLL')) }}</span> + <span v-if="poll.anonymous">{{ t('polls', 'The names of other participants is hidden, as this is a anonymous poll. ') }}</span> + <span>{{ t('polls', '%n voters participated in this poll until now.', 1, participants.length) }}</span> + </div> +</template> + +<script> +import { mapState, mapGetters } from 'vuex' + +export default { + name: 'PollInformation', + + computed: { + ...mapState({ + acl: state => state.acl, + poll: state => state.poll + }), + + ...mapGetters([ + 'participants', + 'expired' + ]) + } +} +</script> diff --git a/src/js/components/VoteTable/VoteHeader.vue b/src/js/components/Base/PollTitle.vue index acb4fc66..3480be79 100644 --- a/src/js/components/VoteTable/VoteHeader.vue +++ b/src/js/components/Base/PollTitle.vue @@ -21,32 +21,19 @@ --> <template> - <div class="voteHeader"> - <h2> - {{ poll.title }} - <span v-if="expired" class="label error">{{ t('polls', 'Expired since %n', 1, moment.unix(poll.expire).format('LLLL')) }}</span> - <span v-if="!expired && poll.expire" class="label success">{{ t('polls', 'Place your votes until %n', 1, moment.unix(poll.expire).format('LLLL')) }}</span> - <span v-if="poll.deleted" class="label error">{{ t('polls', 'Deleted') }}</span> - </h2> - <h3> - {{ poll.description }} - </h3> - </div> + <h2 class="poll-title"> + {{ poll.title }} + <span v-if="expired" class="label error">{{ t('polls', 'Expired since %n', 1, moment.unix(poll.expire).format('LLLL')) }}</span> + <span v-if="!expired && poll.expire" class="label success">{{ t('polls', 'Place your votes until %n', 1, moment.unix(poll.expire).format('LLLL')) }}</span> + <span v-if="poll.deleted" class="label error">{{ t('polls', 'Deleted') }}</span> + </h2> </template> <script> import { mapState, mapGetters } from 'vuex' export default { - name: 'VoteHeader', - - data() { - return { - voteSaved: false, - delay: 50, - newName: '' - } - }, + name: 'PollTitle', computed: { ...mapState({ @@ -63,7 +50,7 @@ export default { </script> <style lang="scss" scoped> - .voteHeader { - margin: 8px 24px; + .pollTitle { + margin: 8px 0; } </style> diff --git a/src/js/components/Base/TextPollItem.vue b/src/js/components/Base/TextPollItem.vue index aefd8d09..ccbb952c 100644 --- a/src/js/components/Base/TextPollItem.vue +++ b/src/js/components/Base/TextPollItem.vue @@ -21,15 +21,17 @@ --> <template> - <li> + <li class="poll-item text"> <div>{{ option.pollOptionText }}</div> <div> - <a class="icon icon-delete svg delete-poll" @click="$emit('remove')" /> + <a v-if="acl.allowEdit" class="icon icon-delete svg delete-poll" @click="$emit('remove')" /> </div> </li> </template> <script> +import { mapState } from 'vuex' + export default { name: 'TextPollItem', @@ -38,7 +40,12 @@ export default { type: Object, default: undefined } + }, + + computed: { + ...mapState({ + acl: state => state.acl + }) } } - </script> diff --git a/src/js/components/SideBar/CommentAdd.vue b/src/js/components/Comments/CommentAdd.vue index 915bc71f..972c476f 100644 --- a/src/js/components/SideBar/CommentAdd.vue +++ b/src/js/components/Comments/CommentAdd.vue @@ -53,6 +53,7 @@ export default { this.isLoading = true this.$store.dispatch('setCommentAsync', { message: this.comment }) .then(() => { + this.isLoading = false OC.Notification.showTemporary(t('polls', 'Your comment was added'), { type: 'success' }) this.isLoading = false }) diff --git a/src/js/components/SideBar/SideBarTabComments.vue b/src/js/components/Comments/Comments.vue index 721b82be..b43a81aa 100644 --- a/src/js/components/SideBar/SideBarTabComments.vue +++ b/src/js/components/Comments/Comments.vue @@ -22,7 +22,8 @@ <template> <div> - <CommentAdd /> + <h2>{{ t('polls','Comments') }} </h2> + <CommentAdd v-if="acl.allowComment" /> <transition-group v-if="countComments" name="fade" class="comments" tag="ul"> <li v-for="(comment) in sortedComments" :key="comment.id"> @@ -49,14 +50,15 @@ import CommentAdd from './CommentAdd' import { mapState, mapGetters } from 'vuex' export default { - name: 'SideBarTabComments', + name: 'Comments', components: { CommentAdd }, computed: { ...mapState({ - comments: state => state.comments + comments: state => state.comments, + acl: state => state.acl }), ...mapGetters([ 'countComments', diff --git a/src/js/components/SideBar/SideBar.vue b/src/js/components/SideBar/SideBar.vue index 207d2eb3..5328f012 100644 --- a/src/js/components/SideBar/SideBar.vue +++ b/src/js/components/SideBar/SideBar.vue @@ -22,10 +22,8 @@ <template> <AppSidebar ref="sideBar" :title="t('polls', 'Details')" @close="$emit('closeSideBar')"> - <UserDiv slot="primary-actions" :user-id="poll.owner" :description="t('polls', 'Owner')" /> - <AppSidebarTab :name="t('polls', 'Comments')" icon="icon-comment"> - <SideBarTabComments /> + <Comments /> </AppSidebarTab> <AppSidebarTab v-if="acl.allowEdit" :name="t('polls', 'options')" icon="icon-toggle-filelist"> @@ -47,7 +45,7 @@ import { AppSidebar, AppSidebarTab } from '@nextcloud/vue' import SideBarTabConfiguration from './SideBarTabConfiguration' import SideBarTabOptions from './SideBarTabOptions' -import SideBarTabComments from './SideBarTabComments' +import Comments from '../Comments/Comments' import SideBarTabShare from './SideBarTabShare' import { mapState } from 'vuex' @@ -55,7 +53,7 @@ export default { name: 'SideBar', components: { SideBarTabConfiguration, - SideBarTabComments, + Comments, SideBarTabOptions, SideBarTabShare, AppSidebar, diff --git a/src/js/components/SideBar/SideBarOnlyComments.vue b/src/js/components/SideBar/SideBarOnlyComments.vue index 420d057c..3a93196f 100644 --- a/src/js/components/SideBar/SideBarOnlyComments.vue +++ b/src/js/components/SideBar/SideBarOnlyComments.vue @@ -25,7 +25,7 @@ <UserDiv slot="primary-actions" :user-id="poll.owner" :description="t('polls', 'Owner')" /> <AppSidebarTab :name="t('polls', 'Comments')" icon="icon-comment"> - <SideBarTabComments /> + <Comments /> </AppSidebarTab> </AppSidebar> </template> @@ -33,13 +33,13 @@ <script> import { AppSidebar, AppSidebarTab } from '@nextcloud/vue' -import SideBarTabComments from './SideBarTabComments' +import Comments from '../Comments/Comments' import { mapState } from 'vuex' export default { name: 'SideBarOnlyComments', components: { - SideBarTabComments, + Comments, AppSidebar, AppSidebarTab }, diff --git a/src/js/components/Subscription/Subscription.vue b/src/js/components/Subscription/Subscription.vue index 20a32077..56fcdac4 100644 --- a/src/js/components/Subscription/Subscription.vue +++ b/src/js/components/Subscription/Subscription.vue @@ -59,6 +59,6 @@ export default { <style lang="css" scoped> .subscription { - padding: 24px; + margin: 8px 0; } </style> diff --git a/src/js/components/VoteTable/VoteHeaderPublic.vue b/src/js/components/VoteTable/VoteHeaderPublic.vue index f16778bb..cd2b7609 100644 --- a/src/js/components/VoteTable/VoteHeaderPublic.vue +++ b/src/js/components/VoteTable/VoteHeaderPublic.vue @@ -23,17 +23,12 @@ <template> <div class="voteHeader"> <div v-if="!isValidUser" class="getUsername"> - <!-- <label> - {{ t('polls', 'Enter a valid username, to participate in this poll.') }} - </label> --> - <form v-if="!redirecting"> <input v-model="userName" :class="{ error: (!isValidName && userName.length > 0), success: isValidName }" type="text" :placeholder="t('polls', 'Enter a valid username with at least 3 Characters')"> <input v-show="isValidName && !checkingUserName" class="icon-confirm" :class="{ error: !isValidName, success: isValidName }" @click="writeUserName"> <span v-show="checkingUserName" class="icon-loading-small" /> - <!-- <span v-if="!isValidName" class="error"> {{ invalidUserNameMessage }} </span> --> </form> <div v-else> <span>{{ t('polls', 'You will be redirected to your personal share.') }}</span> diff --git a/src/js/components/VoteTable/VoteList.vue b/src/js/components/VoteTable/VoteList.vue new file mode 100644 index 00000000..b5fcb42d --- /dev/null +++ b/src/js/components/VoteTable/VoteList.vue @@ -0,0 +1,194 @@ +<!-- + - @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de> + - + - @author René Gieling <github@dartcafe.de> + - + - @license GNU AGPL version 3 or any later version + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + - + --> + +<template lang="html"> + <ul class="vote-list"> + <li v-for="(option) in sortedOptions" :key="option.id" class="vote-row"> + <VoteTableItem + v-if="acl.allowVote" + :user-id="acl.userId" + :option="option" + @voteClick="setVote(option, acl.userId)" /> + <TextPollItem v-if="poll.type === 'textPoll'" :option="option" /> + <DatePollItem v-if="poll.type === 'datePoll'" :option="option" /> + + <div class="counter"> + <div v-if="yesVotes(option.pollOptionText)" class="yes" :style="{ flex: yesVotes(option.pollOptionText) }"> + <span> {{ yesVotes(option.pollOptionText) }} </span> + </div> + + <div v-if="maybeVotes(option.pollOptionText)" class="maybe" :style="{flex: maybeVotes(option.pollOptionText) }"> + <span> {{ maybeVotes(option.pollOptionText) }} </span> + </div> + + <div v-if="noVotes(option.pollOptionText)" class="no" :style="{flex: noVotes(option.pollOptionText) }"> + <span> {{ noVotes(option.pollOptionText) }} </span> + </div> + </div> + </li> + </ul> +</template> + +<script> +import TextPollItem from '../Base/TextPollItem' +import DatePollItem from '../Base/DatePollItem' +import VoteTableItem from './VoteTableItem' +import { mapState, mapGetters } from 'vuex' + +export default { + name: 'VoteTable', + components: { + DatePollItem, + TextPollItem, + VoteTableItem + }, + + computed: { + ...mapState({ + poll: state => state.poll, + acl: state => state.acl + }), + + ...mapGetters([ + 'sortedOptions', + 'participants', + 'votesRank', + 'expired' + ]), + + currentUser() { + return this.acl.userId + }, + + noOptions() { + return (this.sortedOptions.length === 0) + } + + }, + + methods: { + + yesVotes(pollOptionText) { + return this.votesRank.find(rank => { + return rank.pollOptionText === pollOptionText + }).yes + }, + + maybeVotes(pollOptionText) { + return this.votesRank.find(rank => { + return rank.pollOptionText === pollOptionText + }).maybe + }, + + noVotes(pollOptionText) { + return this.participants.length - this.maybeVotes(pollOptionText) - this.yesVotes(pollOptionText) + }, + + setVote(option, participant) { + this.$store + .dispatch('setVoteAsync', { + option: option, + userId: participant, + setTo: this.$store.getters.getNextAnswer({ + option: option, + userId: participant + }) + }) + .then(() => { + // this.$emit('voteSaved') + }) + } + } +} +</script> + +<style lang="scss" scoped> + + .poll-item { + flex: 3; + } + + .vote-item { + flex: 0; + } + + .counter { + display: flex; + width: 80px; + flex: 1; + + > * { + text-align: center; + border-radius: 21px; + margin: 2px; + } + + .yes { + background-color: #ebf5d6; + } + + .maybe { + background-color: #f0db98; + } + + .no { + background-color: #f45573; + } + } + + .vote-list { + margin: 8px 0; + display: flex; + flex: 0; + flex-direction: column; + justify-content: flex-start; + overflow: scroll; + + .vote-row { + display: flex; + justify-content: space-around; + flex: 1; + align-items: center; + border-bottom: 1px solid var(--color-border); + + &:active, + &:hover { + transition: var(--background-dark) 0.3s ease; + background-color: var(--color-background-dark); //$hover-color; + } + + .vote-table-item { + flex: 0; + } + + > li { + display: flex; + align-items: center; + padding-left: 8px; + padding-right: 8px; + line-height: 2em; + min-height: 4em; + overflow: hidden; + } + } + } +</style> diff --git a/src/js/components/VoteTable/VoteTable.vue b/src/js/components/VoteTable/VoteTable.vue index 010c5ec2..1e41b5a5 100644 --- a/src/js/components/VoteTable/VoteTable.vue +++ b/src/js/components/VoteTable/VoteTable.vue @@ -160,6 +160,9 @@ export default { flex: 1; align-items: center; } + .vote-table-item { + flex: 1; + } } @media (max-width: (480px)) { diff --git a/src/js/components/VoteTable/VoteTableItem.vue b/src/js/components/VoteTable/VoteTableItem.vue index e1647fdd..76d0bc14 100644 --- a/src/js/components/VoteTable/VoteTableItem.vue +++ b/src/js/components/VoteTable/VoteTableItem.vue @@ -21,7 +21,7 @@ --> <template> - <div class="vote-item" :class="[answer, { active: isActive}]"> + <div class="vote-table-item" :class="[answer, { active: isActive}]"> <div class="icon" @click="voteClick()" /> </div> </template> @@ -82,7 +82,7 @@ export default { </script> -<style lang="scss" scoped> +<style lang="scss"> $bg-no: #ffede9; $bg-maybe: #fcf7e1; $bg-unvoted: #fff4c8; @@ -93,10 +93,9 @@ export default { $fg-unvoted: #f0db98; $fg-yes: #49bc49; - .vote-item { + .vote-table-item { height: 43px; display: flex; - flex: 1; width: 85px; align-items: center; background-color: var(--color-background-dark); @@ -156,7 +155,7 @@ export default { } @media (max-width: (480px) ) { - .vote-item { + .vote-table-item { border-top: 1px solid var(--color-border-dark); &.active { width: 10vw; diff --git a/src/js/main.js b/src/js/main.js index 3020e2e4..9b7d5117 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -31,7 +31,7 @@ import vClickOutside from 'v-click-outside' import VueClipboard from 'vue-clipboard2' import moment from 'moment' -import { PopoverMenu, Tooltip, DatetimePicker, AppContent } from '@nextcloud/vue' +import { PopoverMenu, Tooltip, DatetimePicker, AppContent, UserBubble } from '@nextcloud/vue' import UserDiv from './components/Base/UserDiv' import LoadingOverlay from './components/Base/LoadingOverlay' @@ -59,6 +59,7 @@ Vue.component('AppContent', AppContent) Vue.component('DatePicker', DatetimePicker) Vue.component('UserDiv', UserDiv) Vue.component('LoadingOverlay', LoadingOverlay) +Vue.component('UserBubble', UserBubble) Vue.directive('tooltip', Tooltip) diff --git a/src/js/store/modules/poll.js b/src/js/store/modules/poll.js index 521bfbf5..cffd1551 100644 --- a/src/js/store/modules/poll.js +++ b/src/js/store/modules/poll.js @@ -88,7 +88,6 @@ const actions = { loadPollMain(context, payload) { let endPoint = 'apps/polls/poll/get/' - if (payload.token) { endPoint = endPoint.concat('s/', payload.token) } else if (payload.pollId) { diff --git a/src/js/store/modules/votes.js b/src/js/store/modules/votes.js index a5a01284..7576282c 100644 --- a/src/js/store/modules/votes.js +++ b/src/js/store/modules/votes.js @@ -79,7 +79,7 @@ const getters = { }, votesRank: (state, getters, rootGetters) => { - const rank = [] + let rank = [] rootGetters.options.list.forEach(function(option) { const countYes = state.list.filter(vote => vote.voteOptionText === option.pollOptionText && vote.voteAnswer === 'yes').length const countMaybe = state.list.filter(vote => vote.voteOptionText === option.pollOptionText && vote.voteAnswer === 'maybe').length @@ -92,7 +92,15 @@ const getters = { maybe: countMaybe }) }) - return orderBy(rank, ['yes', 'maybe'], ['desc', 'desc']) + rank = orderBy(rank, ['yes', 'maybe'], ['desc', 'desc']) + for (var i = 0; i < rank.length; i++) { + if (i > 0 && rank[i].yes === rank[i - 1].yes && rank[i].maybe === rank[i - 1].maybe) { + rank[i].rank = rank[i - 1].rank + } else { + rank[i].rank = i + 1 + } + } + return rank }, winnerCombo: (state, getters) => { diff --git a/src/js/views/PublicVote.vue b/src/js/views/PublicVote.vue index c8d3d019..73bf387e 100644 --- a/src/js/views/PublicVote.vue +++ b/src/js/views/PublicVote.vue @@ -23,33 +23,45 @@ <template> <AppContent> <div v-if="poll.id > 0" class="main-container"> - <a v-if="!sideBarOpen" href="#" class="icon icon-settings active" - :title="t('polls', 'Open Sidebar')" @click="toggleSideBar()" /> - - <VoteHeader /> + <PollTitle /> + <PollInformation /> + <PollDescription /> <VoteHeaderPublic /> - <VoteTable /> + <button class="button btn primary" @click="tableMode = !tableMode"> + <span>{{ t('polls', 'Switch view') }}</span> + </button> + <VoteList v-show="!isLoading && !tableMode" /> + <VoteTable v-show="!isLoading && tableMode" /> + <ParticipantsList /> + <Comments /> </div> - <SideBar v-if="sideBarOpen" @closeSideBar="toggleSideBar" /> - <LoadingOverlay v-if="loading" /> + <LoadingOverlay v-if="isLoading" /> </AppContent> </template> <script> -import VoteHeader from '../components/VoteTable/VoteHeader' +import Comments from '../components/Comments/Comments' +import ParticipantsList from '../components/Base/ParticipantsList' +import PollDescription from '../components/Base/PollDescription' +import PollInformation from '../components/Base/PollInformation' +import PollTitle from '../components/Base/PollTitle' import VoteHeaderPublic from '../components/VoteTable/VoteHeaderPublic' +import VoteList from '../components/VoteTable/VoteList' import VoteTable from '../components/VoteTable/VoteTable' -import SideBar from '../components/SideBar/SideBar' import { mapState } from 'vuex' export default { name: 'Vote', components: { - VoteHeader, + ParticipantsList, + PollInformation, + PollTitle, + PollDescription, VoteHeaderPublic, + Comments, VoteTable, - SideBar + VoteList }, data() { @@ -57,15 +69,15 @@ export default { voteSaved: false, delay: 50, sideBarOpen: false, - loading: false, - initialTab: 'comments' + isLoading: false, + initialTab: 'comments', + tableMode: true } }, computed: { ...mapState({ - poll: state => state.poll, - acl: state => state.acl + poll: state => state.poll }), windowTitle: function() { @@ -86,19 +98,19 @@ export default { methods: { loadPoll() { - this.loading = false + this.isLoading = false this.$store.dispatch('loadPollMain', { token: this.$route.params.token }) .then(() => { this.$store.dispatch({ type: 'loadAcl', token: this.$route.params.token }) .then(() => { this.$store.dispatch('loadPoll', { token: this.$route.params.token }) .then(() => { - this.loading = false + this.isLoading = false }) }) .catch((error) => { console.error(error) - this.loading = false + this.isLoading = false }) }) }, @@ -112,23 +124,21 @@ export default { </script> <style lang="scss" scoped> - .main-container { - flex: 1; - margin: 0; - flex-direction: column; - flex-wrap: nowrap; - overflow-x: scroll; - h1, h2, h3, h4 { - margin-left: 24px; - } - } +.main-container { + flex: 1; + padding: 0 24px; + margin: 0; + flex-direction: column; + flex-wrap: nowrap; + overflow-x: scroll; +} - .icon.icon-settings.active { - display: block; - width: 44px; - height: 44px; - right: 0; - position: absolute; - } +.icon.icon-settings.active { + display: block; + width: 44px; + height: 44px; + right: 0; + position: absolute; +} </style> diff --git a/src/js/views/Vote.vue b/src/js/views/Vote.vue index 021cc947..a43eb214 100644 --- a/src/js/views/Vote.vue +++ b/src/js/views/Vote.vue @@ -23,34 +23,49 @@ <template> <AppContent> <div v-if="poll.id > 0" class="main-container"> - <a v-if="!sideBarOpen" href="#" class="icon icon-settings active" + <a v-if="!sideBarOpen && acl.allowEdit" href="#" class="icon icon-settings active" :title="t('polls', 'Open Sidebar')" @click="toggleSideBar()" /> - <VoteHeader /> - <VoteTable v-show="!loading" /> + <PollTitle /> + <PollInformation /> + <PollDescription /> + <button class="button btn primary" @click="tableMode = !tableMode"> + <span>{{ t('polls', 'Switch view') }}</span> + </button> + <VoteList v-show="!isLoading && !tableMode" /> + <VoteTable v-show="!isLoading && tableMode" /> <Subscription /> + <ParticipantsList /> + <Comments /> </div> <SideBar v-if="sideBarOpen && acl.allowEdit" @closeSideBar="toggleSideBar" /> - <SideBarOnlyComments v-if="sideBarOpen && !acl.allowEdit" @closeSideBar="toggleSideBar" /> - <LoadingOverlay v-if="loading" /> + <LoadingOverlay v-if="isLoading" /> </AppContent> </template> <script> +import Comments from '../components/Comments/Comments' import Subscription from '../components/Subscription/Subscription' -import VoteHeader from '../components/VoteTable/VoteHeader' -import VoteTable from '../components/VoteTable/VoteTable' +import ParticipantsList from '../components/Base/ParticipantsList' +import PollDescription from '../components/Base/PollDescription' +import PollInformation from '../components/Base/PollInformation' +import PollTitle from '../components/Base/PollTitle' import SideBar from '../components/SideBar/SideBar' -import SideBarOnlyComments from '../components/SideBar/SideBarOnlyComments' -import { mapState, mapGetters } from 'vuex' +import VoteList from '../components/VoteTable/VoteList' +import VoteTable from '../components/VoteTable/VoteTable' +import { mapState } from 'vuex' export default { name: 'Vote', components: { + ParticipantsList, + PollInformation, + PollTitle, + PollDescription, Subscription, - VoteHeader, VoteTable, - SideBarOnlyComments, + VoteList, + Comments, SideBar }, @@ -59,30 +74,23 @@ export default { voteSaved: false, delay: 50, sideBarOpen: false, - loading: false, + isLoading: false, initialTab: 'comments', - newName: '' + newName: '', + tableMode: true } }, computed: { ...mapState({ poll: state => state.poll, - shares: state => state.shares, acl: state => state.acl }), - ...mapGetters([ - 'expired' - ]), - windowTitle: function() { return t('polls', 'Polls') + ' - ' + this.poll.title - }, - - votePossible() { - return this.acl.allowVote && !this.expired } + }, watch: { @@ -98,7 +106,7 @@ export default { if (this.acl.allowEdit && moment.unix(this.poll.created).diff() > -10000) { this.sideBarOpen = true } - this.loading = false + this.isLoading = false }) }) } @@ -110,10 +118,12 @@ export default { methods: { loadPoll() { - this.loading = false + this.isLoading = false this.$store.dispatch({ type: 'loadPollMain', pollId: this.$route.params.id }) + .then(() => { + }) .catch(() => { - this.loading = false + this.isLoading = false }) }, @@ -127,13 +137,11 @@ export default { <style lang="scss" scoped> .main-container { flex: 1; + padding: 0 24px; margin: 0; flex-direction: column; flex-wrap: nowrap; overflow-x: scroll; - h1, h2, h3, h4 { - margin-left: 24px; - } } .icon.icon-settings.active { |