From c23ed76368125905cb0cb23273d8835b36f017a8 Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 28 Mar 2023 15:14:35 +0200 Subject: [PATCH 01/25] Init --- .../src/components/OcModal/OcModal.vue | 42 +++-- .../components/FilesList/ContextActions.vue | 8 +- .../src/composables/actions/files/index.ts | 1 + .../actions/files/useFileActions.ts | 5 +- .../useFileActionsCreateSpaceFromResource.ts | 154 ++++++++++++++++++ 5 files changed, 190 insertions(+), 20 deletions(-) create mode 100644 packages/web-app-files/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts diff --git a/packages/design-system/src/components/OcModal/OcModal.vue b/packages/design-system/src/components/OcModal/OcModal.vue index d05a7d4b963..bea212d9a25 100644 --- a/packages/design-system/src/components/OcModal/OcModal.vue +++ b/packages/design-system/src/components/OcModal/OcModal.vue @@ -17,23 +17,31 @@
- -

+

item.isEnabled(unref(actionOptions))) ) @@ -109,6 +112,7 @@ export default defineComponent({ ...unref(copyActions), ...unref(pasteActions), ...unref(renameActions), + ...unref(createSpaceFromResourceActions), ...unref(showEditTagsActions), ...unref(restoreActions), ...unref(acceptShareActions), diff --git a/packages/web-app-files/src/composables/actions/files/index.ts b/packages/web-app-files/src/composables/actions/files/index.ts index 1c7c946440e..1203708d328 100644 --- a/packages/web-app-files/src/composables/actions/files/index.ts +++ b/packages/web-app-files/src/composables/actions/files/index.ts @@ -18,3 +18,4 @@ export * from './useFileActionsShowActions' export * from './useFileActionsShowDetails' export * from './useFileActionsShowEditTags' export * from './useFileActionsShowShares' +export * from './useFileActionsCreateSpaceFromResource' diff --git a/packages/web-app-files/src/composables/actions/files/useFileActions.ts b/packages/web-app-files/src/composables/actions/files/useFileActions.ts index d317750f561..ebc0a3fec40 100644 --- a/packages/web-app-files/src/composables/actions/files/useFileActions.ts +++ b/packages/web-app-files/src/composables/actions/files/useFileActions.ts @@ -27,7 +27,8 @@ import { useFileActionsNavigate, useFileActionsRename, useFileActionsRestore, - useFileActionsShowEditTags + useFileActionsShowEditTags, + useFileActionsCreateSpaceFromResource } from './index' export const EDITOR_MODE_EDIT = 'edit' @@ -55,6 +56,7 @@ export const useFileActions = ({ store }: { store?: Store } = {}) => { const { actions: renameActions } = useFileActionsRename({ store }) const { actions: restoreActions } = useFileActionsRestore({ store }) const { actions: showEditTagsActions } = useFileActionsShowEditTags({ store }) + const { actions: createSpaceFromResource } = useFileActionsCreateSpaceFromResource({ store }) const systemActions = computed((): Action[] => [ ...unref(downloadArchiveActions), @@ -63,6 +65,7 @@ export const useFileActions = ({ store }: { store?: Store } = {}) => { ...unref(moveActions), ...unref(copyActions), ...unref(renameActions), + ...unref(createSpaceFromResource), ...unref(showEditTagsActions), ...unref(restoreActions), ...unref(acceptShareActions), diff --git a/packages/web-app-files/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts b/packages/web-app-files/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts new file mode 100644 index 00000000000..8ffc84c500e --- /dev/null +++ b/packages/web-app-files/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts @@ -0,0 +1,154 @@ +import { Store } from 'vuex' +import { computed, unref } from 'vue' +import { useGettext } from 'vue3-gettext' +import { FileAction, FileActionOptions } from 'web-pkg/src/composables/actions' +import { configurationManager, useAbility, useClientService, useRouter } from 'web-pkg' +import { buildSpace, isPersonalSpaceResource } from 'web-client/src/helpers' +import { isLocationSpacesActive } from 'web-app-files/src/router' +import { WebDAV } from 'web-client/src/webdav' +import { Drive } from 'web-client/src/generated' + +export const useFileActionsCreateSpaceFromResource = ({ store }: { store?: Store } = {}) => { + const { can } = useAbility() + const { $gettext, $ngettext } = useGettext() + const router = useRouter() + const clientService = useClientService() + const hasCreatePermission = computed(() => can('create-all', 'Space')) + + const confirmAction = async ({ spaceName, resources }) => { + store.dispatch('hideModal') + + try { + const client = clientService.graphAuthenticated + const { data: createdSpace } = await client.drives.createDrive({ name: spaceName }, {}) + const spaceResource = buildSpace({ + ...createdSpace, + serverUrl: configurationManager.serverUrl + }) + store.commit('runtime/spaces/UPSERT_SPACE', spaceResource) + + await (clientService.webdav as WebDAV).createFolder(spaceResource, { path: '.space' }) + const markdown = await (clientService.webdav as WebDAV).putFileContents(spaceResource, { + path: '.space/readme.md', + content: $gettext('Here you can add a description for this Space.') + }) + + const { data: updatedSpace } = await client.drives.updateDrive( + createdSpace.id, + { + special: [ + { + specialFolder: { + name: 'readme' + }, + id: markdown.id as string + } + ] + } as Drive, + {} + ) + store.commit('runtime/spaces/UPDATE_SPACE_FIELD', { + id: createdSpace.id, + field: 'spaceReadmeData', + value: updatedSpace.special.find((special) => special.specialFolder.name === 'readme') + }) + store.commit('runtime/spaces/UPDATE_SPACE_FIELD', { + id: createdSpace.id, + field: 'spaceQuota', + value: updatedSpace.quota + }) + } catch (error) { + console.error(error) + store.dispatch('showMessage', { + title: $gettext('Creating space failed…'), + status: 'danger' + }) + } + } + const handler = ({ resources, space }: FileActionOptions) => { + const modal = { + variation: 'passive', + title: $ngettext( + 'Create Space from "%{resourceName}"', + 'Create Space from selection', + resources.length, + { + resourceName: resources[0].name + } + ), + message: $ngettext( + 'Create Space with the content of "%{resourceName}". The content will be copied', + 'Create Space with the selected files. The content will be copied', + resources.length, + { + resourceName: resources[0].name + } + ), + cancelText: $gettext('Cancel'), + confirmText: $gettext('Create'), + hasInput: true, + inputLabel: $gettext('Space name'), + onInput: checkSpaceName, + onCancel: () => store.dispatch('hideModal'), + onConfirm: (spaceName) => confirmAction({ spaceName, space, resources }) + } + + store.dispatch('createModal', modal) + } + + const checkSpaceName = (name) => { + if (name.trim() === '') { + return store.dispatch('setModalInputErrorMessage', $gettext('Space name cannot be empty')) + } + if (name.length > 255) { + return store.dispatch( + 'setModalInputErrorMessage', + $gettext('Space name cannot exceed 255 characters') + ) + } + if (/[/\\.:?*"><|]/.test(name)) { + return store.dispatch( + 'setModalInputErrorMessage', + $gettext('Space name cannot contain the following characters: / \\\\ . : ? * " > < |\'') + ) + } + store.dispatch('setModalInputErrorMessage', null) + } + + const actions = computed((): FileAction[] => { + return [ + { + name: 'create-space-from-resource', + icon: 'layout-grid', + handler, + label: () => { + return $gettext('Create Space from selection') + }, + isEnabled: ({ resources, space }) => { + if (!resources.length) { + return false + } + + if (!unref(hasCreatePermission)) { + return false + } + + if ( + !isLocationSpacesActive(router, 'files-spaces-generic') || + !isPersonalSpaceResource(space) + ) { + return false + } + + return true + }, + componentType: 'button', + class: 'oc-files-actions-create-space-from-resource-trigger' + } + ] + }) + + return { + actions + } +} From 8ab083e22f011611b514c2c18ae7c7ea6907f1d6 Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 28 Mar 2023 15:32:17 +0200 Subject: [PATCH 02/25] Enhacement --- .../useFileActionsCreateSpaceFromResource.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/web-app-files/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts b/packages/web-app-files/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts index 8ffc84c500e..6afc3073c9e 100644 --- a/packages/web-app-files/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts +++ b/packages/web-app-files/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts @@ -2,7 +2,13 @@ import { Store } from 'vuex' import { computed, unref } from 'vue' import { useGettext } from 'vue3-gettext' import { FileAction, FileActionOptions } from 'web-pkg/src/composables/actions' -import { configurationManager, useAbility, useClientService, useRouter } from 'web-pkg' +import { + configurationManager, + useAbility, + useClientService, + useLoadingService, + useRouter +} from 'web-pkg' import { buildSpace, isPersonalSpaceResource } from 'web-client/src/helpers' import { isLocationSpacesActive } from 'web-app-files/src/router' import { WebDAV } from 'web-client/src/webdav' @@ -11,15 +17,16 @@ import { Drive } from 'web-client/src/generated' export const useFileActionsCreateSpaceFromResource = ({ store }: { store?: Store } = {}) => { const { can } = useAbility() const { $gettext, $ngettext } = useGettext() + const loadingService = useLoadingService() const router = useRouter() const clientService = useClientService() const hasCreatePermission = computed(() => can('create-all', 'Space')) - const confirmAction = async ({ spaceName, resources }) => { + const confirmAction = async ({ spaceName, resources, space }) => { store.dispatch('hideModal') + const client = clientService.graphAuthenticated try { - const client = clientService.graphAuthenticated const { data: createdSpace } = await client.drives.createDrive({ name: spaceName }, {}) const spaceResource = buildSpace({ ...createdSpace, @@ -57,6 +64,10 @@ export const useFileActionsCreateSpaceFromResource = ({ store }: { store?: Store field: 'spaceQuota', value: updatedSpace.quota }) + + store.dispatch('showMessage', { + title: $gettext('Space was created successfully') + }) } catch (error) { console.error(error) store.dispatch('showMessage', { @@ -90,7 +101,8 @@ export const useFileActionsCreateSpaceFromResource = ({ store }: { store?: Store inputLabel: $gettext('Space name'), onInput: checkSpaceName, onCancel: () => store.dispatch('hideModal'), - onConfirm: (spaceName) => confirmAction({ spaceName, space, resources }) + onConfirm: (spaceName) => + loadingService.addTask(() => confirmAction({ spaceName, space, resources })) } store.dispatch('createModal', modal) From e635b586370cdedbb6fa40c9ea57d9e7f3e953f2 Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 28 Mar 2023 16:35:52 +0200 Subject: [PATCH 03/25] Refactor --- .../src/components/AppBar/CreateSpace.vue | 69 +++------------- .../useFileActionsCreateSpaceFromResource.ts | 80 +++---------------- .../web-app-files/src/composables/index.ts | 1 + .../src/composables/spaces/index.ts | 1 + .../src/composables/spaces/useCreateSpace.ts | 44 ++++++++++ .../actions/spaces/useSpaceActionsRename.ts | 26 +----- packages/web-pkg/src/composables/index.ts | 2 + .../web-pkg/src/composables/spaces/index.ts | 1 + .../src/composables/spaces/useSpaceHelpers.ts | 28 +++++++ 9 files changed, 100 insertions(+), 152 deletions(-) create mode 100644 packages/web-app-files/src/composables/spaces/index.ts create mode 100644 packages/web-app-files/src/composables/spaces/useCreateSpace.ts create mode 100644 packages/web-pkg/src/composables/spaces/index.ts create mode 100644 packages/web-pkg/src/composables/spaces/useSpaceHelpers.ts diff --git a/packages/web-app-files/src/components/AppBar/CreateSpace.vue b/packages/web-app-files/src/components/AppBar/CreateSpace.vue index f017e4b1c60..12ebc79cdf7 100644 --- a/packages/web-app-files/src/components/AppBar/CreateSpace.vue +++ b/packages/web-app-files/src/components/AppBar/CreateSpace.vue @@ -15,18 +15,17 @@