You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nextcloud/apps/files_sharing/src/components/SharePermissionsEditor.vue

295 lines
8.6 KiB
Vue

<!--
- @copyright Copyright (c) 2022 Louis Chmn <louis@chmn.me>
-
- @author Louis Chmn <louis@chmn.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>
<li>
<ul>
<!-- file -->
<NcActionCheckbox v-if="!isFolder"
:checked="shareHasPermissions(atomicPermissions.UPDATE)"
:disabled="saving"
@update:checked="toggleSharePermissions(atomicPermissions.UPDATE)">
{{ t('files_sharing', 'Allow editing') }}
</NcActionCheckbox>
<!-- folder -->
<template v-if="isFolder && fileHasCreatePermission && config.isPublicUploadEnabled">
<template v-if="!showCustomPermissionsForm">
<NcActionRadio :checked="sharePermissionEqual(bundledPermissions.READ_ONLY)"
:value="bundledPermissions.READ_ONLY"
:name="randomFormName"
:disabled="saving"
@change="setSharePermissions(bundledPermissions.READ_ONLY)">
{{ t('files_sharing', 'Read only') }}
</NcActionRadio>
<NcActionRadio :checked="sharePermissionEqual(bundledPermissions.UPLOAD_AND_UPDATE)"
:value="bundledPermissions.UPLOAD_AND_UPDATE"
:disabled="saving"
:name="randomFormName"
@change="setSharePermissions(bundledPermissions.UPLOAD_AND_UPDATE)">
{{ t('files_sharing', 'Allow upload and editing') }}
</NcActionRadio>
<NcActionRadio :checked="sharePermissionEqual(bundledPermissions.FILE_DROP)"
:value="bundledPermissions.FILE_DROP"
:disabled="saving"
:name="randomFormName"
class="sharing-entry__action--public-upload"
@change="setSharePermissions(bundledPermissions.FILE_DROP)">
{{ t('files_sharing', 'File drop (upload only)') }}
</NcActionRadio>
<!-- custom permissions button -->
<NcActionButton :title="t('files_sharing', 'Custom permissions')"
@click="showCustomPermissionsForm = true">
<template #icon>
<Tune />
</template>
{{ sharePermissionsIsBundle ? "" : sharePermissionsSummary }}
</NcActionButton>
</template>
<!-- custom permissions -->
<span v-else :class="{error: !sharePermissionsSetIsValid}">
<NcActionCheckbox :checked="shareHasPermissions(atomicPermissions.READ)"
:disabled="saving || !canToggleSharePermissions(atomicPermissions.READ)"
@update:checked="toggleSharePermissions(atomicPermissions.READ)">
{{ t('files_sharing', 'Read') }}
</NcActionCheckbox>
<NcActionCheckbox :checked="shareHasPermissions(atomicPermissions.CREATE)"
:disabled="saving || !canToggleSharePermissions(atomicPermissions.CREATE)"
@update:checked="toggleSharePermissions(atomicPermissions.CREATE)">
{{ t('files_sharing', 'Upload') }}
</NcActionCheckbox>
<NcActionCheckbox :checked="shareHasPermissions(atomicPermissions.UPDATE)"
:disabled="saving || !canToggleSharePermissions(atomicPermissions.UPDATE)"
@update:checked="toggleSharePermissions(atomicPermissions.UPDATE)">
{{ t('files_sharing', 'Edit') }}
</NcActionCheckbox>
<NcActionCheckbox :checked="shareHasPermissions(atomicPermissions.DELETE)"
:disabled="saving || !canToggleSharePermissions(atomicPermissions.DELETE)"
@update:checked="toggleSharePermissions(atomicPermissions.DELETE)">
{{ t('files_sharing', 'Delete') }}
</NcActionCheckbox>
<NcActionButton @click="showCustomPermissionsForm = false">
<template #icon>
<ChevronLeft />
</template>
{{ t('files_sharing', 'Bundled permissions') }}
</NcActionButton>
</span>
</template>
</ul>
</li>
</template>
<script>
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
import NcActionRadio from '@nextcloud/vue/dist/Components/NcActionRadio.js'
import NcActionCheckbox from '@nextcloud/vue/dist/Components/NcActionCheckbox.js'
import SharesMixin from '../mixins/SharesMixin.js'
import {
ATOMIC_PERMISSIONS,
BUNDLED_PERMISSIONS,
hasPermissions,
permissionsSetIsValid,
togglePermissions,
canTogglePermissions,
} from '../lib/SharePermissionsToolBox.js'
import Tune from 'vue-material-design-icons/Tune.vue'
import ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'
export default {
name: 'SharePermissionsEditor',
components: {
NcActionButton,
NcActionCheckbox,
NcActionRadio,
Tune,
ChevronLeft,
},
mixins: [SharesMixin],
data() {
return {
randomFormName: Math.random().toString(27).substring(2),
showCustomPermissionsForm: false,
atomicPermissions: ATOMIC_PERMISSIONS,
bundledPermissions: BUNDLED_PERMISSIONS,
}
},
computed: {
/**
* Return the summary of custom checked permissions.
*
* @return {string}
*/
sharePermissionsSummary() {
return Object.values(this.atomicPermissions)
.filter(permission => this.shareHasPermissions(permission))
.map(permission => {
switch (permission) {
case this.atomicPermissions.CREATE:
return this.t('files_sharing', 'Upload')
case this.atomicPermissions.READ:
return this.t('files_sharing', 'Read')
case this.atomicPermissions.UPDATE:
return this.t('files_sharing', 'Edit')
case this.atomicPermissions.DELETE:
return this.t('files_sharing', 'Delete')
default:
return null
}
})
.filter(permissionLabel => permissionLabel !== null)
.join(', ')
},
/**
* Return whether the share's permission is a bundle.
*
* @return {boolean}
*/
sharePermissionsIsBundle() {
return Object.values(BUNDLED_PERMISSIONS)
.map(bundle => this.sharePermissionEqual(bundle))
.filter(isBundle => isBundle)
.length > 0
},
/**
* Return whether the share's permission is valid.
*
* @return {boolean}
*/
sharePermissionsSetIsValid() {
return permissionsSetIsValid(this.share.permissions)
},
/**
* Is the current share a folder ?
* TODO: move to a proper FileInfo model?
*
* @return {boolean}
*/
isFolder() {
return this.fileInfo.type === 'dir'
},
/**
* Does the current file/folder have create permissions.
* TODO: move to a proper FileInfo model?
*
* @return {boolean}
*/
fileHasCreatePermission() {
return !!(this.fileInfo.permissions & ATOMIC_PERMISSIONS.CREATE)
},
},
mounted() {
// Show the Custom Permissions view on open if the permissions set is not a bundle.
this.showCustomPermissionsForm = !this.sharePermissionsIsBundle
},
methods: {
/**
* Return whether the share has the exact given permissions.
*
* @param {number} permissions - the permissions to check.
*
* @return {boolean}
*/
sharePermissionEqual(permissions) {
// We use the share's permission without PERMISSION_SHARE as it is not relevant here.
return (this.share.permissions & ~ATOMIC_PERMISSIONS.SHARE) === permissions
},
/**
* Return whether the share has the given permissions.
*
* @param {number} permissions - the permissions to check.
*
* @return {boolean}
*/
shareHasPermissions(permissions) {
return hasPermissions(this.share.permissions, permissions)
},
/**
* Set the share permissions to the given permissions.
*
* @param {number} permissions - the permissions to set.
*
* @return {void}
*/
setSharePermissions(permissions) {
this.share.permissions = permissions
this.queueUpdate('permissions')
},
/**
* Return whether some given permissions can be toggled.
*
* @param {number} permissionsToToggle - the permissions to toggle.
*
* @return {boolean}
*/
canToggleSharePermissions(permissionsToToggle) {
return canTogglePermissions(this.share.permissions, permissionsToToggle)
},
/**
* Toggle a given permission.
*
* @param {number} permissions - the permissions to toggle.
*
* @return {void}
*/
toggleSharePermissions(permissions) {
this.share.permissions = togglePermissions(this.share.permissions, permissions)
if (!permissionsSetIsValid(this.share.permissions)) {
return
}
this.queueUpdate('permissions')
},
},
}
</script>
<style lang="scss" scoped>
.error {
::v-deep .action-checkbox__label:before {
border: 1px solid var(--color-error);
}
}
</style>