diff --git a/src/components/BreakoutRoomsEditor/BreakoutRoomsEditor.vue b/src/components/BreakoutRoomsEditor/BreakoutRoomsEditor.vue index efd539696d7..8774914f4be 100644 --- a/src/components/BreakoutRoomsEditor/BreakoutRoomsEditor.vue +++ b/src/components/BreakoutRoomsEditor/BreakoutRoomsEditor.vue @@ -97,6 +97,8 @@ import NcModal from '@nextcloud/vue/dist/Components/NcModal.js' import BreakoutRoomsParticipantsEditor from './BreakoutRoomsParticipantsEditor.vue' +import { useBreakoutRoomsStore } from '../../stores/breakoutRooms.js' + export default { name: 'BreakoutRoomsEditor', @@ -117,6 +119,12 @@ export default { emits: ['close'], + setup() { + return { + breakoutRoomsStore: useBreakoutRoomsStore(), + } + }, + data() { return { mode: '1', @@ -148,7 +156,7 @@ export default { methods: { async handleCreateRooms() { try { - await this.$store.dispatch('configureBreakoutRoomsAction', { + await this.breakoutRoomsStore.configureBreakoutRooms({ token: this.token, mode: this.mode, amount: this.amount, diff --git a/src/components/BreakoutRoomsEditor/BreakoutRoomsParticipantsEditor.vue b/src/components/BreakoutRoomsEditor/BreakoutRoomsParticipantsEditor.vue index 7d21cf691c7..ee432733a9f 100644 --- a/src/components/BreakoutRoomsEditor/BreakoutRoomsParticipantsEditor.vue +++ b/src/components/BreakoutRoomsEditor/BreakoutRoomsParticipantsEditor.vue @@ -119,6 +119,7 @@ import SelectableParticipant from './SelectableParticipant.vue' import BreakoutRoomItem from '../RightSidebar/BreakoutRooms/BreakoutRoomItem.vue' import { ATTENDEE, CONVERSATION, PARTICIPANT } from '../../constants.js' +import { useBreakoutRoomsStore } from '../../stores/breakoutRooms.js' export default { name: 'BreakoutRoomsParticipantsEditor', @@ -149,12 +150,18 @@ export default { breakoutRooms: { type: Array, - default: undefined, + default: () => [], }, }, emits: ['back', 'close'], + setup() { + return { + breakoutRoomsStore: useBreakoutRoomsStore(), + } + }, + data() { return { selectedParticipants: [], @@ -207,7 +214,7 @@ export default { // If the breakoutRooms prop is populated it means that this component is // being used to reorganize the attendees of an existing breakout room. isReorganizingAttendees() { - return this.breakoutRooms?.length + return this.breakoutRooms.length }, confirmButtonLabel() { @@ -316,7 +323,7 @@ export default { }, createRooms() { - this.$store.dispatch('configureBreakoutRoomsAction', { + this.breakoutRoomsStore.configureBreakoutRooms({ token: this.token, mode: 2, amount: this.roomNumber, @@ -326,7 +333,7 @@ export default { }, reorganizeAttendees() { - this.$store.dispatch('reorganizeAttendeesAction', { + this.breakoutRoomsStore.reorganizeAttendees({ token: this.token, attendeeMap: this.createAttendeeMap(), }) @@ -338,9 +345,7 @@ export default { }, deleteBreakoutRooms() { - this.$store.dispatch('deleteBreakoutRoomsAction', { - token: this.token, - }) + this.breakoutRoomsStore.deleteBreakoutRooms(this.token) }, }, } diff --git a/src/components/NewMessage/NewMessage.vue b/src/components/NewMessage/NewMessage.vue index 15092792a81..8e81bd6abaa 100644 --- a/src/components/NewMessage/NewMessage.vue +++ b/src/components/NewMessage/NewMessage.vue @@ -224,6 +224,7 @@ import BrowserStorage from '../../services/BrowserStorage.js' import { EventBus } from '../../services/EventBus.js' import { shareFile } from '../../services/filesSharingServices.js' import { searchPossibleMentions } from '../../services/mentionsService.js' +import { useBreakoutRoomsStore } from '../../stores/breakoutRooms.js' import { useChatExtrasStore } from '../../stores/chatExtras.js' import { useSettingsStore } from '../../stores/settings.js' import { fetchClipboardContent } from '../../utils/clipboard.js' @@ -314,12 +315,10 @@ export default { expose: ['focusInput'], setup() { - const chatExtrasStore = useChatExtrasStore() - const settingsStore = useSettingsStore() - return { - chatExtrasStore, - settingsStore, + breakoutRoomsStore: useBreakoutRoomsStore(), + chatExtrasStore: useChatExtrasStore(), + settingsStore: useSettingsStore(), supportTypingStatus, } }, @@ -719,7 +718,7 @@ export default { // Broadcast message to all breakout rooms async broadcastMessage(token, message) { try { - await this.$store.dispatch('broadcastMessageToBreakoutRoomsAction', { token, message }) + await this.breakoutRoomsStore.broadcastMessageToBreakoutRooms({ token, message }) this.$emit('sent') } catch { this.$emit('failure') diff --git a/src/components/RightSidebar/BreakoutRooms/BreakoutRoomItem.vue b/src/components/RightSidebar/BreakoutRooms/BreakoutRoomItem.vue index f2545349121..7ff6fd03b11 100644 --- a/src/components/RightSidebar/BreakoutRooms/BreakoutRoomItem.vue +++ b/src/components/RightSidebar/BreakoutRooms/BreakoutRoomItem.vue @@ -93,6 +93,7 @@ import SendMessageDialog from '../../BreakoutRoomsEditor/SendMessageDialog.vue' import { CONVERSATION, PARTICIPANT } from '../../../constants.js' import { EventBus } from '../../../services/EventBus.js' +import { useBreakoutRoomsStore } from '../../../stores/breakoutRooms.js' export default { name: 'BreakoutRoomItem', @@ -138,6 +139,12 @@ export default { }, }, + setup() { + return { + breakoutRoomsStore: useBreakoutRoomsStore(), + } + }, + data() { return { showParticipants: true, @@ -217,7 +224,7 @@ export default { }, dismissRequestAssistance() { - this.$store.dispatch('resetRequestAssistanceAction', { token: this.roomToken }) + this.breakoutRoomsStore.dismissRequestAssistance(this.roomToken) }, async joinRoom() { @@ -228,8 +235,8 @@ export default { } else { try { if (this.mainConversation.breakoutRoomMode === CONVERSATION.BREAKOUT_ROOM_MODE.FREE) { - await this.$store.dispatch('switchToBreakoutRoomAction', { - token: this.$store.getters.parentRoomToken(this.roomToken), + await this.breakoutRoomsStore.switchToBreakoutRoom({ + token: this.breakoutRoomsStore.getParentRoomToken(this.roomToken), target: this.roomToken, }) } diff --git a/src/components/RightSidebar/BreakoutRooms/BreakoutRoomsActions.vue b/src/components/RightSidebar/BreakoutRooms/BreakoutRoomsActions.vue index 6920ed88d6e..a1a2441cf22 100644 --- a/src/components/RightSidebar/BreakoutRooms/BreakoutRoomsActions.vue +++ b/src/components/RightSidebar/BreakoutRooms/BreakoutRoomsActions.vue @@ -124,6 +124,7 @@ import SendMessageDialog from '../../BreakoutRoomsEditor/SendMessageDialog.vue' import { useIsInCall } from '../../../composables/useIsInCall.js' import { CONVERSATION, PARTICIPANT } from '../../../constants.js' import { EventBus } from '../../../services/EventBus.js' +import { useBreakoutRoomsStore } from '../../../stores/breakoutRooms.js' export default { name: 'BreakoutRoomsActions', @@ -169,8 +170,10 @@ export default { }, setup() { - const isInCall = useIsInCall() - return { isInCall } + return { + isInCall: useIsInCall(), + breakoutRoomsStore: useBreakoutRoomsStore(), + } }, data() { @@ -235,11 +238,11 @@ export default { methods: { startBreakoutRooms() { - this.$store.dispatch('startBreakoutRoomsAction', this.mainToken) + this.breakoutRoomsStore.startBreakoutRooms(this.mainToken) }, stopBreakoutRooms() { - this.$store.dispatch('stopBreakoutRoomsAction', this.mainToken) + this.breakoutRoomsStore.stopBreakoutRooms(this.mainToken) }, openSendMessageDialog() { diff --git a/src/components/RightSidebar/BreakoutRooms/BreakoutRoomsTab.vue b/src/components/RightSidebar/BreakoutRooms/BreakoutRoomsTab.vue index b9932706ea7..41485c64349 100644 --- a/src/components/RightSidebar/BreakoutRooms/BreakoutRoomsTab.vue +++ b/src/components/RightSidebar/BreakoutRooms/BreakoutRoomsTab.vue @@ -58,6 +58,7 @@ import BreakoutRoomsActions from './BreakoutRoomsActions.vue' import Participant from '../Participants/Participant.vue' import { CONVERSATION, PARTICIPANT } from '../../../constants.js' +import { useBreakoutRoomsStore } from '../../../stores/breakoutRooms.js' export default { name: 'BreakoutRoomsTab', @@ -90,6 +91,12 @@ export default { }, }, + setup() { + return { + breakoutRoomsStore: useBreakoutRoomsStore(), + } + }, + data() { return { breakoutRoomsParticipantsInterval: undefined, @@ -111,7 +118,7 @@ export default { }, breakoutRooms() { - return this.$store.getters.breakoutRooms(this.mainToken) + return this.breakoutRoomsStore.breakoutRooms(this.mainToken) }, breakoutRoomsConfigured() { @@ -139,7 +146,9 @@ export default { mounted() { // Get the breakout room every time the tab is mounted - this.getBreakoutRooms() + if (this.breakoutRoomsConfigured) { + this.breakoutRoomsStore.getBreakoutRooms(this.mainToken) + } }, beforeDestroy() { @@ -148,19 +157,9 @@ export default { }, methods: { - getBreakoutRooms() { - if (this.breakoutRoomsConfigured) { - this.$store.dispatch('getBreakoutRoomsAction', { - token: this.mainToken, - }) - } - }, - getParticipants() { if (this.breakoutRoomsConfigured) { - this.$store.dispatch('getBreakoutRoomsParticipantsAction', { - token: this.mainToken, - }) + this.breakoutRoomsStore.fetchBreakoutRoomsParticipants(this.mainToken) } }, }, diff --git a/src/components/TopBar/CallButton.vue b/src/components/TopBar/CallButton.vue index 5cae9914221..108b78e45c8 100644 --- a/src/components/TopBar/CallButton.vue +++ b/src/components/TopBar/CallButton.vue @@ -116,6 +116,7 @@ import { useIsInCall } from '../../composables/useIsInCall.js' import { ATTENDEE, CALL, CONVERSATION, PARTICIPANT } from '../../constants.js' import { callSIPDialOut } from '../../services/callsService.js' import { EventBus } from '../../services/EventBus.js' +import { useBreakoutRoomsStore } from '../../stores/breakoutRooms.js' import { useSettingsStore } from '../../stores/settings.js' import { useTalkHashStore } from '../../stores/talkHash.js' import { blockCalls, unsupportedWarning } from '../../utils/browserCheck.js' @@ -184,13 +185,11 @@ export default { }, setup() { - const isInCall = useIsInCall() - const talkHashStore = useTalkHashStore() - const settingsStore = useSettingsStore() return { - isInCall, - settingsStore, - talkHashStore, + isInCall: useIsInCall(), + breakoutRoomsStore: useBreakoutRoomsStore(), + talkHashStore: useTalkHashStore(), + settingsStore: useSettingsStore(), } }, @@ -430,9 +429,8 @@ export default { }, async switchToParentRoom() { - const parentRoomToken = this.$store.getters.parentRoomToken(this.token) EventBus.$emit('switch-to-conversation', { - token: parentRoomToken, + token: this.breakoutRoomsStore.getParentRoomToken(this.token), }) }, diff --git a/src/components/TopBar/TopBarMenu.vue b/src/components/TopBar/TopBarMenu.vue index 3867a7567f0..01d5a3bc654 100644 --- a/src/components/TopBar/TopBarMenu.vue +++ b/src/components/TopBar/TopBarMenu.vue @@ -190,6 +190,7 @@ import PromotedView from '../../assets/missingMaterialDesignIcons/PromotedView.v import { useIsInCall } from '../../composables/useIsInCall.js' import { CALL, CONVERSATION, PARTICIPANT } from '../../constants.js' +import { useBreakoutRoomsStore } from '../../stores/breakoutRooms.js' import { generateAbsoluteUrl } from '../../utils/handleUrl.ts' import { callParticipantCollection } from '../../utils/webrtc/index.js' @@ -252,8 +253,10 @@ export default { emits: ['open-breakout-rooms-editor'], setup() { - const isInCall = useIsInCall() - return { isInCall } + return { + isInCall: useIsInCall(), + breakoutRoomsStore: useBreakoutRoomsStore(), + } }, data() { @@ -475,9 +478,9 @@ export default { } const hasAssistanceRequested = this.conversation.breakoutRoomStatus === CONVERSATION.BREAKOUT_ROOM_STATUS.STATUS_ASSISTANCE_REQUESTED if (newState && !hasAssistanceRequested) { - this.$store.dispatch('requestAssistanceAction', { token: this.token }) + this.breakoutRoomsStore.requestAssistance(this.token) } else if (!newState && hasAssistanceRequested) { - this.$store.dispatch('resetRequestAssistanceAction', { token: this.token }) + this.breakoutRoomsStore.dismissRequestAssistance(this.token) } } }, diff --git a/src/services/breakoutRoomsService.js b/src/services/breakoutRoomsService.js index 5e9f475deb5..6457b115310 100644 --- a/src/services/breakoutRoomsService.js +++ b/src/services/breakoutRoomsService.js @@ -114,7 +114,7 @@ const broadcastMessageToBreakoutRooms = async function(token, message) { * @param {string} token the conversation token * @return {Promise>} The array of conversations */ -const getBreakoutRoomsParticipants = async function(token) { +const fetchBreakoutRoomsParticipants = async function(token) { return await axios.get(generateOcsUrl('/apps/spreed/api/v4/room/{token}/breakout-rooms/participants', { token, })) @@ -139,7 +139,7 @@ const requestAssistance = async function(token) { * @param {string} token the breakout room token * @return {Promise>} The array of conversations */ -const resetRequestAssistance = async function(token) { +const dismissRequestAssistance = async function(token) { return await axios.delete(generateOcsUrl('/apps/spreed/api/v1/breakout-rooms/{token}/request-assistance', { token, }) @@ -171,8 +171,8 @@ export { startBreakoutRooms, stopBreakoutRooms, broadcastMessageToBreakoutRooms, - getBreakoutRoomsParticipants, + fetchBreakoutRoomsParticipants, requestAssistance, - resetRequestAssistance, + dismissRequestAssistance, switchToBreakoutRoom, } diff --git a/src/store/breakoutRoomsStore.js b/src/store/breakoutRoomsStore.js deleted file mode 100644 index 32f959ee282..00000000000 --- a/src/store/breakoutRoomsStore.js +++ /dev/null @@ -1,263 +0,0 @@ -/** - * @copyright Copyright (c) 2022 Marco Ambrosini - * - * @author Marco Ambrosini - * - * @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 - * 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 . - * - */ -import Vue from 'vue' - -import { showError } from '@nextcloud/dialogs' -import { emit } from '@nextcloud/event-bus' - -import { - configureBreakoutRooms, - deleteBreakoutRooms, - getBreakoutRooms, - startBreakoutRooms, - stopBreakoutRooms, - broadcastMessageToBreakoutRooms, - getBreakoutRoomsParticipants, - requestAssistance, - resetRequestAssistance, - reorganizeAttendees, - switchToBreakoutRoom, -} from '../services/breakoutRoomsService.js' - -const state = { - breakoutRooms: {}, -} - -const getters = { - breakoutRooms: (state) => (token) => { - if (!state.breakoutRooms[token]) { - return [] - } - return state.breakoutRooms?.[token] - }, - - // Get the parent room token provided a breakoutroom token - parentRoomToken: (state) => (token) => { - for (const parentRoomToken in state.breakoutRooms) { - if (state.breakoutRooms[parentRoomToken].find(breakoutRoom => breakoutRoom.token === token)) { - return parentRoomToken - } - } - }, -} - -const mutations = { - /** - * Adds a breakout room to the store. - * - * @param {object} state current store state; - * @param {object} conversation the conversation; - * @param {string} conversation.parentRoomToken the parent room token; - * @param {object} conversation.breakoutRoom the breakout room; - */ - addBreakoutRoom(state, { parentRoomToken, breakoutRoom }) { - if (!state.breakoutRooms[parentRoomToken]) { - Vue.set(state.breakoutRooms, parentRoomToken, []) - } - // The breakout room to be added is first removed if it exists already. - state.breakoutRooms[parentRoomToken] = state.breakoutRooms[parentRoomToken].filter(current => current.token !== breakoutRoom.token) - Vue.set(state.breakoutRooms, parentRoomToken, [...state.breakoutRooms[parentRoomToken], breakoutRoom]) - }, - - // Deletes all breakout rooms for a given parent room token - deleteBreakoutRooms(state, parentRoomToken) { - Vue.delete(state.breakoutRooms, parentRoomToken) - }, -} - -/** - * The breakout rooms api return an array with mixed breakout rooms and "parent" conversations, we want to add the - * breakout rooms to this store and update the parent conversations in the conversations store. - * - * @param {Array} conversationsList the array of mixed breakout rooms and "parent" conversation - * @param {string }parentRoomToken the parent room token; - * @param {object} context the context object - */ -const processConversations = (conversationsList, parentRoomToken, context) => { - - conversationsList.forEach(conversation => { - if (conversation.token === parentRoomToken) { - context.commit('addConversation', conversation) - } else { - context.commit('addBreakoutRoom', { - parentRoomToken, - breakoutRoom: conversation, - }) - } - }) -} - -const actions = { - async configureBreakoutRoomsAction(context, { token, mode, amount, attendeeMap }) { - try { - const response = await configureBreakoutRooms(token, mode, amount, attendeeMap) - // Get the participants of the breakout rooms - context.dispatch('getBreakoutRoomsParticipantsAction', { token }) - - processConversations(response.data.ocs.data, token, context) - - // Open the sidebar and switch to the breakout rooms tab - emit('spreed:select-active-sidebar-tab', 'breakout-rooms') - context.dispatch('showSidebar') - - } catch (error) { - console.error(error) - showError(t('spreed', 'An error occurred while creating breakout rooms')) - } - }, - - async reorganizeAttendeesAction(context, { token, attendeeMap }) { - try { - const response = await reorganizeAttendees(token, attendeeMap) - // Get the participants of the breakout rooms - context.dispatch('getBreakoutRoomsParticipantsAction', { token }) - - processConversations(response.data.ocs.data, token, context) - - } catch (error) { - console.error(error) - showError(t('spreed', 'An error occurred while re-ordering the attendees')) - } - }, - - async deleteBreakoutRoomsAction(context, { token }) { - try { - const response = await deleteBreakoutRooms(token) - const conversation = response.data.ocs.data - - // Add the updated parent conversation to the conversations store - context.commit('addConversation', conversation) - - // Remove breakout rooms from this store - context.commit('deleteBreakoutRooms', token) - } catch (error) { - console.error(error) - showError(t('spreed', 'An error occurred while deleting breakout rooms')) - } - }, - - async getBreakoutRoomsAction(context, { token }) { - try { - const response = await getBreakoutRooms(token) - - processConversations(response.data.ocs.data, token, context) - - } catch (error) { - console.error(error) - } - }, - - async startBreakoutRoomsAction(context, token) { - try { - const response = await startBreakoutRooms(token) - - processConversations(response.data.ocs.data, token, context) - } catch (error) { - console.error(error) - showError(t('spreed', 'An error occurred while starting breakout rooms')) - } - }, - - async stopBreakoutRoomsAction(context, token) { - try { - const response = await stopBreakoutRooms(token) - - processConversations(response.data.ocs.data, token, context) - } catch (error) { - console.error(error) - showError(t('spreed', 'An error occurred while stopping breakout rooms')) - } - }, - - async broadcastMessageToBreakoutRoomsAction(context, { token, message }) { - try { - await broadcastMessageToBreakoutRooms(token, message) - } catch (error) { - console.error(error) - showError(t('spreed', 'An error occurred while sending a message to the breakout rooms')) - } - }, - - async getBreakoutRoomsParticipantsAction(context, { token }) { - try { - const response = await getBreakoutRoomsParticipants(token) - const splittedParticipants = response.data.ocs.data.reduce((acc, participant) => { - if (!acc[participant.roomToken]) { - acc[participant.roomToken] = [] - } - acc[participant.roomToken].push(participant) - return acc - }, {}) - - Object.entries(splittedParticipants).forEach(([token, newParticipants]) => { - context.dispatch('patchParticipants', { token, newParticipants, hasUserStatuses: false }) - }) - } catch (error) { - console.error(error) - } - }, - - async requestAssistanceAction(context, { token }) { - try { - const response = await requestAssistance(token) - // Add the updated parent conversation to the conversations store - context.commit('addConversation', response.data.ocs.data) - context.commit('addBreakoutRoom', { - parentRoomToken: response.data.ocs.data.objectId, - breakoutRoom: response.data.ocs.data, - }) - } catch (error) { - console.error(error) - showError(t('spreed', 'An error occurred while requesting assistance')) - } - }, - - async resetRequestAssistanceAction(context, { token }) { - try { - const response = await resetRequestAssistance(token) - // Add the updated parent conversation to the conversations store - context.commit('addConversation', response.data.ocs.data) - context.commit('addBreakoutRoom', { - parentRoomToken: response.data.ocs.data.objectId, - breakoutRoom: response.data.ocs.data, - }) - } catch (error) { - console.error(error) - showError(t('spreed', 'An error occurred while resetting the request for assistance')) - } - }, - - async switchToBreakoutRoomAction(context, { token, target }) { - try { - const response = await switchToBreakoutRoom(token, target) - - // A single breakout room (the target one) is returned, so it needs - // to be wrapper in an array. - processConversations([response.data.ocs.data], token, context) - } catch (error) { - console.error(error) - showError(t('spreed', 'An error occurred while joining breakout room')) - } - }, -} - -export default { state, getters, mutations, actions } diff --git a/src/store/conversationsStore.js b/src/store/conversationsStore.js index b17014ad384..ffdb6a913e8 100644 --- a/src/store/conversationsStore.js +++ b/src/store/conversationsStore.js @@ -70,6 +70,7 @@ import { stopCallRecording, } from '../services/recordingService.js' import { talkBroadcastChannel } from '../services/talkBroadcastChannel.js' +import { useBreakoutRoomsStore } from '../stores/breakoutRooms.js' import { useChatExtrasStore } from '../stores/chatExtras.js' import { useReactionsStore } from '../stores/reactions.js' import { useTalkHashStore } from '../stores/talkHash.js' @@ -365,6 +366,7 @@ const actions = { */ patchConversations(context, { conversations, withRemoving = false, withCaching = false }) { let storeHasChanged = false + const breakoutRoomsStore = useBreakoutRoomsStore() const currentConversations = context.state.conversations const newConversations = Object.fromEntries( @@ -392,10 +394,7 @@ const actions = { } if (newConversation.objectType === CONVERSATION.OBJECT_TYPE.BREAKOUT_ROOM) { - context.commit('addBreakoutRoom', { - parentRoomToken: newConversation.objectId, - breakoutRoom: newConversation, - }) + breakoutRoomsStore.addBreakoutRoom(newConversation.objectId, newConversation) } } diff --git a/src/store/storeConfig.js b/src/store/storeConfig.js index d7c3b07b22d..9ed5d280aa9 100644 --- a/src/store/storeConfig.js +++ b/src/store/storeConfig.js @@ -22,7 +22,6 @@ import actorStore from './actorStore.js' import audioRecorderStore from './audioRecorderStore.js' -import breakoutRoomsStore from './breakoutRoomsStore.js' import callViewStore from './callViewStore.js' import conversationsStore from './conversationsStore.js' import fileUploadStore from './fileUploadStore.js' @@ -50,7 +49,6 @@ export default { uiModeStore, windowVisibilityStore, pollStore, - breakoutRoomsStore, }, mutations: {}, diff --git a/src/stores/breakoutRooms.js b/src/stores/breakoutRooms.js new file mode 100644 index 00000000000..5d0be8b10e4 --- /dev/null +++ b/src/stores/breakoutRooms.js @@ -0,0 +1,301 @@ +/** + * @copyright Copyright (c) 2024 Maksim Sukharev + * + * @author Marco Ambrosini + * @author Maksim Sukharev + * + * @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 + * 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 . + * + */ + +import { defineStore } from 'pinia' +import Vue from 'vue' + +import { showError } from '@nextcloud/dialogs' +import { emit } from '@nextcloud/event-bus' + +import { + configureBreakoutRooms, + deleteBreakoutRooms, + getBreakoutRooms, + startBreakoutRooms, + stopBreakoutRooms, + broadcastMessageToBreakoutRooms, + fetchBreakoutRoomsParticipants, + requestAssistance, + dismissRequestAssistance, + reorganizeAttendees, + switchToBreakoutRoom, +} from '../services/breakoutRoomsService.js' +import store from '../store/index.js' + +export const useBreakoutRoomsStore = defineStore('breakoutRooms', { + state: () => ({ + rooms: {}, + }), + + getters: { + breakoutRooms: (state) => (token) => { + return Object.values(Object(state.rooms[token])) + }, + + getParentRoomToken: (state) => (token) => { + for (const parentRoomToken in state.rooms) { + if (state.rooms[parentRoomToken]?.[token] !== undefined) { + return parentRoomToken + } + } + }, + }, + + actions: { + /** + * The breakout rooms API return an array with mixed breakout and parent rooms, we want to update + * breakout rooms in this store and all conversations in conversationsStore. + * + * @param {string} token the parent room token; + * @param {Array|object} conversationOrArray a single conversation or an array of conversations. + * + */ + processConversations(token, conversationOrArray) { + const conversations = Array.isArray(conversationOrArray) ? conversationOrArray : [conversationOrArray] + store.dispatch('patchConversations', { conversations }) + }, + + /** + * Purges breakout rooms from both stores. + * + * @param {string} token the parent room token; + */ + purgeBreakoutRoomsStore(token) { + for (const roomToken in this.rooms[token]) { + store.dispatch('deleteConversation', roomToken) + } + Vue.delete(this.rooms, token) + }, + + /** + * Adds a breakout room to the store. + * + * @param {string} token the parent room token; + * @param {object} breakoutRoom the breakout room. + */ + addBreakoutRoom(token, breakoutRoom) { + if (!this.rooms[token]) { + Vue.set(this.rooms, token, {}) + } + Vue.set(this.rooms[token], breakoutRoom.token, breakoutRoom) + }, + + /** + * Creates breakout rooms for specified conversation. + * + * @param {object} payload the action payload; + * @param {string} payload.token the parent room token; + * @param {string} payload.mode the mode of the breakout rooms; + * @param {number} payload.amount the amount of the breakout rooms to create; + * @param {string} payload.attendeeMap the stringified JSON object with attendee map. + */ + async configureBreakoutRooms({ token, mode, amount, attendeeMap }) { + try { + const response = await configureBreakoutRooms(token, mode, amount, attendeeMap) + this.processConversations(token, response.data.ocs.data) + + // Get the participants of the breakout rooms + await this.fetchBreakoutRoomsParticipants(token) + + // Open the sidebar and switch to the breakout rooms tab + emit('spreed:select-active-sidebar-tab', 'breakout-rooms') + store.dispatch('showSidebar') + } catch (error) { + console.error(error) + showError(t('spreed', 'An error occurred while creating breakout rooms')) + } + }, + + /** + * Reassign participants to another breakout rooms. + * + * @param {object} payload the action payload; + * @param {string} payload.token the parent room token; + * @param {string} payload.attendeeMap the stringified JSON object with attendee map. + */ + async reorganizeAttendees({ token, attendeeMap }) { + try { + const response = await reorganizeAttendees(token, attendeeMap) + this.processConversations(token, response.data.ocs.data) + + // Get the participants of the breakout rooms + await this.fetchBreakoutRoomsParticipants(token) + + } catch (error) { + console.error(error) + showError(t('spreed', 'An error occurred while re-ordering the attendees')) + } + }, + + /** + * Deletes configured breakout rooms for a given parent room token. + * + * @param {string} token the parent room token. + */ + async deleteBreakoutRooms(token) { + try { + const response = await deleteBreakoutRooms(token) + // Update returned parent conversation + this.processConversations(token, response.data.ocs.data) + // Remove breakout rooms from this store + this.purgeBreakoutRoomsStore(token) + } catch (error) { + console.error(error) + showError(t('spreed', 'An error occurred while deleting breakout rooms')) + } + }, + + /** + * Get configured breakout rooms for a given parent room token. + * + * @param {string} token the parent room token. + */ + async getBreakoutRooms(token) { + try { + const response = await getBreakoutRooms(token) + this.processConversations(token, response.data.ocs.data) + } catch (error) { + console.error(error) + } + }, + + /** + * Start a breakout rooms session for a given parent room token. + * + * @param {string} token the parent room token. + */ + async startBreakoutRooms(token) { + try { + const response = await startBreakoutRooms(token) + this.processConversations(token, response.data.ocs.data) + } catch (error) { + console.error(error) + showError(t('spreed', 'An error occurred while starting breakout rooms')) + } + }, + + /** + * Stop a breakout rooms session for a given parent room token. + * + * @param {string} token the parent room token. + */ + async stopBreakoutRooms(token) { + try { + const response = await stopBreakoutRooms(token) + this.processConversations(token, response.data.ocs.data) + } catch (error) { + console.error(error) + showError(t('spreed', 'An error occurred while stopping breakout rooms')) + } + }, + + /** + * Send a message to all breakout rooms for a given parent room token. + * + * @param {object} payload the action payload; + * @param {string} payload.token the parent room token; + * @param {string} payload.message the message text. + */ + async broadcastMessageToBreakoutRooms({ token, message }) { + try { + await broadcastMessageToBreakoutRooms(token, message) + } catch (error) { + console.error(error) + showError(t('spreed', 'An error occurred while sending a message to the breakout rooms')) + } + }, + + /** + * Update participants in breakout rooms for a given token. + * + * @param {string} token the parent room token. + */ + async fetchBreakoutRoomsParticipants(token) { + try { + const response = await fetchBreakoutRoomsParticipants(token) + const splittedParticipants = response.data.ocs.data.reduce((acc, participant) => { + if (!acc[participant.roomToken]) { + acc[participant.roomToken] = [] + } + acc[participant.roomToken].push(participant) + return acc + }, {}) + + Object.entries(splittedParticipants).forEach(([token, newParticipants]) => { + store.dispatch('patchParticipants', { token, newParticipants, hasUserStatuses: false }) + }) + } catch (error) { + console.error(error) + } + }, + + /** + * Notify moderators when raise a hand in a breakout room with given token. + * + * @param {string} token the breakout room token. + */ + async requestAssistance(token) { + try { + const response = await requestAssistance(token) + const parentToken = response.data.ocs.data.objectId + this.processConversations(parentToken, response.data.ocs.data) + } catch (error) { + console.error(error) + showError(t('spreed', 'An error occurred while requesting assistance')) + } + }, + + /** + * Dismiss a notification about raised hand for a breakout room with given token. + * + * @param {string} token the breakout room token. + */ + async dismissRequestAssistance(token) { + try { + const response = await dismissRequestAssistance(token) + const parentToken = response.data.ocs.data.objectId + this.processConversations(parentToken, response.data.ocs.data) + } catch (error) { + console.error(error) + showError(t('spreed', 'An error occurred while resetting the request for assistance')) + } + }, + + /** + * Switch between breakout rooms if participant is allowed to choose the room freely + * + * @param {object} payload the action payload; + * @param {string} payload.token the parent room token; + * @param {string} payload.target the breakout room token. + */ + async switchToBreakoutRoom({ token, target }) { + try { + const response = await switchToBreakoutRoom(token, target) + this.processConversations(token, response.data.ocs.data) + } catch (error) { + console.error(error) + showError(t('spreed', 'An error occurred while joining breakout room')) + } + }, + } +})