From 779f0e33aaa4573811fbacc30c1306178df331a9 Mon Sep 17 00:00:00 2001 From: JanAckermann Date: Thu, 10 Feb 2022 10:00:52 +0100 Subject: [PATCH 01/32] Add set as space img/md action --- .../components/FilesList/ContextActions.vue | 6 +- .../src/mixins/spaces/actions/delete.js | 3 + .../src/mixins/spaces/actions/disable.js | 3 + .../mixins/spaces/actions/editDescription.js | 15 +- .../spaces/actions/editReadmeContent.js | 89 ++++++ .../src/mixins/spaces/actions/rename.js | 3 + .../src/mixins/spaces/actions/restore.js | 3 + .../src/mixins/spaces/actions/setImage.js | 87 ++++++ .../src/mixins/spaces/actions/setReadme.js | 54 ++++ .../src/mixins/spaces/actions/uploadImage.js | 91 ++++++ .../src/views/spaces/Project.vue | 282 ++++++++++++------ .../src/views/spaces/Projects.vue | 166 +++++++---- .../tests/unit/views/spaces/Project.spec.js | 13 +- .../tests/unit/views/spaces/Projects.spec.js | 10 +- .../spaces/__snapshots__/Project.spec.js.snap | 108 ++++++- 15 files changed, 771 insertions(+), 162 deletions(-) create mode 100644 packages/web-app-files/src/mixins/spaces/actions/editReadmeContent.js create mode 100644 packages/web-app-files/src/mixins/spaces/actions/setImage.js create mode 100644 packages/web-app-files/src/mixins/spaces/actions/setReadme.js create mode 100644 packages/web-app-files/src/mixins/spaces/actions/uploadImage.js diff --git a/packages/web-app-files/src/components/FilesList/ContextActions.vue b/packages/web-app-files/src/components/FilesList/ContextActions.vue index b520d245fc7..4e696a38db3 100644 --- a/packages/web-app-files/src/components/FilesList/ContextActions.vue +++ b/packages/web-app-files/src/components/FilesList/ContextActions.vue @@ -39,6 +39,8 @@ import Restore from '../../mixins/actions/restore' import ShowActions from '../../mixins/actions/showActions' import ShowDetails from '../../mixins/actions/showDetails' import ShowShares from '../../mixins/actions/showShares' +import SetSpaceImage from '../../mixins/spaces/actions/setImage' +import SetSpaceReadme from '../../mixins/spaces/actions/setReadme' export default { name: 'ContextActions', @@ -60,7 +62,9 @@ export default { Restore, ShowActions, ShowDetails, - ShowShares + ShowShares, + SetSpaceImage, + SetSpaceReadme ], props: { diff --git a/packages/web-app-files/src/mixins/spaces/actions/delete.js b/packages/web-app-files/src/mixins/spaces/actions/delete.js index 564cce18c23..f961f464341 100644 --- a/packages/web-app-files/src/mixins/spaces/actions/delete.js +++ b/packages/web-app-files/src/mixins/spaces/actions/delete.js @@ -68,6 +68,9 @@ export default { .then(() => { this.hideModal() this.REMOVE_FILE({ id }) + this.showMessage({ + title: this.$gettext('Space successfully deleted') + }) }) .catch((error) => { this.showMessage({ diff --git a/packages/web-app-files/src/mixins/spaces/actions/disable.js b/packages/web-app-files/src/mixins/spaces/actions/disable.js index 75a018c6a13..a6aaa345907 100644 --- a/packages/web-app-files/src/mixins/spaces/actions/disable.js +++ b/packages/web-app-files/src/mixins/spaces/actions/disable.js @@ -68,6 +68,9 @@ export default { field: 'disabled', value: true }) + this.showMessage({ + title: this.$gettext('Space successfully disabled') + }) }) .catch((error) => { this.showMessage({ diff --git a/packages/web-app-files/src/mixins/spaces/actions/editDescription.js b/packages/web-app-files/src/mixins/spaces/actions/editDescription.js index 833d8f54b9d..8fc332888be 100644 --- a/packages/web-app-files/src/mixins/spaces/actions/editDescription.js +++ b/packages/web-app-files/src/mixins/spaces/actions/editDescription.js @@ -11,10 +11,10 @@ export default { name: 'editDescription', icon: 'pencil', label: () => { - return this.$gettext('Change description') + return this.$gettext('Change subtitle') }, handler: this.$_editDescription_trigger, - isEnabled: () => false, // @TODO enable as soon as backend supports this + isEnabled: ({ resources }) => spaces.length === 1, componentType: 'oc-button', class: 'oc-files-actions-edit-description-trigger' } @@ -38,12 +38,12 @@ export default { const modal = { variation: 'passive', - title: this.$gettext('Change description for space') + ' ' + resources[0].name, + title: this.$gettext('Change subtitle for space') + ' ' + spaces[0].name, cancelText: this.$gettext('Cancel'), confirmText: this.$gettext('Confirm'), hasInput: true, - inputLabel: this.$gettext('Space description'), - inputValue: resources[0].description, + inputLabel: this.$gettext('Space subtitle'), + inputValue: spaces[0].description, onCancel: this.hideModal, onConfirm: (description) => this.$_editDescription_editDescriptionSpace(resources[0].id, description) @@ -63,10 +63,13 @@ export default { field: 'description', value: description }) + this.showMessage({ + title: this.$gettext('Space subtitle successfully changed') + }) }) .catch((error) => { this.showMessage({ - title: this.$gettext('Renaming space description failed…'), + title: this.$gettext('Changing space subtitle failed…'), desc: error, status: 'danger' }) diff --git a/packages/web-app-files/src/mixins/spaces/actions/editReadmeContent.js b/packages/web-app-files/src/mixins/spaces/actions/editReadmeContent.js new file mode 100644 index 00000000000..226920d0663 --- /dev/null +++ b/packages/web-app-files/src/mixins/spaces/actions/editReadmeContent.js @@ -0,0 +1,89 @@ +import { mapActions, mapGetters, mapMutations } from 'vuex' + +export default { + data: function () { + return { + $_editReadmeContent_modalOpen: false, + $_editReadmeContent_content: '' + } + }, + computed: { + ...mapGetters(['configuration', 'getToken']), + ...mapGetters('Files', ['currentFolder']), + $_editReadmeContent_items() { + return [ + { + name: 'editReadmeContent', + icon: 'markdown', + label: () => { + return this.$gettext('Change description') + }, + handler: this.$_editReadmeContent_trigger, + isEnabled: ({ spaces }) => { + if (!spaces[0].spaceReadmeData) { + return false + } + + return spaces.length === 1 + }, + componentType: 'oc-button', + class: 'oc-files-actions-edit-readme-content-trigger' + } + ] + } + }, + methods: { + ...mapActions([ + 'createModal', + 'hideModal', + 'setModalInputErrorMessage', + 'showMessage', + 'toggleModalConfirmButton' + ]), + ...mapMutations('Files', ['UPDATE_RESOURCE_FIELD']), + + $_editReadmeContent_trigger({ spaces }) { + if (spaces.length !== 1) { + return + } + const webDavPathComponents = spaces[0].spaceReadmeData.webDavUrl.split('/') + const path = webDavPathComponents.slice(webDavPathComponents.indexOf('dav') + 1).join('/') + + this.$client.files.getFileContents(path).then((readmeContent) => { + this.$data.$_editReadmeContent_modalOpen = true + this.$data.$_editReadmeContent_content = readmeContent + }) + }, + + $_editReadmeContent_editReadmeContentSpace() { + const space = this.currentFolder + const webDavPathComponents = space.spaceReadmeData.webDavUrl.split('/') + const path = webDavPathComponents.slice(webDavPathComponents.indexOf('dav') + 1).join('/') + + this.$client.files + .putFileContents(path, this.$data.$_editReadmeContent_content) + .then((readmeMetaData) => { + this.$_editReadmeContent_closeModal() + this.UPDATE_RESOURCE_FIELD({ + id: space.id, + field: 'spaceReadmeData', + value: { ...space.spaceReadmeData, ...{ etag: readmeMetaData.ETag } } + }) + this.showMessage({ + title: this.$gettext('Space description successfully changed') + }) + }) + .catch((error) => { + this.showMessage({ + title: this.$gettext('Changing space description failed…'), + desc: error, + status: 'danger' + }) + }) + }, + + $_editReadmeContent_closeModal() { + this.$data.$_editReadmeContent_modalOpen = false + } + } +} diff --git a/packages/web-app-files/src/mixins/spaces/actions/rename.js b/packages/web-app-files/src/mixins/spaces/actions/rename.js index f7bde4bd7d0..7442bc4b09f 100644 --- a/packages/web-app-files/src/mixins/spaces/actions/rename.js +++ b/packages/web-app-files/src/mixins/spaces/actions/rename.js @@ -70,6 +70,9 @@ export default { field: 'name', value: name }) + this.showMessage({ + title: this.$gettext('Space name successfully changed') + }) }) .catch((error) => { this.showMessage({ diff --git a/packages/web-app-files/src/mixins/spaces/actions/restore.js b/packages/web-app-files/src/mixins/spaces/actions/restore.js index 7bbd5afd937..d23f3b891b5 100644 --- a/packages/web-app-files/src/mixins/spaces/actions/restore.js +++ b/packages/web-app-files/src/mixins/spaces/actions/restore.js @@ -76,6 +76,9 @@ export default { field: 'disabled', value: false }) + this.showMessage({ + title: this.$gettext('Space successfully restored') + }) }) .catch((error) => { this.showMessage({ diff --git a/packages/web-app-files/src/mixins/spaces/actions/setImage.js b/packages/web-app-files/src/mixins/spaces/actions/setImage.js new file mode 100644 index 00000000000..25c2d38948d --- /dev/null +++ b/packages/web-app-files/src/mixins/spaces/actions/setImage.js @@ -0,0 +1,87 @@ +import { isLocationSpacesActive } from '../../../router' +import { clientService } from 'web-pkg/src/services' +import { mapMutations } from 'vuex' +import { buildResource } from '../../../helpers/resources' +import { bus } from 'web-pkg/src/instance' + +export default { + computed: { + $_setSpaceImage_items() { + return [ + { + name: 'set-space-image', + icon: 'image-edit', + handler: this.$_setSpaceImage_trigger, + label: () => { + return this.$gettext('Set as space image') + }, + isEnabled: ({ resources }) => { + if (resources.length !== 1) { + return false + } + if (!resources[0].mimeType?.startsWith('image/')) { + return false + } + return isLocationSpacesActive(this.$router, 'files-spaces-project') + }, + canBeDefault: false, + componentType: 'oc-button', + class: 'oc-files-actions-set-space-image-trigger' + } + ] + } + }, + methods: { + ...mapMutations('Files', ['UPDATE_RESOURCE_FIELD']), + $_setSpaceImage_trigger({ resources }) { + const graphClient = clientService.graphAuthenticated(this.configuration.server, this.getToken) + const spaceId = this.$route.params.spaceId + const sourcePath = resources[0].webDavPath + const destinationPath = `/spaces/${spaceId}/.space/${resources[0].name}` + + if (sourcePath === destinationPath) { + return + } + + this.$client.files.copy(sourcePath, destinationPath).then(() => { + this.$client.files.fileInfo(destinationPath).then((fileInfo) => { + const file = buildResource(fileInfo) + + graphClient.drives + .updateDrive( + spaceId, + { + special: [ + { + specialFolder: { + name: 'image' + }, + id: Buffer.from(file.id, 'base64').toString().split(':').pop() + } + ] + }, + {} + ) + .then(({ data }) => { + this.UPDATE_RESOURCE_FIELD({ + id: spaceId, + field: 'spaceImageData', + value: data.special.find((special) => special.specialFolder.name === 'image') + }) + this.showMessage({ + title: this.$gettext('Space image successfully set') + }) + bus.publish('app.files.list.load') + }) + .catch((error) => { + this.showMessage({ + title: this.$gettext('Set space image failed…'), + desc: error, + status: 'danger' + }) + }) + }) + }) + } + } +} diff --git a/packages/web-app-files/src/mixins/spaces/actions/setReadme.js b/packages/web-app-files/src/mixins/spaces/actions/setReadme.js new file mode 100644 index 00000000000..e8ad585b3ad --- /dev/null +++ b/packages/web-app-files/src/mixins/spaces/actions/setReadme.js @@ -0,0 +1,54 @@ +import { isLocationSpacesActive } from '../../../router' +import { mapMutations, mapGetters } from 'vuex' +import { bus } from 'web-pkg/src/instance' + +export default { + computed: { + ...mapGetters('Files', ['currentFolder']), + $_setSpaceReadme_items() { + return [ + { + name: 'set-space-readme', + icon: 'markdown', + handler: this.$_setSpaceReadme_trigger, + label: () => { + return this.$gettext('Set as space description') + }, + isEnabled: ({ resources }) => { + if (resources.length !== 1) { + return false + } + if (!resources[0].mimeType?.startsWith('text/')) { + return false + } + return isLocationSpacesActive(this.$router, 'files-spaces-project') + }, + canBeDefault: false, + componentType: 'oc-button', + class: 'oc-files-actions-set-space-readme-trigger' + } + ] + } + }, + methods: { + ...mapMutations('Files', ['UPDATE_RESOURCE_FIELD']), + $_setSpaceReadme_trigger({ resources }) { + const space = this.currentFolder + this.$client.files.getFileContents(resources[0].webDavPath).then((fileContent) => { + this.$client.files + .putFileContents(`/spaces/${space.id}/.space/readme.md`, fileContent) + .then((fileMetaData) => { + this.UPDATE_RESOURCE_FIELD({ + id: space.id, + field: 'spaceReadmeData', + value: { ...space.spaceReadmeData, ...{ etag: fileMetaData.ETag } } + }) + this.showMessage({ + title: this.$gettext('Space description successfully set') + }) + bus.publish('app.files.list.load') + }) + }) + } + } +} diff --git a/packages/web-app-files/src/mixins/spaces/actions/uploadImage.js b/packages/web-app-files/src/mixins/spaces/actions/uploadImage.js new file mode 100644 index 00000000000..c1612967fb0 --- /dev/null +++ b/packages/web-app-files/src/mixins/spaces/actions/uploadImage.js @@ -0,0 +1,91 @@ +import { mapMutations } from 'vuex' +import { clientService } from 'web-pkg/src/services' +import { bus } from 'web-pkg/src/instance' + +export default { + data: function () { + return { + selectedSpace: null + } + }, + computed: { + $_uploadSpaceImage_items() { + return [ + { + name: 'upload-space-image', + icon: 'image-add', + handler: this.$_uploadSpaceImage_trigger, + label: () => { + return this.$gettext('Upload new space image') + }, + isEnabled: ({ spaces }) => spaces.length === 1, + componentType: 'oc-button', + class: 'oc-files-actions-upload-space-image-trigger' + } + ] + } + }, + methods: { + ...mapMutations('Files', ['UPDATE_RESOURCE_FIELD']), + $_uploadSpaceImage_trigger({ spaces }) { + if (spaces.length !== 1) { + return + } + + this.selectedSpace = spaces[0] + this.$refs.spaceImageInput.click() + }, + $_uploadSpaceImage(ev) { + const graphClient = clientService.graphAuthenticated(this.configuration.server, this.getToken) + const file = ev.currentTarget.files[0] + + const extraHeaders = {} + if (file.lastModifiedDate) { + extraHeaders['X-OC-Mtime'] = '' + file.lastModifiedDate.getTime() / 1000 + } else if (file.lastModified) { + extraHeaders['X-OC-Mtime'] = '' + file.lastModified / 1000 + } + + this.$client.files + .putFileContents(`/spaces/${this.selectedSpace.id}/.space/${file.name}`, file, { + headers: extraHeaders, + overwrite: true + }) + .then((image) => { + graphClient.drives + .updateDrive( + this.selectedSpace.id, + { + special: [ + { + specialFolder: { + name: 'image' + }, + id: Buffer.from(image['OC-FileId'], 'base64').toString().split(':').pop() + } + ] + }, + {} + ) + .then(({ data }) => { + this.UPDATE_RESOURCE_FIELD({ + id: this.selectedSpace.id, + field: 'spaceImageData', + value: data.special.find((special) => special.specialFolder.name === 'image') + }) + bus.publish('app.files.list.load') + this.showMessage({ + title: this.$gettext('Space image successfully uploaded') + }) + }) + .catch((error) => { + this.showMessage({ + title: this.$gettext('Upload new space image failed…'), + desc: error, + status: 'danger' + }) + }) + }) + } + } +} diff --git a/packages/web-app-files/src/views/spaces/Project.vue b/packages/web-app-files/src/views/spaces/Project.vue index 494048a6dda..a1bfc8f662f 100644 --- a/packages/web-app-files/src/views/spaces/Project.vue +++ b/packages/web-app-files/src/views/spaces/Project.vue @@ -1,36 +1,110 @@