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(recording) - add frontend support for recording consent #10633

Merged
merged 7 commits into from
Oct 20, 2023
46 changes: 43 additions & 3 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
</NcAppContent>
<RightSidebar :is-in-call="isInCall" />
<PreventUnload :when="warnLeaving || isSendingMessages" />
<MediaSettings :initialize-on-mounted="false" />
<MediaSettings :initialize-on-mounted="false" :recording-consent-given.sync="recordingConsentGiven" />
<SettingsDialog />
<ConversationSettingsDialog />
</NcContent>
Expand Down Expand Up @@ -104,6 +104,7 @@ export default {
defaultPageTitle: false,
loading: false,
isRefreshingCurrentConversation: false,
recordingConsentGiven: false,
}
},

Expand Down Expand Up @@ -220,13 +221,18 @@ export default {
this.setPageTitle(this.getConversationName(this.token), this.atLeastOneLastMessageIdChanged)
},

token() {
// Collapse the sidebar if it's a 1to1 conversation
token(newValue, oldValue) {
// Collapse the sidebar if it's a one to one conversation
if (this.isOneToOne || BrowserStorage.getItem('sidebarOpen') === 'false' || this.isMobile) {
this.$store.dispatch('hideSidebar')
} else if (BrowserStorage.getItem('sidebarOpen') === 'true') {
this.$store.dispatch('showSidebar')
}

// Reset recording consent if switch doesn't happen within breakout rooms or main room
if (!this.isBreakoutRoomsNavigation(oldValue, newValue)) {
this.recordingConsentGiven = false
}
},
},

Expand Down Expand Up @@ -348,6 +354,8 @@ export default {
token: params.token,
participantIdentifier: this.$store.getters.getParticipantIdentifier(),
flags,
silent: true,
recordingConsent: this.recordingConsentGiven,
})

this.$store.dispatch('setForceCallView', false)
Expand Down Expand Up @@ -676,6 +684,38 @@ export default {
})
document.querySelector('.conversations-search input').focus()
},

/**
* Check if conversation was switched within breakout rooms and parent room.
*
* @param {string} oldToken The old conversation's token
* @param {string} newToken The new conversation's token
* @return {boolean}
*/
isBreakoutRoomsNavigation(oldToken, newToken) {
const oldConversation = this.$store.getters.conversation(oldToken)
const newConversation = this.$store.getters.conversation(newToken)

// One of rooms is undefined
if (!oldConversation || !newConversation) {
return false
}

// Parent to breakout
if (oldConversation.breakoutRoomMode !== CONVERSATION.BREAKOUT_ROOM_MODE.NOT_CONFIGURED
&& newConversation.objectType === 'room') {
return true
}

// Breakout to parent
if (oldConversation.objectType === 'room'
&& newConversation.breakoutRoomMode !== CONVERSATION.BREAKOUT_ROOM_MODE.NOT_CONFIGURED) {
return true
}

// Breakout to breakout
return oldConversation.objectType === 'room' && newConversation.objectType === 'room'
}
},
}
</script>
Expand Down
18 changes: 8 additions & 10 deletions src/PublicShareAuthSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,20 @@
:is-sidebar="true" />
<CallView :token="token" :is-sidebar="true" />
<ChatView />
<MediaSettings :initialize-on-mounted="false" :recording-consent-given.sync="recordingConsentGiven" />
</template>
</aside>
</TransitionWrapper>
</template>

<script>
import { getCurrentUser } from '@nextcloud/auth'
import { emit } from '@nextcloud/event-bus'
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 TopBar from './components/TopBar/TopBar.vue'
import TransitionWrapper from './components/TransitionWrapper.vue'

Expand All @@ -61,6 +64,7 @@ export default {
components: {
CallView,
ChatView,
MediaSettings,
TopBar,
TransitionWrapper,
},
Expand All @@ -74,6 +78,7 @@ export default {
return {
fetchCurrentConversationIntervalId: null,
isWaitingToClose: false,
recordingConsentGiven: false
}
},

Expand Down Expand Up @@ -136,8 +141,9 @@ export default {

// Joining the call needs to be done once the participant identifier
// has been set, which is done once the conversation has been
// fetched.
this.joinCall()
// fetched. MediaSettings are called to set up audio and video devices
// and also to give a consent to recording, if set up
emit('talk:media-settings:show')

// FIXME The participant will not be updated with the server data
// when the conversation is got again (as "addParticipantOnce" is
Expand All @@ -156,14 +162,6 @@ export default {
}
},

async joinCall() {
await this.$store.dispatch('joinCall', {
token: this.token,
participantIdentifier: this.$store.getters.getParticipantIdentifier(),
silent: false,
})
},

async fetchCurrentConversation() {
if (!this.token) {
return
Expand Down
3 changes: 2 additions & 1 deletion src/PublicShareSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<PreventUnload :when="warnLeaving" />
<CallButton class="call-button" />
<ChatView />
<MediaSettings :initialize-on-mounted="false" />
<MediaSettings :initialize-on-mounted="false" :recording-consent-given.sync="recordingConsentGiven" />
</template>
</aside>
</TransitionWrapper>
Expand Down Expand Up @@ -125,6 +125,7 @@ export default {
return {
fetchCurrentConversationIntervalId: null,
joiningConversation: false,
recordingConsentGiven: false,
}
},

Expand Down
70 changes: 70 additions & 0 deletions src/components/AdminSettings/RecordingServers.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,26 @@
:label="t('spreed', 'Shared secret')"
label-visible
@update:value="updateSecret" />

<template v-if="servers.length && recordingConsentCapability">
<h3>{{ t('spreed', 'Recording consent') }}</h3>

<template v-for="level in recordingConsentOptions">
<NcCheckboxRadioSwitch :key="level.value + '_radio'"
:value="level.value.toString()"
:checked.sync="recordingConsentSelected"
name="recording-consent"
type="radio"
:disabled="loading"
@update:checked="setRecordingConsent">
{{ level.label }}
</NcCheckboxRadioSwitch>

<p :key="level.value + '_description'" class="consent-description">
{{ getRecordingConsentDescription(level.value) }}
</p>
</template>
</template>
</section>
</template>

Expand All @@ -73,36 +93,55 @@ import debounce from 'debounce'

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

import { getCapabilities } from '@nextcloud/capabilities'
import { showSuccess } from '@nextcloud/dialogs'
import { formatFileSize } from '@nextcloud/files'
import { loadState } from '@nextcloud/initial-state'

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

import RecordingServer from '../../components/AdminSettings/RecordingServer.vue'
import { CALL } from '../../constants.js'
import TransitionWrapper from '../TransitionWrapper.vue'

const recordingConsentCapability = getCapabilities()?.spreed?.features?.includes('recording-consent')
const recordingConsentOptions = [
{ value: CALL.RECORDING_CONSENT.OFF, label: t('spreed', 'Disabled for all calls') },
{ value: CALL.RECORDING_CONSENT.REQUIRED, label: t('spreed', 'Enabled for all calls') },
{ value: CALL.RECORDING_CONSENT.OPTIONAL, label: t('spreed', 'Configurable on conversation level by moderators') },
]

export default {
name: 'RecordingServers',

components: {
NcButton,
NcCheckboxRadioSwitch,
NcNoteCard,
NcTextField,
Plus,
RecordingServer,
TransitionWrapper,
},

setup() {
return {
recordingConsentCapability,
recordingConsentOptions,
}
},

data() {
return {
servers: [],
secret: '',
uploadLimit: 0,
loading: false,
saved: false,
recordingConsentSelected: loadState('spreed', 'recording_consent').toString(),
}
},

Expand Down Expand Up @@ -164,6 +203,27 @@ export default {
})
},

setRecordingConsent(value) {
this.loading = true
OCP.AppConfig.setValue('spreed', 'recording_consent', value, {
success: function() {
this.loading = false
}.bind(this),
})
},

getRecordingConsentDescription(value) {
switch (value) {
case CALL.RECORDING_CONSENT.OPTIONAL:
return t('spreed', 'Moderators will be allowed to enable consent on conversation level. The consent to be recorded will be required for each participant before joining every call in this conversation.')
case CALL.RECORDING_CONSENT.REQUIRED:
return t('spreed', 'The consent to be recorded will be required for each participant before joining every call.')
case CALL.RECORDING_CONSENT.OFF:
default:
return t('spreed', 'The consent to be recorded is not required.')
}
},

toggleSave() {
this.saved = true
setTimeout(() => {
Expand All @@ -182,4 +242,14 @@ export default {
.additional-top-margin {
margin-top: 10px;
}

h3 {
margin-top: 24px;
font-weight: 600;
}

.consent-description {
margin-bottom: 12px;
opacity: 0.7;
}
</style>
Loading
Loading