Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,31 @@
class="poll-card"
role="button"
@click="openPoll">
<div class="poll-card__status">
<template v-if="showPollStatus">
<NcChip
:variant="pollImportance"
no-close>
<template #icon>
<IconCircle
:size="10" />
</template>
{{ poll?.status === POLL.STATUS.OPEN ? t('spreed', 'Open poll') : t('spreed', 'Closed poll') }}
</NcChip>
<NcChip
v-if="poll?.resultMode === POLL.MODE.HIDDEN"
variant="tertiary"
no-close>
<template #icon>
<IconAccountCircleOutline :size="15" />
</template>
<!-- TRANSLATORS: "Anonymous" refers to a poll where votes are anonymous -->
{{ t('spreed', 'Anonymous') }}
</NcChip>
</template>
</div>
<span class="poll-card__header">
<IconPoll class="poll-card__header-icon" :size="20" />
<IconPoll class="poll-card__header-icon" :size="25" />
<span class="poll-card__header-name">{{ name }}</span>
</span>
<span class="poll-card__footer">
Expand All @@ -62,8 +85,11 @@
import { n, t } from '@nextcloud/l10n'
import { vIntersectionObserver as IntersectionObserver } from '@vueuse/components'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcChip from '@nextcloud/vue/components/NcChip'
import IconAccountCircleOutline from 'vue-material-design-icons/AccountCircleOutline.vue'
import IconPoll from 'vue-material-design-icons/ChartBoxOutline.vue'
import IconCircle from 'vue-material-design-icons/Circle.vue'
import IconPencilOutline from 'vue-material-design-icons/PencilOutline.vue'
import IconPoll from 'vue-material-design-icons/Poll.vue'
import IconTrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue'
import { POLL } from '../../../../../constants.ts'
import { hasTalkFeature } from '../../../../../services/CapabilitiesManager.ts'
Expand All @@ -77,6 +103,9 @@ export default {
IconTrashCanOutline,
IconPencilOutline,
IconPoll,
NcChip,
IconCircle,
IconAccountCircleOutline,
},

directives: {
Expand Down Expand Up @@ -115,6 +144,7 @@ export default {
setup() {
return {
pollsStore: usePollsStore(),
POLL,
}
},

Expand All @@ -128,20 +158,30 @@ export default {
pollFooterText() {
if (this.poll?.status === POLL.STATUS.OPEN) {
return this.poll?.votedSelf.length > 0
? t('spreed', 'Open poll • You voted already')
: t('spreed', 'Open poll • Click to vote')
? t('spreed', 'Click to change your vote')
: t('spreed', 'Click to vote')
} else if (this.draft) {
return n('spreed', 'Poll draft • %n option', 'Poll draft • %n options', this.poll?.options?.length)
} else {
return this.poll?.status === POLL.STATUS.CLOSED
? t('spreed', 'Poll • Ended')
: t('spreed', 'Poll')
return n('spreed', '%n vote', '%n votes', this.poll?.numVoters || 0)
}
},

canEditPollDraft() {
return this.draft && hasTalkFeature(this.token, 'edit-draft-poll')
},

showPollStatus() {
return this.poll && Object.keys(this.poll).length > 0 && this.poll.status !== POLL.STATUS.DRAFT
},

pollImportance() {
if (this.poll.status === POLL.STATUS.OPEN) {
return this.poll?.votedSelf.length > 0 ? 'secondary' : 'primary'
} else {
return 'tertiary'
}
},
},

methods: {
Expand Down Expand Up @@ -200,6 +240,14 @@ export default {
outline: none;
}

.poll-card__status {
min-height: calc( 24px + var(--default-grid-baseline));
display: flex;
gap: var(--default-grid-baseline);
border-bottom: 1px solid var(--color-border-dark);
padding-bottom: var(--default-grid-baseline);
}

&__header {
display: flex;
align-items: flex-start;
Expand Down
121 changes: 88 additions & 33 deletions src/components/PollViewer/PollViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,45 @@
@close="dismissModal">
<div v-if="poll" class="poll-modal">
<div class="poll-modal__header">
<IconPoll :size="20" />
<IconPoll :size="25" />
<span :id="dialogHeaderId" role="heading" aria-level="2">
{{ name }}
</span>
</div>
<p class="poll-modal__summary">
{{ pollSummaryText }}
</p>
<div class="poll-modal__summary">
<div class="poll-modal__summary-details">
<span>
{{ pollSummaryText }}
</span>
<NcPopover
v-if="!isPollPublic"
placement="bottom"
:triggers="['hover']">
<template #trigger>
<NcButton variant="tertiary-no-background" :aria-label="t('spreed', 'Anonymous poll information')">
<template #icon>
<IconInformationOutline :size="16" />
</template>
</NcButton>
</template>
<template #default>
<span class="poll-popover__text">
{{ t('spreed', 'This poll is anonymous, identities of voters are hidden from everyone.') }}
</span>
</template>
</NcPopover>
</div>
<div
v-if="isPollOpen"
class="poll-modal__hint">
<span v-if="!isMultipleAnswers">
{{ t('spreed', 'You can select only one answer') }}
</span>
<span v-else>
{{ t('spreed', 'You can select multiple answers') }}
</span>
</div>
</div>

<!-- options -->
<div v-if="modalPage === 'voting'" class="poll-modal__options">
Expand Down Expand Up @@ -68,18 +99,10 @@
<div v-if="isPollOpen" class="poll-modal__actions">
<!-- Submit vote button-->
<NcButton
v-if="modalPage === 'voting'"
variant="primary"
:disabled="disabled"
@click="submitVote">
{{ t('spreed', 'Submit vote') }}
</NcButton>
<!-- Vote again-->
<NcButton
v-else
variant="secondary"
@click="modalPage = 'voting'">
{{ t('spreed', 'Change your vote') }}
{{ !selfHasVoted ? t('spreed', 'Submit vote') : t('spreed', 'Change your vote') }}
</NcButton>
<NcActions v-if="canEndPoll" force-menu>
<NcActionButton v-if="supportPollDrafts && isModerator" @click="createPollDraft">
Expand Down Expand Up @@ -134,11 +157,13 @@ import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwit
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import NcModal from '@nextcloud/vue/components/NcModal'
import NcPopover from '@nextcloud/vue/components/NcPopover'
import NcProgressBar from '@nextcloud/vue/components/NcProgressBar'
import IconPoll from 'vue-material-design-icons/ChartBoxOutline.vue'
import IconCheck from 'vue-material-design-icons/Check.vue'
import IconFileEditOutline from 'vue-material-design-icons/FileEditOutline.vue'
import IconFileLockOutline from 'vue-material-design-icons/FileLockOutline.vue'
import IconPoll from 'vue-material-design-icons/Poll.vue'
import IconInformationOutline from 'vue-material-design-icons/InformationOutline.vue'
import PollVotersDetails from './PollVotersDetails.vue'
import IconFileDownload from '../../../img/material-icons/file-download.svg?raw'
import { useIsInCall } from '../../composables/useIsInCall.js'
Expand All @@ -162,13 +187,15 @@ export default {
NcModal,
NcButton,
NcIconSvgWrapper,
NcPopover,
NcProgressBar,
PollVotersDetails,
// icons
IconCheck,
IconFileLockOutline,
IconFileEditOutline,
IconPoll,
IconInformationOutline,
},

setup() {
Expand Down Expand Up @@ -210,6 +237,7 @@ export default {
supportPollDrafts,
exportPollURI,
exportPollFileName,
POLL,
}
},

Expand Down Expand Up @@ -245,7 +273,14 @@ export default {
},

disabled() {
return this.loading || this.voteToSubmit.length === 0
return (this.loading || this.voteToSubmit.length === 0 // no option is selected or it is loading
|| (this.selfHasVoted && this.isSameVote)) // user has voted and did not change the vote
&& (this.modalPage !== 'results' || !this.isPollPublic) // if results are shown, allow changing vote for public polls
},

isSameVote() {
return this.voteToSubmit.length === this.poll?.votedSelf.length
&& this.voteToSubmit.every((val) => this.poll?.votedSelf.includes(+val))
},

isModerator() {
Expand All @@ -259,18 +294,14 @@ export default {

pollSummaryText() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe don't change these strings? They feel more confusing to me

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean confusing?

if (this.isPollClosed) {
return n('spreed', 'Poll results • %n vote', 'Poll results • %n votes', this.poll?.numVoters)
}

if (this.isPollPublic && (this.selfIsOwnerOrModerator || this.selfHasVoted)) {
return n('spreed', 'Open poll • %n vote', 'Open poll • %n votes', this.poll?.numVoters)
return n('spreed', 'Final results • %n vote', 'Final results • %n votes', this.poll?.numVoters)
}

if (!this.isPollPublic && this.selfHasVoted) {
return t('spreed', 'Open pollYou voted already')
if (this.selfIsOwnerOrModerator || (this.selfHasVoted && this.isPollPublic)) {
return n('spreed', 'Poll open%n vote', 'Poll open • %n votes', this.poll?.numVoters)
}

return t('spreed', 'Open poll')
return 'Poll open'
},

canEndPoll() {
Expand Down Expand Up @@ -309,7 +340,7 @@ export default {
handler(value) {
if (!value) {
this.modalPage = ''
} else if (this.selfHasVoted || this.isPollClosed) {
} else if (this.isPollClosed || (this.isPollPublic && this.selfHasVoted)) {
this.modalPage = 'results'
} else {
this.modalPage = 'voting'
Expand Down Expand Up @@ -358,17 +389,23 @@ export default {
},

async submitVote() {
if (this.modalPage !== 'voting') {
this.modalPage = 'voting'
return
}

this.loading = true
try {
await this.pollsStore.submitVote({
token: this.token,
pollId: this.id,
optionIds: this.voteToSubmit.map((element) => +element),
})
this.modalPage = 'results'
if (this.isPollPublic) {
this.modalPage = 'results'
}
} catch (error) {
console.error(error)
this.modalPage = 'voting'
}
this.loading = false
},
Expand Down Expand Up @@ -408,7 +445,7 @@ export default {
<style lang="scss" scoped>
.poll-modal {
position: relative;
padding: 20px;
padding: calc(3 * var(--default-grid-baseline));

&__header {
display: flex;
Expand All @@ -419,15 +456,17 @@ export default {
font-size: 18px;
white-space: normal;
word-wrap: anywhere;

:deep(.material-design-icon) {
margin-bottom: auto;
}
}

&__summary {
color: var(--color-text-maxcontrast);
margin-bottom: 16px;

&-details {
display: flex;
align-items: center;
gap: var(--default-grid-baseline);
}
}

&__options {
Expand All @@ -450,6 +489,12 @@ export default {
&__loading {
height: 200px;
}

&__hint {
display: flex;
align-items: center;
gap: var(--default-grid-baseline);
}
}

.results__options {
Expand All @@ -463,9 +508,11 @@ export default {
.results__option {
display: flex;
flex-direction: column;
padding: var(--default-grid-baseline);
border-radius: var(--border-radius-large);

&:not(:last-child) {
border-bottom: 1px solid var(--color-border);
&:hover {
background-color: var(--color-background-hover);
}

&__details {
Expand Down Expand Up @@ -496,6 +543,14 @@ export default {
}
}

.poll-popover__text {
display: block;
padding: calc(var(--default-grid-baseline) / 2);
max-width: 250px;
text-align: center;
color: var(--color-text-maxcontrast);
}

.critical :deep(.action-button) {
color: var(--color-text-error) !important;
}
Expand Down
Loading