Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: draft polls management πŸ“Š #13518

Draft
wants to merge 1 commit into
base: feat/13439/drafts-for-polls-frontend
Choose a base branch
from
Draft
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
73 changes: 70 additions & 3 deletions src/components/NewMessage/NewMessagePollEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@
:close-on-click-outside="!isFilled"
v-on="$listeners"
@update:open="emit('close')">
<!-- Draft imports -->
<div v-if="supportPollDrafts && isModerator" class="poll-editor__wrapper">
<NcSelect v-model="selectedDraft"
class="poll-editor__select"
name="poll_drafts_select"
label="question"
:options="pollDrafts"
:input-label="t('spreed', 'Choose poll from drafts:')"
:placeholder="t('spreed', 'Select a draft')"
:loading="!pollsStore.drafts[props.token]" />
</div>

<!-- Poll Question -->
<p class="poll-editor__caption">
{{ t('spreed', 'Question') }}
Expand Down Expand Up @@ -58,6 +70,9 @@
<NcButton type="tertiary" @click="emit('close')">
{{ t('spreed', 'Dismiss') }}
</NcButton>
<NcButton type="secondary" :disabled="!isFilled" @click="createPollDraft">
{{ t('spreed', 'Save as draft') }}
</NcButton>
<NcButton type="primary" :disabled="!isFilled" @click="createPoll">
{{ t('spreed', 'Create poll') }}
</NcButton>
Expand All @@ -66,19 +81,23 @@
</template>

<script setup lang="ts">
import { computed, nextTick, reactive, ref } from 'vue'
import { computed, nextTick, reactive, ref, watch } from 'vue'

import Close from 'vue-material-design-icons/Close.vue'
import Plus from 'vue-material-design-icons/Plus.vue'

import { showSuccess } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'

import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js'
import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'

import { useStore } from '../../composables/useStore.js'
import { POLL } from '../../constants.js'
import { hasTalkFeature } from '../../services/CapabilitiesManager.ts'
import { usePollsStore } from '../../stores/polls.ts'
import type { createPollParams } from '../../types/index.ts'

Expand All @@ -89,16 +108,20 @@ const emit = defineEmits<{
(event: 'close'): void,
}>()

const supportPollDrafts = hasTalkFeature(props.token, 'talk-polls-drafts')

const store = useStore()
const pollsStore = usePollsStore()

const pollOption = ref(null)

const pollForm = reactive<createPollParams>({
const POLL_FORM: createPollParams = {
question: '',
options: ['', ''],
resultMode: POLL.MODE.PUBLIC,
maxVotes: POLL.ANSWER_TYPE.SINGLE,
})
}
const pollForm = reactive({ ...POLL_FORM })

const isFilled = computed(() => !!pollForm.question || pollForm.options.some(option => option))

Expand All @@ -120,6 +143,17 @@ const isMultipleAnswer = computed({
}
})

/**
* Receive poll drafts for the current conversation as owner/moderator
*/
const isModerator = computed(() => (store.getters as unknown).isModerator)
if (supportPollDrafts && isModerator.value) {
pollsStore.getPollDrafts(props.token)
}
const pollDrafts = computed(() => supportPollDrafts ? pollsStore.getDrafts(props.token) : [])
const selectedDraft = ref(null)
watch(selectedDraft, (value) => fillPollForm(value !== null ? value : { ...POLL_FORM }))

/**
* Remove a previously added option
* @param index option index
Expand Down Expand Up @@ -150,6 +184,29 @@ async function createPoll() {
emit('close')
}
}

/**
* Insert data into form fields
* @param payload data to fill with
*/
function fillPollForm(payload: createPollParams) {
for (const key of Object.keys(pollForm)) {
pollForm[key] = payload[key]
}
}

/**
* Saves a poll draft for this conversation
*/
async function createPollDraft() {
const poll = await pollsStore.createPollDraft({
token: props.token,
form: pollForm,
})
if (poll) {
showSuccess(t('spreed', 'Poll draft has been saved'))
}
}
</script>

<style lang="scss" scoped>
Expand All @@ -160,6 +217,16 @@ async function createPoll() {
color: var(--color-primary-element);
}

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

&__select {
width: 100%;
}

&__option {
display: flex;
align-items: flex-end;
Expand Down
64 changes: 41 additions & 23 deletions src/components/PollViewer/PollViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,13 @@
@click="modalPage = 'voting'">
{{ t('spreed', 'Change your vote') }}
</NcButton>
<!-- End poll button-->
<NcActions v-if="canEndPoll" force-menu>
<NcActionButton @click="createPollDraft">
<template #icon>
<IconFileEdit :size="20" />
</template>
{{ t('spreed', 'Save as draft') }}
</NcActionButton>
<NcActionButton class="critical" @click="endPoll">
{{ t('spreed', 'End poll') }}
<template #icon>
Expand All @@ -81,17 +86,27 @@
</NcActionButton>
</NcActions>
</div>
<div v-else-if="selfIsOwnerOrModerator" class="poll-modal__actions">
<NcButton type="tertiary" @click="createPollDraft">
<template #icon>
<IconFileEdit :size="20" />
</template>
{{ t('spreed', 'Save as draft') }}
</NcButton>
</div>
</div>
<NcLoadingIcon v-else class="poll-modal__loading" />
</NcModal>
</template>

<script>
import { ref } from 'vue'
import { computed, ref } from 'vue'

import IconFileEdit from 'vue-material-design-icons/FileEdit.vue'
import FileLock from 'vue-material-design-icons/FileLock.vue'
import PollIcon from 'vue-material-design-icons/Poll.vue'

import { showSuccess } from '@nextcloud/dialogs'
import { t, n } from '@nextcloud/l10n'

import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
Expand Down Expand Up @@ -124,6 +139,7 @@ export default {
PollVotersDetails,
// icons
FileLock,
IconFileEdit,
PollIcon,
},

Expand All @@ -133,37 +149,29 @@ export default {
const loading = ref(false)
const dialogHeaderId = `guest-welcome-header-${useId()}`

const pollsStore = usePollsStore()
const activePoll = computed(() => pollsStore.activePoll)
const name = computed(() => activePoll.value?.name)
const id = computed(() => activePoll.value?.id)
const token = computed(() => activePoll.value?.token)

const poll = computed(() => pollsStore.getPoll(token.value, id.value))

return {
isInCall: useIsInCall(),
pollsStore: usePollsStore(),
pollsStore,
voteToSubmit,
modalPage,
loading,
dialogHeaderId,
name,
id,
token,
poll,
}
},

computed: {
activePoll() {
return this.pollsStore.activePoll
},

name() {
return this.activePoll?.name
},

id() {
return this.activePoll?.id
},

token() {
return this.activePoll?.token
},

poll() {
return this.pollsStore.getPoll(this.token, this.id)
},

selfHasVoted() {
return this.poll?.votedSelf?.length > 0
},
Expand Down Expand Up @@ -324,6 +332,16 @@ export default {
this.loading = false
},

async createPollDraft() {
const poll = await this.pollsStore.createPollDraft({
token: this.token,
form: this.poll,
})
if (poll) {
showSuccess(t('spreed', 'Poll draft has been saved'))
}
},

selfHasVotedOption(index) {
return this.poll?.votedSelf.includes(index)
},
Expand Down