Browse Source
Merge pull request #6382 from nextcloud/feature/6362/add-ability-to-set-conversation-permissions
Merge pull request #6382 from nextcloud/feature/6362/add-ability-to-set-conversation-permissions
Add ability to set default permissions for a conversationpull/6450/head
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 638 additions and 180 deletions
-
266src/components/ConversationSettings/ConversationPermissionsSettings.vue
-
9src/components/ConversationSettings/ConversationSettingsDialog.vue
-
236src/components/PermissionsEditor/PermissionsEditor.vue
-
16src/components/RightSidebar/Participants/ParticipantsList/Participant/Participant.vue
-
48src/components/RightSidebar/Participants/ParticipantsList/Participant/ParticipantPermissionsEditor/ParticipantPermissionsEditor.spec.js
-
163src/components/RightSidebar/Participants/ParticipantsList/Participant/ParticipantPermissionsEditor/ParticipantPermissionsEditor.vue
-
33src/services/conversationsService.js
-
20src/store/conversationsStore.js
-
27src/store/conversationsStore.spec.js
@ -0,0 +1,266 @@ |
|||
<!-- |
|||
- @copyright Copyright (c) 2021 Marco Ambrosini <marcoambrosini@pm.me> |
|||
- |
|||
- @author Marco Ambrosini <marcoambrosini@pm.me> |
|||
- |
|||
- @license GNU AGPL version 3 or any later version |
|||
- |
|||
- 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 <http://www.gnu.org/licenses/>. |
|||
--> |
|||
|
|||
<template> |
|||
<div class="conversation-permissions-editor"> |
|||
<h4 class="conversation-permissions-editor__title"> |
|||
{{ t('spreed', 'Edit the default permissions for participants in this conversation. These settings do not affect moderators.') }} |
|||
</h4> |
|||
<p>{{ t('spreed', 'Warning: every time permissions are modified in this section, custom permissions previously assigned to individual participants will be lost.') }}</p> |
|||
|
|||
<!-- All permissions --> |
|||
<div class="conversation-permissions-editor__setting"> |
|||
<CheckboxRadioSwitch :checked.sync="radioValue" |
|||
:disabled="loading" |
|||
value="all" |
|||
name="permission_radio" |
|||
type="radio" |
|||
@update:checked="handleSubmitPermissions"> |
|||
{{ t('spreed', 'All permissions') }} |
|||
</CheckboxRadioSwitch> |
|||
<span v-show="loading && radioValue === 'all'" class="icon-loading-small" /> |
|||
</div> |
|||
<p>{{ t('spreed', 'Participants have permissions to start a call, join a call, enable audio, video and screenshare.') }}</p> |
|||
|
|||
<!-- No permissions --> |
|||
<div class="conversation-permissions-editor__setting"> |
|||
<CheckboxRadioSwitch :checked.sync="radioValue" |
|||
value="restricted" |
|||
:disabled="loading" |
|||
name="permission_radio" |
|||
type="radio" |
|||
@update:checked="handleSubmitPermissions"> |
|||
{{ t('spreed', 'Restricted') }} |
|||
</CheckboxRadioSwitch> |
|||
<span v-show="loading && radioValue === 'restricted'" class="icon-loading-small" /> |
|||
</div> |
|||
<p>{{ t('spreed', 'Participants can only join calls, but not enable audio, video or screenshare until a moderator manually grants their permissions manually.') }}</p> |
|||
|
|||
<!-- Advanced permissions --> |
|||
<div class="conversation-permissions-editor__setting--advanced"> |
|||
<CheckboxRadioSwitch :checked.sync="radioValue" |
|||
value="advanced" |
|||
:disabled="loading" |
|||
name="permission_radio" |
|||
type="radio" |
|||
@update:checked="showPermissionsEditor = true"> |
|||
{{ t('spreed', 'Advanced permissions') }} |
|||
</CheckboxRadioSwitch> |
|||
|
|||
<!-- Edit advanced permissions --> |
|||
<button |
|||
v-show="showEditButton" |
|||
:aria-label="t('spreed', 'Edit permissions')" |
|||
class="nc-button nc-button__main" |
|||
@click="showPermissionsEditor = true"> |
|||
<Pencil |
|||
:size="20" |
|||
decorative |
|||
title="" /> |
|||
</button> |
|||
</div> |
|||
<PermissionEditor |
|||
v-if="showPermissionsEditor" |
|||
:conversation-name="conversationName" |
|||
:permissions="conversationPermissions" |
|||
:loading="loading" |
|||
@close="handleClosePermissionsEditor" |
|||
@submit="handleSubmitPermissions" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import PermissionEditor from '../PermissionsEditor/PermissionsEditor.vue' |
|||
import CheckboxRadioSwitch from '@nextcloud/vue/dist/Components/CheckboxRadioSwitch' |
|||
import Pencil from 'vue-material-design-icons/Pencil.vue' |
|||
import { PARTICIPANT } from '../../constants' |
|||
import { showError, showSuccess } from '@nextcloud/dialogs' |
|||
|
|||
const PERMISSIONS = PARTICIPANT.PERMISSIONS |
|||
|
|||
export default { |
|||
name: 'ConversationPermissionsSettings', |
|||
|
|||
components: { |
|||
PermissionEditor, |
|||
CheckboxRadioSwitch, |
|||
Pencil, |
|||
}, |
|||
|
|||
props: { |
|||
token: { |
|||
type: String, |
|||
default: null, |
|||
}, |
|||
}, |
|||
|
|||
data() { |
|||
return { |
|||
showPermissionsEditor: false, |
|||
isEditingPermissions: false, |
|||
loading: false, |
|||
radioValue: '', |
|||
} |
|||
}, |
|||
|
|||
computed: { |
|||
/** |
|||
* The participant's name. |
|||
*/ |
|||
conversationName() { |
|||
return this.$store.getters.conversation(this.token).name |
|||
}, |
|||
|
|||
/** |
|||
* The current conversation permissions. |
|||
*/ |
|||
conversationPermissions() { |
|||
return this.$store.getters.conversation(this.token).defaultPermissions |
|||
}, |
|||
|
|||
/** |
|||
* Hides and shows the edit button for advanced permissions. |
|||
*/ |
|||
showEditButton() { |
|||
return this.radioValue === 'advanced' && !this.showPermissionsEditor |
|||
}, |
|||
}, |
|||
|
|||
mounted() { |
|||
/** |
|||
* Set the initial radio value. |
|||
*/ |
|||
this.setCurrentRadioValue() |
|||
}, |
|||
|
|||
methods: { |
|||
/** |
|||
* Binary sum all the permissions and make the request to change them. |
|||
* |
|||
* @param {string | number} value - The permissions value, which is a |
|||
* string (e.g. 'restricted' or 'all') unless this method is called by |
|||
* the click event emitted by the `permissionsEditor` component, in |
|||
* which case it's a number indicating the permissions value. |
|||
*/ |
|||
async handleSubmitPermissions(value) { |
|||
let permissions |
|||
|
|||
// Compute the permissions value |
|||
switch (value) { |
|||
case 'all': |
|||
permissions = PERMISSIONS.MAX_DEFAULT |
|||
break |
|||
case 'restricted': |
|||
permissions = PERMISSIONS.CALL_JOIN |
|||
break |
|||
default: |
|||
permissions = value |
|||
} |
|||
|
|||
this.loading = true |
|||
|
|||
// Make the store call |
|||
try { |
|||
await this.$store.dispatch('setConversationPermissions', { |
|||
token: this.token, |
|||
permissions, |
|||
}) |
|||
showSuccess(t('spreed', 'Default permissions modified for {conversationName}', { conversationName: this.conversationName })) |
|||
|
|||
// Modify the radio buttons value |
|||
this.radioValue = this.getPermissionRadioValue(permissions) |
|||
this.showPermissionsEditor = false |
|||
} catch (error) { |
|||
console.debug(error) |
|||
showError(t('spreed', 'Could not modify default permissions for {conversationName}', { conversationName: this.conversationName })) |
|||
|
|||
// Go back to the previous radio value |
|||
this.radioValue = this.getPermissionRadioValue(this.conversationPermissions) |
|||
} finally { |
|||
this.loading = false |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* Get the radio button string value given a permission number. |
|||
* |
|||
* @param {number} value - The permissions value. |
|||
*/ |
|||
getPermissionRadioValue(value) { |
|||
switch (value) { |
|||
case PERMISSIONS.MAX_DEFAULT: |
|||
return 'all' |
|||
case PERMISSIONS.CALL_JOIN: |
|||
return 'restricted' |
|||
|
|||
default: |
|||
return 'advanced' |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* Set the radio value that corresponds to the current default |
|||
* permissions in the store. |
|||
*/ |
|||
setCurrentRadioValue() { |
|||
this.radioValue = this.getPermissionRadioValue(this.conversationPermissions) |
|||
}, |
|||
|
|||
/** |
|||
* Hides the modal and resets conversation permissions to the previous |
|||
* value. |
|||
*/ |
|||
handleClosePermissionsEditor() { |
|||
this.showPermissionsEditor = false |
|||
this.setCurrentRadioValue() |
|||
}, |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
@import '../../assets/buttons.scss'; |
|||
|
|||
::v-deep .mx-input { |
|||
margin: 0; |
|||
} |
|||
|
|||
.conversation-permissions-editor { |
|||
&__setting { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
&--advanced { |
|||
display: flex; |
|||
justify-content: flex-start; |
|||
|
|||
// Edit button |
|||
button { |
|||
margin-left: 16px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
p { |
|||
color: var(--color-text-lighter); |
|||
margin-bottom: 16px; |
|||
} |
|||
</style> |
@ -0,0 +1,236 @@ |
|||
<!-- |
|||
- @copyright Copyright (c) 2021 Marco Ambrosini <marcoambrosini@pm.me> |
|||
- |
|||
- @author Marco Ambrosini <marcoambrosini@pm.me> |
|||
- |
|||
- @license GNU AGPL version 3 or any later version |
|||
- |
|||
- 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 <http://www.gnu.org/licenses/>. |
|||
--> |
|||
|
|||
<template> |
|||
<Modal v-on="$listeners"> |
|||
<div class="wrapper"> |
|||
<template v-if="!loading"> |
|||
<!-- eslint-disable-next-line vue/no-v-html --> |
|||
<p class="title" v-html="modalTitle" /> |
|||
<form @submit.prevent="handleSubmitPermissions"> |
|||
<CheckboxRadioSwitch |
|||
ref="callStart" |
|||
:checked.sync="callStart" |
|||
class="checkbox"> |
|||
{{ t('spreed', 'Start a call') }} |
|||
</CheckboxRadioSwitch> |
|||
<CheckboxRadioSwitch |
|||
ref="lobbyIgnore" |
|||
:checked.sync="lobbyIgnore" |
|||
class="checkbox"> |
|||
{{ t('spreed', 'Skip the lobby') }} |
|||
</CheckboxRadioSwitch> |
|||
<CheckboxRadioSwitch |
|||
ref="publishAudio" |
|||
:checked.sync="publishAudio" |
|||
class="checkbox"> |
|||
{{ t('spreed', 'Enable the microphone') }} |
|||
</CheckboxRadioSwitch> |
|||
<CheckboxRadioSwitch |
|||
ref="publishVideo" |
|||
:checked.sync="publishVideo" |
|||
class="checkbox"> |
|||
{{ t('spreed', 'Enable the camera') }} |
|||
</CheckboxRadioSwitch> |
|||
<CheckboxRadioSwitch |
|||
ref="publishScreen" |
|||
:checked.sync="publishScreen" |
|||
class="checkbox"> |
|||
{{ t('spreed', 'Share the screen') }} |
|||
</CheckboxRadioSwitch> |
|||
<button ref="submit" |
|||
type="submit" |
|||
:disabled="submitButtonDisabled" |
|||
class="nc-button primary"> |
|||
{{ t('spreed', 'Update permissions') }} |
|||
</button> |
|||
</form> |
|||
</template> |
|||
<div v-if="loading" class="loading-screen"> |
|||
<span class="icon-loading" /> |
|||
<p>{{ t('spreed', 'Updating permissions') }}</p> |
|||
</div> |
|||
</div> |
|||
</Modal> |
|||
</template> |
|||
|
|||
<script> |
|||
import CheckboxRadioSwitch from '@nextcloud/vue/dist/Components/CheckboxRadioSwitch' |
|||
import { PARTICIPANT } from '../../constants' |
|||
import Modal from '@nextcloud/vue/dist/Components/Modal' |
|||
|
|||
const PERMISSIONS = PARTICIPANT.PERMISSIONS |
|||
|
|||
export default { |
|||
name: 'PermissionsEditor', |
|||
|
|||
components: { |
|||
CheckboxRadioSwitch, |
|||
Modal, |
|||
}, |
|||
|
|||
props: { |
|||
permissions: { |
|||
type: Number, |
|||
default: null, |
|||
}, |
|||
|
|||
/** |
|||
* The user's displayname. Don't provide this only when modifying the |
|||
* default conversation's permissions. |
|||
*/ |
|||
displayName: { |
|||
type: String, |
|||
default: '', |
|||
}, |
|||
|
|||
/** |
|||
* The conversation's name. Don't provide this when modifying |
|||
* participants' permissions. |
|||
*/ |
|||
conversationName: { |
|||
type: String, |
|||
default: '', |
|||
}, |
|||
|
|||
/** |
|||
* Displays the loading state of this component |
|||
*/ |
|||
loading: { |
|||
type: Boolean, |
|||
default: false, |
|||
}, |
|||
}, |
|||
|
|||
data() { |
|||
return { |
|||
// Permission to start a call |
|||
callStart: false, |
|||
// Permission to bypass the lobby |
|||
lobbyIgnore: false, |
|||
// Permission to enable the microphone |
|||
publishAudio: false, |
|||
// Permission to enable the camera |
|||
publishVideo: false, |
|||
// Permission to start a screenshare |
|||
publishScreen: false, |
|||
} |
|||
}, |
|||
|
|||
computed: { |
|||
modalTitle() { |
|||
if (this.displayName) { |
|||
return t('spreed', 'In this conversation <strong>{user}</strong> can:', { |
|||
user: this.displayName, |
|||
}) |
|||
} else if (this.conversationName) { |
|||
return t('spreed', 'Edit default permissions for participants in <strong>{conversationName}</strong>', { |
|||
conversationName: this.conversationName, |
|||
}) |
|||
} else throw Error('you need to fill either the conversationName or the displayName props') |
|||
}, |
|||
/** |
|||
* The number of the edited permissions during the editing of the form. |
|||
* We use this to compare it with the actual permissions of the |
|||
* participant in the store and enable or disable the submit button |
|||
* accordingly. |
|||
*/ |
|||
formPermissions() { |
|||
return (this.callStart ? PERMISSIONS.CALL_START : 0) |
|||
| (this.lobbyIgnore ? PERMISSIONS.LOBBY_IGNORE : 0) |
|||
| (this.publishAudio ? PERMISSIONS.PUBLISH_AUDIO : 0) |
|||
| (this.publishVideo ? PERMISSIONS.PUBLISH_VIDEO : 0) |
|||
| (this.publishScreen ? PERMISSIONS.PUBLISH_SCREEN : 0) |
|||
| PERMISSIONS.CUSTOM |
|||
}, |
|||
|
|||
/** |
|||
* If the permissions are not changed, the submit button will stay |
|||
* disabled. |
|||
*/ |
|||
submitButtonDisabled() { |
|||
return this.permissions === this.formPermissions |
|||
}, |
|||
}, |
|||
|
|||
mounted() { |
|||
this.writePermissionsToComponent(this.permissions) |
|||
}, |
|||
|
|||
methods: { |
|||
/** |
|||
* Takes the permissions from the store and writes them in the data of |
|||
* this component. |
|||
* |
|||
* @param {number} permissions - the permissions number. |
|||
*/ |
|||
writePermissionsToComponent(permissions) { |
|||
permissions & PERMISSIONS.CALL_START ? this.callStart = true : this.callStart = false |
|||
permissions & PERMISSIONS.LOBBY_IGNORE ? this.lobbyIgnore = true : this.lobbyIgnore = false |
|||
permissions & PERMISSIONS.PUBLISH_AUDIO ? this.publishAudio = true : this.publishAudio = false |
|||
permissions & PERMISSIONS.PUBLISH_VIDEO ? this.publishVideo = true : this.publishVideo = false |
|||
permissions & PERMISSIONS.PUBLISH_SCREEN ? this.publishScreen = true : this.publishScreen = false |
|||
}, |
|||
|
|||
handleSubmitPermissions() { |
|||
this.$emit('submit', this.formPermissions) |
|||
}, |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
@import '../../assets/buttons.scss'; |
|||
|
|||
$editor-width: 350px; |
|||
|
|||
.nc-button { |
|||
width: 100%; |
|||
margin-top: 12px; |
|||
} |
|||
|
|||
.wrapper { |
|||
width: $editor-width; |
|||
padding: 0 24px 24px 24px; |
|||
} |
|||
|
|||
.title { |
|||
font-size: 16px; |
|||
margin-bottom: 12px; |
|||
padding-top: 24px; |
|||
} |
|||
|
|||
.loading-screen { |
|||
width: $editor-width; |
|||
height: 200px; |
|||
text-align: center; |
|||
font-weight: bold; |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
|
|||
// Loader |
|||
span { |
|||
margin-bottom: 16px; |
|||
} |
|||
} |
|||
|
|||
</style> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue