diff --git a/src/PublicShareAuthSidebar.vue b/src/PublicShareAuthSidebar.vue index ae6f8b5d2c2..3adadfd18eb 100644 --- a/src/PublicShareAuthSidebar.vue +++ b/src/PublicShareAuthSidebar.vue @@ -32,6 +32,7 @@ :is-sidebar="true" /> + @@ -46,6 +47,7 @@ import { loadState } from '@nextcloud/initial-state' import CallView from './components/CallView/CallView.vue' import ChatView from './components/ChatView.vue' import MediaSettings from './components/MediaSettings/MediaSettings.vue' +import PollViewer from './components/PollViewer/PollViewer.vue' import TopBar from './components/TopBar/TopBar.vue' import TransitionWrapper from './components/TransitionWrapper.vue' @@ -66,6 +68,7 @@ export default { CallView, ChatView, MediaSettings, + PollViewer, TopBar, TransitionWrapper, }, diff --git a/src/PublicShareSidebar.vue b/src/PublicShareSidebar.vue index ded5c415e19..779f4d023dd 100644 --- a/src/PublicShareSidebar.vue +++ b/src/PublicShareSidebar.vue @@ -46,6 +46,7 @@ + @@ -63,6 +64,7 @@ import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' import CallView from './components/CallView/CallView.vue' import ChatView from './components/ChatView.vue' import MediaSettings from './components/MediaSettings/MediaSettings.vue' +import PollViewer from './components/PollViewer/PollViewer.vue' import CallButton from './components/TopBar/CallButton.vue' import TopBar from './components/TopBar/TopBar.vue' import TransitionWrapper from './components/TransitionWrapper.vue' @@ -83,12 +85,13 @@ export default { name: 'PublicShareSidebar', components: { - NcButton, CallButton, CallView, ChatView, - PreventUnload, MediaSettings, + NcButton, + PollViewer, + PreventUnload, TopBar, TransitionWrapper, }, diff --git a/src/components/MessagesList/MessagesGroup/Message/MessagePart/MessageBody.vue b/src/components/MessagesList/MessagesGroup/Message/MessagePart/MessageBody.vue index 1d37ade1fb4..30dc957a848 100644 --- a/src/components/MessagesList/MessagesGroup/Message/MessagePart/MessageBody.vue +++ b/src/components/MessagesList/MessagesGroup/Message/MessagePart/MessageBody.vue @@ -28,7 +28,7 @@ :class="{ 'system-message': isSystemMessage && !showJoinCallButton, 'deleted-message': isDeletedMessage, - 'call-started': showJoinCallButton, + 'message-highlighted': showJoinCallButton, }"> @@ -49,6 +49,7 @@
@@ -284,6 +285,14 @@ export default { return this.messageParameters?.file }, + isNewPollMessage() { + if (this.messageParameters?.object?.type !== 'talk-poll') { + return false + } + + return this.isInCall && !!this.$store.getters.getNewPolls[this.messageParameters.object.id] + }, + hideDate() { return this.isTemporary || this.isDeleting || !!this.sendingFailure }, @@ -442,7 +451,7 @@ export default { padding: 0 20px; } - &.call-started { + &.message-highlighted { color: var(--color-text-light); background-color: var(--color-primary-element-light); padding: 10px; diff --git a/src/components/MessagesList/MessagesGroup/Message/MessagePart/Poll.vue b/src/components/MessagesList/MessagesGroup/Message/MessagePart/Poll.vue index 7332374fdcc..eb3c025738b 100644 --- a/src/components/MessagesList/MessagesGroup/Message/MessagePart/Poll.vue +++ b/src/components/MessagesList/MessagesGroup/Message/MessagePart/Poll.vue @@ -2,8 +2,9 @@ - @copyright Copyright (c) 2022 Marco Ambrosini - - @author Marco Ambrosini + - @author Maksim Sukharev - - - @license GNU AGPL version 3 or any later version + - @license AGPL-3.0-or-later - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as @@ -20,152 +21,43 @@ --> diff --git a/src/components/PollViewer/PollViewer.vue b/src/components/PollViewer/PollViewer.vue new file mode 100644 index 00000000000..7a0e332f011 --- /dev/null +++ b/src/components/PollViewer/PollViewer.vue @@ -0,0 +1,446 @@ + + + + + + + diff --git a/src/components/MessagesList/MessagesGroup/Message/MessagePart/PollVotersDetails.vue b/src/components/PollViewer/PollVotersDetails.vue similarity index 95% rename from src/components/MessagesList/MessagesGroup/Message/MessagePart/PollVotersDetails.vue rename to src/components/PollViewer/PollVotersDetails.vue index 977685f7c23..94df9557b42 100644 --- a/src/components/MessagesList/MessagesGroup/Message/MessagePart/PollVotersDetails.vue +++ b/src/components/PollViewer/PollVotersDetails.vue @@ -59,9 +59,9 @@ import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' import NcPopover from '@nextcloud/vue/dist/Components/NcPopover.js' -import AvatarWrapper from '../../../../AvatarWrapper/AvatarWrapper.vue' +import AvatarWrapper from '../AvatarWrapper/AvatarWrapper.vue' -import { ATTENDEE, AVATAR } from '../../../../../constants.js' +import { ATTENDEE, AVATAR } from '../../constants.js' export default { @@ -82,7 +82,7 @@ export default { container: { type: String, required: true, - } + }, }, setup() { diff --git a/src/constants.js b/src/constants.js index 109b3a0c6e3..9a40b0c2f3f 100644 --- a/src/constants.js +++ b/src/constants.js @@ -225,6 +225,23 @@ export const FLOW = { }, } +export const POLL = { + STATUS: { + OPEN: 0, + CLOSED: 1, + }, + + MODE: { + PUBLIC: 0, + HIDDEN: 1, + }, + + ANSWER_TYPE: { + MULTIPLE: 0, + SINGLE: 1, + }, +} + export const PRIVACY = { PUBLIC: 0, PRIVATE: 1, diff --git a/src/store/messagesStore.js b/src/store/messagesStore.js index 91962c6b603..4d4d25924fe 100644 --- a/src/store/messagesStore.js +++ b/src/store/messagesStore.js @@ -599,6 +599,9 @@ const actions = { if (message.messageParameters?.object || message.messageParameters?.file) { // Handle voice messages, shares with single file, polls, deck cards, e.t.c sharedItemsStore.addSharedItemFromMessage(token, message) + if (message.messageParameters?.object?.type === 'talk-poll') { + EventBus.$emit('talk:poll-added', { token, message }) + } } else if (Object.keys(message.messageParameters).some(key => key.startsWith('file'))) { // Handle shares with multiple files } diff --git a/src/store/pollStore.js b/src/store/pollStore.js index 674d68003e8..22cd458d082 100644 --- a/src/store/pollStore.js +++ b/src/store/pollStore.js @@ -22,18 +22,29 @@ import debounce from 'debounce' import Vue from 'vue' -import { showError } from '@nextcloud/dialogs' +import { showError, showInfo, TOAST_PERMANENT_TIMEOUT } from '@nextcloud/dialogs' import pollService from '../services/pollService.js' const state = { polls: {}, + pollDebounceFunctions: {}, + activePoll: null, + pollToastsQueue: {}, } const getters = { getPoll: (state) => (token, id) => { return state.polls?.[token]?.[id] }, + + activePoll: (state) => { + return state.activePoll + }, + + getNewPolls: (state) => { + return state.pollToastsQueue + }, } const mutations = { @@ -44,15 +55,40 @@ const mutations = { Vue.set(state.polls[token], poll.id, poll) }, + setActivePoll(state, { token, pollId, name }) { + Vue.set(state, 'activePoll', { token, id: pollId, name }) + }, + + removeActivePoll(state) { + if (state.activePoll) { + Vue.set(state, 'activePoll', null) + } + }, + + addPollToast(state, { pollId, toast }) { + Vue.set(state.pollToastsQueue, pollId, toast) + }, + + hidePollToast(state, id) { + if (state.pollToastsQueue[id]) { + state.pollToastsQueue[id].hideToast() + Vue.delete(state.pollToastsQueue, id) + } + }, + + hideAllPollToasts(state) { + for (const id in state.pollToastsQueue) { + state.pollToastsQueue[id].hideToast() + Vue.delete(state.pollToastsQueue, id) + } + }, + // Add debounce function for getting the poll data addDebounceGetPollDataFunction(state, { token, pollId, debounceGetPollDataFunction }) { - if (!state.polls?.pollDebounceFunctions) { - Vue.set(state.polls, 'pollDebounceFunctions', {}) + if (!state.pollDebounceFunctions[token]) { + Vue.set(state.pollDebounceFunctions, token, {}) } - if (!state.polls.pollDebounceFunctions?.[token]) { - Vue.set(state.polls.pollDebounceFunctions, [token], {}) - } - Vue.set(state.polls.pollDebounceFunctions[token], pollId, debounceGetPollDataFunction) + Vue.set(state.pollDebounceFunctions[token], pollId, debounceGetPollDataFunction) }, } @@ -85,7 +121,7 @@ const actions = { debounceGetPollData(context, { token, pollId }) { // Create debounce function for getting this particular poll data // if it does not exist yet - if (!context.state.polls?.pollDebounceFunctions?.[token]?.[pollId]) { + if (!context.state.pollDebounceFunctions[token]?.[pollId]) { const debounceGetPollDataFunction = debounce(async () => { await context.dispatch('getPollData', { token, @@ -100,7 +136,7 @@ const actions = { }) } // Call the debounce function for getting the poll data - context.state.polls.pollDebounceFunctions[token][pollId]() + context.state.pollDebounceFunctions[token][pollId]() }, async submitVote(context, { token, pollId, vote }) { @@ -126,6 +162,41 @@ const actions = { showError(t('spreed', 'An error occurred while ending the poll')) } }, + + setActivePoll(context, { token, pollId, name }) { + context.commit('setActivePoll', { token, pollId, name }) + }, + + removeActivePoll(context) { + context.commit('removeActivePoll') + }, + + addPollToast(context, { token, message }) { + const pollId = message.messageParameters.object.id + const name = message.messageParameters.object.name + + const toast = showInfo(t('spreed', 'Poll "{name}" was created by {user}. Click to vote', { + name, + user: message.actorDisplayName, + }), { + onClick: () => { + if (!context.state.activePoll) { + context.dispatch('setActivePoll', { token, pollId, name }) + } + }, + timeout: TOAST_PERMANENT_TIMEOUT, + }) + + context.commit('addPollToast', { pollId, toast }) + }, + + hidePollToast(context, id) { + context.commit('hidePollToast', id) + }, + + hideAllPollToasts(context) { + context.commit('hideAllPollToasts') + }, } export default { state, mutations, getters, actions } diff --git a/src/views/FilesSidebarChatView.vue b/src/views/FilesSidebarChatView.vue index 527ffb333f4..3bbaf4e19bd 100644 --- a/src/views/FilesSidebarChatView.vue +++ b/src/views/FilesSidebarChatView.vue @@ -23,6 +23,7 @@
+
@@ -30,6 +31,7 @@ import ChatView from '../components/ChatView.vue' import MediaSettings from '../components/MediaSettings/MediaSettings.vue' +import PollViewer from '../components/PollViewer/PollViewer.vue' import CallButton from '../components/TopBar/CallButton.vue' export default { @@ -40,6 +42,7 @@ export default { CallButton, ChatView, MediaSettings, + PollViewer, }, data() { diff --git a/src/views/MainView.vue b/src/views/MainView.vue index 26a17e708be..3ac396f359f 100644 --- a/src/views/MainView.vue +++ b/src/views/MainView.vue @@ -9,6 +9,7 @@ +
@@ -17,6 +18,7 @@ import CallView from '../components/CallView/CallView.vue' import ChatView from '../components/ChatView.vue' import LobbyScreen from '../components/LobbyScreen.vue' +import PollViewer from '../components/PollViewer/PollViewer.vue' import TopBar from '../components/TopBar/TopBar.vue' import TransitionWrapper from '../components/TransitionWrapper.vue' @@ -25,10 +27,11 @@ import { useIsInCall } from '../composables/useIsInCall.js' export default { name: 'MainView', components: { + CallView, ChatView, LobbyScreen, + PollViewer, TopBar, - CallView, TransitionWrapper, },