From 1c71bb823ffd29b6b8c72e205eb10368daa3aaa4 Mon Sep 17 00:00:00 2001 From: Jannik Stehle Date: Fri, 18 Feb 2022 15:57:02 +0100 Subject: [PATCH] Implement people sharing for spaces --- .../enhancement-spaces-people-sharing | 11 ++ .../SideBar/Details/SpaceDetails.vue | 89 +++++------ .../SideBar/Shares/Collaborators/ListItem.vue | 27 +++- .../InviteCollaborator/AutocompleteItem.vue | 6 +- .../InviteCollaboratorForm.vue | 34 ++++- .../InviteCollaborator/RecipientContainer.vue | 9 +- .../SideBar/Shares/RoleDropdown.vue | 31 +++- .../components/SideBar/Shares/SpaceShares.vue | 139 ++++++++++++++++++ packages/web-app-files/src/fileSideBars.js | 10 ++ .../web-app-files/src/helpers/resources.js | 45 +++++- .../web-app-files/src/helpers/share/role.ts | 49 ++++++ .../web-app-files/src/helpers/share/type.ts | 7 +- packages/web-app-files/src/store/actions.js | 131 ++++++++++++++++- packages/web-app-files/src/store/mutations.js | 21 ++- .../src/views/spaces/Project.vue | 56 ++++++- .../src/views/spaces/Projects.vue | 87 ++++++----- 16 files changed, 630 insertions(+), 122 deletions(-) create mode 100644 changelog/unreleased/enhancement-spaces-people-sharing create mode 100644 packages/web-app-files/src/components/SideBar/Shares/SpaceShares.vue diff --git a/changelog/unreleased/enhancement-spaces-people-sharing b/changelog/unreleased/enhancement-spaces-people-sharing new file mode 100644 index 00000000000..e10db827fc0 --- /dev/null +++ b/changelog/unreleased/enhancement-spaces-people-sharing @@ -0,0 +1,11 @@ +Enhancement: Implement people sharing for spaces + +Spaces can now be shared with other people. This change specifically includes: + +* listing all members who have access to a space (possible for all space members) +* adding members to a space and giving them dedicated roles (possible for managers only) +* editing the role of members (possible for managers only) +* removing members from a space (possible for managers only) + +https://github.com/owncloud/web/pull/? +https://github.com/owncloud/web/issues/6283 diff --git a/packages/web-app-files/src/components/SideBar/Details/SpaceDetails.vue b/packages/web-app-files/src/components/SideBar/Details/SpaceDetails.vue index 1567bcd94d4..5620ebf244e 100644 --- a/packages/web-app-files/src/components/SideBar/Details/SpaceDetails.vue +++ b/packages/web-app-files/src/components/SideBar/Details/SpaceDetails.vue @@ -16,7 +16,14 @@ />
- + + +
@@ -33,7 +40,7 @@ - + @@ -50,11 +57,10 @@ import { ref } from '@vue/composition-api' import Mixins from '../../../mixins' import MixinResources from '../../../mixins/resources' -import { mapGetters } from 'vuex' +import { mapActions, mapGetters } from 'vuex' import { useTask } from 'vue-concurrency' import { buildWebDavSpacesPath } from '../../../helpers/resources' -import { useStore } from 'web-pkg/src/composables' -import { clientService } from 'web-pkg/src/services' +import { spaceManager } from '../../../helpers/share' import SpaceQuota from '../../SpaceQuota.vue' export default { @@ -66,13 +72,8 @@ export default { return $gettext('Details') }, setup() { - const store = useStore() const spaceImage = ref('') const owners = ref([]) - const graphClient = clientService.graphAuthenticated( - store.getters.configuration.server, - store.getters.getToken - ) const loadImageTask = useTask(function* (signal, ref) { if (!ref.space?.spaceImageData) { @@ -87,24 +88,14 @@ export default { spaceImage.value = Buffer.from(fileContents).toString('base64') }) - const loadOwnersTask = useTask(function* (signal, ref) { - const promises = [] - for (const userId of ref.ownerUserIds) { - promises.push(graphClient.users.getUser(userId)) - } - - if (promises.length > 0) { - yield Promise.all(promises).then((resolvedData) => { - resolvedData.forEach((response) => { - owners.value.push(response.data) - }) - }) - } - }) - - return { loadImageTask, loadOwnersTask, spaceImage, owners } + return { loadImageTask, spaceImage, owners } }, computed: { + ...mapGetters('Files', [ + 'highlightedFile', + 'currentFileOutgoingCollaborators', + 'currentFileOutgoingLinks' + ]), ...mapGetters(['user']), space() { @@ -116,46 +107,31 @@ export default { lastModifyDate() { return this.formDateFromISO(this.space.mdate) }, - ownerUserIds() { - const permissions = this.space.spacePermissions?.filter((permission) => - permission.roles.includes('manager') - ) - if (!permissions.length) { - return [] - } - - const userIds = permissions.reduce((acc, item) => { - const ids = item.grantedTo.map((user) => user.user.id) - acc = acc.concat(ids) - return acc - }, []) - - return [...new Set(userIds)] - }, ownerUsernames() { const userId = this.user?.id - return this.owners - .map((owner) => { - if (owner.onPremisesSamAccountName === userId) { + return this.currentFileOutgoingCollaborators + .filter((share) => share.role.name === spaceManager.name) + .map((share) => { + if (share.collaborator.onPremisesSamAccountName === userId) { return this.$gettextInterpolate(this.$gettext('%{displayName} (me)'), { - displayName: owner.displayName + displayName: share.collaborator.displayName }) } - return owner.displayName + return share.collaborator.displayName }) .join(', ') }, hasPeopleShares() { - return false // @TODO + return this.currentFileOutgoingCollaborators.length > 1 }, hasLinkShares() { - return false // @TODO + return this.currentFileOutgoingLinks.length > 1 }, peopleShareCount() { - return 0 // @TODO + return this.currentFileOutgoingCollaborators.length }, linkShareCount() { - return 0 // @TODO + return this.currentFileOutgoingLinks.length }, shareLabel() { let peopleString, linksString @@ -203,7 +179,16 @@ export default { }, mounted() { this.loadImageTask.perform(this) - this.loadOwnersTask.perform(this) + }, + methods: { + ...mapActions('Files/sidebar', { + setSidebarPanel: 'setActivePanel', + closeSidebar: 'close' + }), + + expandPeoplePanel() { + this.setSidebarPanel('space-share-item') + } } } diff --git a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue index b93c1aabe9a..9fa372d5789 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue @@ -1,11 +1,13 @@ @@ -126,6 +132,10 @@ export default { return this.shareType === ShareTypes.user }, + isSpace() { + return this.shareType === ShareTypes.space + }, + shareTypeText() { return this.$gettext(this.shareType.label) }, @@ -135,6 +145,11 @@ export default { }, shareDisplayName() { + if (this.user.id === this.share.collaborator.name) { + return this.$gettextInterpolate(this.$gettext('%{collaboratorName} (me)'), { + collaboratorName: this.share.collaborator.displayName + }) + } return this.share.collaborator.displayName }, @@ -243,7 +258,9 @@ export default { saveShareChanges({ role, permissions, expirationDate }) { const bitmask = role.hasCustomPermissions ? SharePermissions.permissionsToBitmask(permissions) - : SharePermissions.permissionsToBitmask(role.permissions(!this.isOcis)) + : SharePermissions.permissionsToBitmask( + role.permissions(!this.isOcis || this.shareType === ShareTypes.space) + ) this.changeShare({ client: this.$client, share: this.share, diff --git a/packages/web-app-files/src/components/SideBar/Shares/InviteCollaborator/AutocompleteItem.vue b/packages/web-app-files/src/components/SideBar/Shares/InviteCollaborator/AutocompleteItem.vue index 8ca2360b1a5..07a0e5c2a8a 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/InviteCollaborator/AutocompleteItem.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/InviteCollaborator/AutocompleteItem.vue @@ -5,7 +5,7 @@ :class="collaboratorClass" > user.value.shareWith !== this.user.id) + .map((result) => { + // Inject the correct share type here as the response has always type "user" + return { ...result, value: { ...result.value, shareType } } + }) const groups = recipients.exact.groups.concat(recipients.groups) const remotes = recipients.exact.remotes.concat(recipients.remotes) @@ -229,15 +249,19 @@ export default { saveQueue.add(() => { const bitmask = this.selectedRole.hasCustomPermissions ? SharePermissions.permissionsToBitmask(this.customPermissions) - : SharePermissions.permissionsToBitmask(this.selectedRole.permissions(!this.isOcis)) + : SharePermissions.permissionsToBitmask( + this.selectedRole.permissions(!this.isOcis || this.resourceIsSpace) + ) this.addShare({ client: this.$client, path: this.highlightedFile.path, $gettext: this.$gettext, shareWith: collaborator.value.shareWith, + displayName: collaborator.label, shareType: collaborator.value.shareType, permissions: bitmask, - expirationDate: this.expirationDate + expirationDate: this.expirationDate, + spaceId: this.resourceIsSpace ? this.highlightedFile.id : null }) }) ) diff --git a/packages/web-app-files/src/components/SideBar/Shares/InviteCollaborator/RecipientContainer.vue b/packages/web-app-files/src/components/SideBar/Shares/InviteCollaborator/RecipientContainer.vue index afcdcbb1924..555bb3d7c6b 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/InviteCollaborator/RecipientContainer.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/InviteCollaborator/RecipientContainer.vue @@ -40,7 +40,9 @@ export default { formattedRecipient: { name: this.recipient.label, icon: this.getRecipientIcon(), - hasAvatar: this.recipient.value.shareType === ShareTypes.user.value, + hasAvatar: + this.recipient.value.shareType === ShareTypes.user.value || + this.recipient.value.shareType === ShareTypes.space.value, isLoadingAvatar: true } } @@ -57,10 +59,7 @@ export default { }, async created() { - if ( - this.capabilities.files_sharing.user.profile_picture && - this.recipient.value.shareType === ShareTypes.user.value - ) { + if (this.capabilities.files_sharing.user.profile_picture && this.hasAvatar) { try { this.formattedRecipient.avatar = await avatarUrl({ server: this.configuration.server, diff --git a/packages/web-app-files/src/components/SideBar/Shares/RoleDropdown.vue b/packages/web-app-files/src/components/SideBar/Shares/RoleDropdown.vue index 1788922f5a6..3b1c97b2109 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/RoleDropdown.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/RoleDropdown.vue @@ -77,7 +77,12 @@ + + diff --git a/packages/web-app-files/src/fileSideBars.js b/packages/web-app-files/src/fileSideBars.js index 63ab3881037..6587dcd31f8 100644 --- a/packages/web-app-files/src/fileSideBars.js +++ b/packages/web-app-files/src/fileSideBars.js @@ -7,6 +7,7 @@ import FileLinks from './components/SideBar/Links/FileLinks.vue' import NoSelection from './components/SideBar/NoSelection.vue' import SpaceActions from './components/SideBar/Actions/SpaceActions.vue' import SpaceDetails from './components/SideBar/Details/SpaceDetails.vue' +import SpaceShares from './components/SideBar/Shares/SpaceShares.vue' import { isLocationCommonActive, isLocationSpacesActive } from './router' export default [ @@ -112,5 +113,14 @@ export default [ get enabled() { return highlightedFile?.type === 'space' } + }), + ({ highlightedFile }) => ({ + app: 'space-share-item', + component: SpaceShares, + icon: 'group', + iconFillType: 'line', + get enabled() { + return highlightedFile?.type === 'space' + } }) ] diff --git a/packages/web-app-files/src/helpers/resources.js b/packages/web-app-files/src/helpers/resources.js index 253d9313d67..50ae0913546 100644 --- a/packages/web-app-files/src/helpers/resources.js +++ b/packages/web-app-files/src/helpers/resources.js @@ -4,7 +4,15 @@ import { DateTime } from 'luxon' import { getIndicators } from './statusIndicators' import { $gettext } from '../gettext' import { DavPermission, DavProperty } from 'web-pkg/src/constants' -import { PeopleShareRoles, SharePermissions, ShareStatus, ShareTypes } from './share' +import { + PeopleShareRoles, + SharePermissions, + ShareStatus, + ShareTypes, + spaceEditor, + spaceManager, + spaceViewer +} from './share' function _getFileExtension(name) { const extension = path.extname(name) @@ -324,9 +332,44 @@ export function buildShare(s, file, allowSharePermission) { if (parseInt(s.share_type) === ShareTypes.link.value) { return _buildLink(s) } + if (parseInt(s.share_type) === ShareTypes.space.value) { + return buildSpaceShare(s, file) + } + return buildCollaboratorShare(s, file, allowSharePermission) } +export function buildSpaceShare(s, spaceId) { + let permissions, role + + switch (s.role) { + case spaceManager.inlineLabel: + permissions = 31 + role = spaceManager + break + case spaceEditor.inlineLabel: + permissions = 15 + role = spaceEditor + break + case spaceViewer.inlineLabel: + permissions = 1 + role = spaceViewer + break + } + + return { + shareType: ShareTypes.space.value, + id: spaceId, + collaborator: { + name: s.onPremisesSamAccountName, + displayName: s.displayName, + additionalInfo: null + }, + permissions, + role + } +} + function _buildLink(link) { let description = '' diff --git a/packages/web-app-files/src/helpers/share/role.ts b/packages/web-app-files/src/helpers/share/role.ts index bd55034878e..4c52f658d7b 100644 --- a/packages/web-app-files/src/helpers/share/role.ts +++ b/packages/web-app-files/src/helpers/share/role.ts @@ -98,6 +98,20 @@ export class PeopleShareRole extends ShareRole { } } +export class SpaceShareRole extends ShareRole { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public description(allowSharing: boolean): string { + switch (this.name) { + case spaceViewer.name: + return $gettext('Download and preview') + case spaceEditor.name: + return $gettext('Upload, edit, delete, download and preview') + case spaceManager.name: + return $gettext('Upload, edit, delete, download, preview and share') + } + } +} + export class LinkShareRole extends ShareRole { public description(allowSharing: boolean): string { return linkRoleDescriptions[this.bitmask(allowSharing)] @@ -193,6 +207,41 @@ export const linkRoleUploaderFolder = new LinkShareRole( $gettext('uploader'), [SharePermissions.create] ) +export const spaceViewer = new SpaceShareRole( + 'spaceViewer', + false, + $gettext('Viewer'), + $gettext('viewer'), + [SharePermissions.read] +) +export const spaceEditor = new SpaceShareRole( + 'spaceEditor', + false, + $gettext('Editor'), + $gettext('editor'), + [SharePermissions.read, SharePermissions.update, SharePermissions.create, SharePermissions.delete] +) +export const spaceManager = new SpaceShareRole( + 'spaceManager', + false, + $gettext('Manager'), + $gettext('manager'), + [ + SharePermissions.read, + SharePermissions.update, + SharePermissions.create, + SharePermissions.delete, + SharePermissions.share + ] +) + +export abstract class SpacePeopleShareRoles { + static readonly all = [spaceViewer, spaceEditor, spaceManager] + + static getByBitmask(bitmask: number): ShareRole { + return this.all.find((r) => r.bitmask(true) === bitmask) + } +} export abstract class PeopleShareRoles { static readonly all = [ diff --git a/packages/web-app-files/src/helpers/share/type.ts b/packages/web-app-files/src/helpers/share/type.ts index 34114f95628..47c928d7489 100644 --- a/packages/web-app-files/src/helpers/share/type.ts +++ b/packages/web-app-files/src/helpers/share/type.ts @@ -35,12 +35,13 @@ export abstract class ShareTypes { static readonly link = new ShareType('link', 3, $gettext('Link')) static readonly guest = new ShareType('guest', 4, $gettext('Guest')) static readonly remote = new ShareType('remote', 6, $gettext('Federated')) + static readonly space = new ShareType('space', 7, $gettext('User')) - static readonly individuals = [this.user, this.guest, this.remote] + static readonly individuals = [this.user, this.guest, this.remote, this.space] static readonly collectives = [this.group] static readonly unauthenticated = [this.link] - static readonly authenticated = [this.user, this.group, this.guest, this.remote] - static readonly all = [this.user, this.group, this.link, this.guest, this.remote] + static readonly authenticated = [this.user, this.group, this.guest, this.remote, this.space] + static readonly all = [this.user, this.group, this.link, this.guest, this.remote, this.space] static isIndividual(type: ShareType): boolean { return this.individuals.includes(type) diff --git a/packages/web-app-files/src/store/actions.js b/packages/web-app-files/src/store/actions.js index 37d0160a99a..94fb0971f6e 100644 --- a/packages/web-app-files/src/store/actions.js +++ b/packages/web-app-files/src/store/actions.js @@ -2,12 +2,17 @@ import PQueue from 'p-queue' import { getParentPaths } from '../helpers/path' import { dirname } from 'path' -import { buildResource, buildShare, buildCollaboratorShare } from '../helpers/resources' +import { + buildResource, + buildShare, + buildCollaboratorShare, + buildSpaceShare +} from '../helpers/resources' import { $gettext, $gettextInterpolate } from '../gettext' import { loadPreview } from '../helpers/resource' import { avatarUrl } from '../helpers/user' import { has } from 'lodash-es' -import { ShareTypes } from '../helpers/share' +import { ShareTypes, SpacePeopleShareRoles } from '../helpers/share' export default { updateFileProgress({ commit }, progress) { @@ -144,11 +149,47 @@ export default { value: computeShareTypes(state.currentFileOutgoingShares) }) }, - loadCurrentFileOutgoingShares(context, { client, path }) { + loadCurrentFileOutgoingShares(context, { client, path, space }) { context.commit('CURRENT_FILE_OUTGOING_SHARES_SET', []) context.commit('CURRENT_FILE_OUTGOING_SHARES_ERROR', null) context.commit('CURRENT_FILE_OUTGOING_SHARES_LOADING', true) + if (space) { + const promises = [] + const spaceShares = [] + + for (const permission of space.spacePermissions) { + for (const { + user: { id } + } of permission.grantedTo) { + promises.push( + client.users.getUser(id).then((resolved) => { + spaceShares.push( + buildSpaceShare( + { + ...resolved.data, + role: permission.roles[0] + }, + space.id + ) + ) + }) + ) + } + } + + return Promise.all(promises) + .then(() => { + context.commit('CURRENT_FILE_OUTGOING_SHARES_SET', spaceShares) + context.dispatch('updateCurrentFileShareTypes') + context.commit('CURRENT_FILE_OUTGOING_SHARES_LOADING', false) + }) + .catch((error) => { + context.commit('CURRENT_FILE_OUTGOING_SHARES_ERROR', error.message) + context.commit('CURRENT_FILE_OUTGOING_SHARES_LOADING', false) + }) + } + // see https://owncloud.dev/owncloud-sdk/Shares.html client.shares .getShares(path, { reshares: true }) @@ -211,6 +252,29 @@ export default { }) } + if (share.shareType === ShareTypes.space.value) { + return new Promise((resolve, reject) => { + client.shares + .shareSpaceWithUser('', share.collaborator.name, share.id, { + permissions + }) + .then(() => { + const role = SpacePeopleShareRoles.getByBitmask(permissions) + const shareObj = { + role: role.inlineLabel, + onPremisesSamAccountName: share.collaborator.name, + displayName: share.collaborator.displayName + } + const updatedShare = buildSpaceShare(shareObj, share.id) + commit('CURRENT_FILE_OUTGOING_SHARES_UPDATE', updatedShare) + resolve(updatedShare) + }) + .catch((e) => { + reject(e) + }) + }) + } + return new Promise((resolve, reject) => { client.shares .updateShare(share.id, params) @@ -228,7 +292,10 @@ export default { }) }) }, - addShare(context, { client, path, shareWith, shareType, permissions, expirationDate }) { + addShare( + context, + { client, path, shareWith, shareType, permissions, expirationDate, spaceId, displayName } + ) { if (shareType === ShareTypes.group.value) { client.shares .shareFileWithGroup(path, shareWith, { @@ -261,6 +328,41 @@ export default { return } + if (shareType === ShareTypes.space.value) { + client.shares + .shareSpaceWithUser(path, shareWith, spaceId, { + permissions + }) + .then(() => { + const role = SpacePeopleShareRoles.getByBitmask(permissions) + const shareObj = { + role: role.inlineLabel, + onPremisesSamAccountName: shareWith, + displayName + } + + context.commit('CURRENT_FILE_OUTGOING_SHARES_ADD', buildSpaceShare(shareObj, spaceId)) + context.commit('CURRENT_FILE_OUTGOING_SHARES_LOADING', true) + + // FIXME + return Promise.all([]).then(() => { + context.commit('CURRENT_FILE_OUTGOING_SHARES_LOADING', false) + }) + }) + .catch((e) => { + context.dispatch( + 'showMessage', + { + title: $gettext('Error while sharing.'), + desc: e, + status: 'danger' + }, + { root: true } + ) + }) + return + } + const remoteShare = shareType === ShareTypes.remote.value client.shares .shareFileWithUser(path, shareWith, { @@ -293,12 +395,27 @@ export default { }) }, deleteShare(context, { client, share, resource }) { + const additionalParams = {} + if (share.shareType === ShareTypes.space.value) { + additionalParams.shareWith = share.collaborator.name + } + client.shares - .deleteShare(share.id) + .deleteShare(share.id, additionalParams) .then(() => { context.commit('CURRENT_FILE_OUTGOING_SHARES_REMOVE', share) - context.dispatch('updateCurrentFileShareTypes') - context.dispatch('loadIndicators', { client, currentFolder: resource.path }) + + if (share.shareType !== ShareTypes.space.value) { + context.dispatch('updateCurrentFileShareTypes') + context.dispatch('loadIndicators', { client, currentFolder: resource.path }) + } else { + context.commit('CURRENT_FILE_OUTGOING_SHARES_LOADING', true) + + // FIXME + return Promise.all([]).then(() => { + context.commit('CURRENT_FILE_OUTGOING_SHARES_LOADING', false) + }) + } }) .catch((e) => { console.error(e) diff --git a/packages/web-app-files/src/store/mutations.js b/packages/web-app-files/src/store/mutations.js index 42929b18445..5c03d85323a 100644 --- a/packages/web-app-files/src/store/mutations.js +++ b/packages/web-app-files/src/store/mutations.js @@ -4,6 +4,7 @@ import { DateTime } from 'luxon' import { set, has } from 'lodash-es' import { getIndicators } from '../helpers/statusIndicators' import { renameResource } from '../helpers/resources' +import { ShareTypes } from '../helpers/share' export default { UPDATE_FILE_PROGRESS(state, file) { @@ -121,14 +122,28 @@ export default { state.currentFileOutgoingShares.push(share) }, CURRENT_FILE_OUTGOING_SHARES_REMOVE(state, share) { + if (share.shareType === ShareTypes.space.value) { + state.currentFileOutgoingShares = state.currentFileOutgoingShares.filter( + (s) => share.id === s.id && share.collaborator.name !== s.collaborator.name + ) + return + } state.currentFileOutgoingShares = state.currentFileOutgoingShares.filter( (s) => share.id !== s.id ) }, CURRENT_FILE_OUTGOING_SHARES_UPDATE(state, share) { - const fileIndex = state.currentFileOutgoingShares.findIndex((s) => { - return s.id === share.id - }) + let fileIndex + if (share.shareType === ShareTypes.space.value) { + fileIndex = state.currentFileOutgoingShares.findIndex((s) => { + return share.id === s.id && share.collaborator.name === s.collaborator.name + }) + } else { + fileIndex = state.currentFileOutgoingShares.findIndex((s) => { + return s.id === share.id + }) + } + if (fileIndex >= 0) { Vue.set(state.currentFileOutgoingShares, fileIndex, share) } else { diff --git a/packages/web-app-files/src/views/spaces/Project.vue b/packages/web-app-files/src/views/spaces/Project.vue index 494048a6dda..ea8c4b1551f 100644 --- a/packages/web-app-files/src/views/spaces/Project.vue +++ b/packages/web-app-files/src/views/spaces/Project.vue @@ -18,7 +18,21 @@ />
-

{{ space.name }}

+
+

{{ space.name }}

+ + + + +

{{ space.description }}

@@ -218,6 +232,14 @@ export default { } }) + const loadSharesTask = useTask(function* (signal, ref) { + ref.loadCurrentFileOutgoingShares({ + client: graphClient, + path: ref.space.id, + space: ref.space + }) + }) + return { space, loadImageTask, @@ -226,6 +248,7 @@ export default { imageContent, loadResourcesTask, loadFilesListTask, + loadSharesTask, resourceTargetLocation: createLocationSpaces('files-spaces-project'), paginatedResources, paginationPages, @@ -250,7 +273,8 @@ export default { 'selectedFiles', 'currentFolder', 'totalFilesCount', - 'totalFilesSize' + 'totalFilesSize', + 'currentFileOutgoingCollaborators' ]), selected: { @@ -278,6 +302,17 @@ export default { }, displayThumbnails() { return !this.configuration.options.disablePreviews + }, + peopleCountString() { + const translated = this.$ngettext( + '%{count} invited person', + '%{count} invited people', + this.currentFileOutgoingCollaborators.length + ) + + return this.$gettextInterpolate(translated, { + count: this.currentFileOutgoingCollaborators.length + }) } }, watch: { @@ -295,6 +330,7 @@ export default { }, async mounted() { await this.loadResourcesTask.perform(this, false, this.$route.params.item || '') + this.loadSharesTask.perform(this) document.title = `${this.space.name} - ${this.$route.meta.title}` this.$route.params.name = this.space.name @@ -313,7 +349,11 @@ export default { } }, methods: { - ...mapActions('Files', ['loadIndicators', 'loadPreview']), + ...mapActions('Files', ['loadIndicators', 'loadPreview', 'loadCurrentFileOutgoingShares']), + ...mapActions('Files/sidebar', { + openSidebarWithPanel: 'openWithPanel', + closeSidebar: 'close' + }), ...mapMutations('Files', [ 'SET_CURRENT_FOLDER', 'LOAD_FILES', @@ -368,6 +408,12 @@ export default { isResourceInSelection(resource) { return this.selected?.includes(resource) + }, + + async openSidebarSharePanel() { + await this.closeSidebar() + this.SET_FILE_SELECTION([this.space]) + this.openSidebarWithPanel('space-share-item') } } } @@ -394,6 +440,10 @@ export default { font-size: 1.5rem; } + &-people-count { + white-space: nowrap; + } + .markdown-container * { color: var(--oc-color-text-muted) !important; } diff --git a/packages/web-app-files/src/views/spaces/Projects.vue b/packages/web-app-files/src/views/spaces/Projects.vue index 55a386b1319..695d9ef8c4d 100644 --- a/packages/web-app-files/src/views/spaces/Projects.vue +++ b/packages/web-app-files/src/views/spaces/Projects.vue @@ -82,40 +82,52 @@
- - - - -
    -
  • - + + + +
+
+ + + + +
    +
  • - - {{ action.label() }} - -
  • -
-
+ + + {{ action.label() }} + + + + +

@@ -231,6 +243,10 @@ export default { }, methods: { ...mapActions(['createModal', 'hideModal', 'setModalInputErrorMessage']), + ...mapActions('Files/sidebar', { + openSidebarWithPanel: 'openWithPanel', + closeSidebar: 'close' + }), ...mapMutations('Files', [ 'SET_CURRENT_FOLDER', 'LOAD_FILES', @@ -305,6 +321,11 @@ export default { return 'state-trashed' } return '' + }, + async openSidebarSharePanel(space) { + await this.closeSidebar() + this.SET_FILE_SELECTION([space]) + this.openSidebarWithPanel('space-share-item') } } }