diff --git a/packages/web-app-files/src/components/AppBar/AppBar.vue b/packages/web-app-files/src/components/AppBar/AppBar.vue index 239f0e1444d..81ead33e524 100644 --- a/packages/web-app-files/src/components/AppBar/AppBar.vue +++ b/packages/web-app-files/src/components/AppBar/AppBar.vue @@ -32,131 +32,16 @@ class="oc-flex-1 oc-flex oc-flex-start" style="gap: 15px" > - + @@ -171,30 +56,28 @@ import { mapActions, mapGetters, mapState, mapMutations } from 'vuex' import pathUtil from 'path' import Mixins from '../../mixins' -import MixinFileActions, { EDITOR_MODE_CREATE } from '../../mixins/fileActions' +import MixinFileActions from '../../mixins/fileActions' import { buildResource, buildWebDavFilesPath, buildWebDavSpacesPath } from '../../helpers/resources' import { bus } from 'web-pkg/src/instance' -import { isLocationActive, isLocationPublicActive, isLocationSpacesActive } from '../../router' +import { DavProperties } from 'web-pkg/src/constants' +import { isLocationPublicActive, isLocationSpacesActive } from '../../router' import { useActiveLocation } from '../../composables' import BatchActions from './SelectedResources/BatchActions.vue' +import ContextActions from '../FilesList/ContextActions.vue' +import CreateAndUpload from './CreateAndUpload.vue' import FileDrop from './Upload/FileDrop.vue' -import FileUpload from './Upload/FileUpload.vue' -import FolderUpload from './Upload/FolderUpload.vue' import SizeInfo from './SelectedResources/SizeInfo.vue' import ViewOptions from './ViewOptions.vue' -import { DavProperties, DavProperty } from 'web-pkg/src/constants' -import ContextActions from '../FilesList/ContextActions.vue' export default { components: { BatchActions, + ContextActions, + CreateAndUpload, FileDrop, - FileUpload, - FolderUpload, SizeInfo, - ViewOptions, - ContextActions + ViewOptions }, mixins: [Mixins, MixinFileActions], setup() { @@ -222,53 +105,6 @@ export default { ...mapGetters('Files', ['files', 'currentFolder', 'selectedFiles', 'publicLinkPassword']), ...mapState('Files', ['areHiddenFilesShown']), - mimetypesAllowedForCreation() { - // we can't use `mapGetters` here because the External app doesn't exist in all deployments - const mimeTypes = this.$store.getters['External/mimeTypes'] - if (!mimeTypes) { - return [] - } - return mimeTypes.filter((mimetype) => mimetype.allow_creation) || [] - }, - createFileActionsAvailable() { - return this.newFileHandlersForRoute.length > 0 || this.mimetypesAllowedForCreation.length > 0 - }, - newButtonTooltip() { - if (!this.canUpload) { - return this.$gettext('You have no permission to create new files!') - } - if (!this.hasFreeSpace) { - return this.$gettext('You have not enough space left to create new files!') - } - return null - }, - newButtonAriaLabel() { - const tooltip = this.newButtonTooltip - if (tooltip) { - return tooltip - } - if (!this.createFileActionsAvailable) { - return this.$gettext('Create a new folder') - } - return this.$gettext('Create new files or folders') - }, - uploadButtonTooltip() { - if (!this.canUpload) { - return this.$gettext('You have no permission to upload!') - } - if (!this.hasFreeSpace) { - return this.$gettext('You have not enough space left to upload!') - } - return null - }, - uploadButtonAriaLabel() { - const tooltip = this.uploadButtonTooltip - if (tooltip) { - return tooltip - } - return this.$gettext('Upload files or folders') - }, - currentPath() { const path = this.$route.params.item || '' if (path.endsWith('/')) { @@ -388,10 +224,6 @@ export default { return this.selectedFiles.length < 1 }, - uploadOrFileCreationBlocked() { - return !this.canUpload || !this.hasFreeSpace - }, - selectedResourcesAnnouncement() { if (this.selectedFiles.length === 0) { return this.$gettext('No items selected.') @@ -402,12 +234,6 @@ export default { this.selectedFiles.length ) return this.$gettextInterpolate(translated, { amount: this.selectedFiles.length }) - }, - - newFileHandlersForRoute() { - return this.newFileHandlers.filter(({ routes = [] }) => - isLocationActive(this.$router, ...routes.map((name) => ({ name }))) - ) } }, @@ -423,318 +249,10 @@ export default { methods: { ...mapActions('Files', ['updateFileProgress', 'removeFilesFromTrashbin', 'loadIndicators']), - ...mapActions(['openFile', 'showMessage', 'createModal', 'setModalInputErrorMessage']), + ...mapActions(['openFile', 'showMessage']), ...mapMutations('Files', ['UPSERT_RESOURCE', 'SET_HIDDEN_FILES_VISIBILITY']), ...mapMutations(['SET_QUOTA']), - showCreateResourceModal( - isFolder = true, - ext = 'txt', - openAction = null, - addAppProviderFile = false - ) { - const defaultName = isFolder - ? this.$gettext('New folder') - : this.$gettext('New file') + '.' + ext - const checkInputValue = (value) => { - this.setModalInputErrorMessage( - isFolder ? this.checkNewFolderName(value) : this.checkNewFileName(value) - ) - } - - // Sets action to be executed after creation of the file - if (!isFolder) { - this.newFileAction = openAction - } - - const modal = { - variation: 'passive', - title: isFolder ? this.$gettext('Create a new folder') : this.$gettext('Create a new file'), - cancelText: this.$gettext('Cancel'), - confirmText: this.$gettext('Create'), - hasInput: true, - inputValue: defaultName, - inputLabel: isFolder ? this.$gettext('Folder name') : this.$gettext('File name'), - inputError: isFolder - ? this.checkNewFolderName(defaultName) - : this.checkNewFileName(defaultName), - onCancel: this.hideModal, - onConfirm: isFolder - ? this.addNewFolder - : addAppProviderFile - ? this.addAppProviderFile - : this.addNewFile, - onInput: checkInputValue - } - - this.createModal(modal) - }, - - async addNewFolder(folderName) { - if (folderName === '') { - return - } - - this.fileFolderCreationLoading = true - - try { - let path = pathUtil.join(this.currentPath, folderName) - let resource - - if (this.isPersonalLocation) { - path = buildWebDavFilesPath(this.user.id, path) - await this.$client.files.createFolder(path) - resource = await this.$client.files.fileInfo(path, DavProperties.Default) - } else if (this.isSpacesProjectLocation) { - path = buildWebDavSpacesPath(this.$route.params.spaceId, path) - await this.$client.files.createFolder(path) - resource = await this.$client.files.fileInfo(path, DavProperties.Default) - } else { - await this.$client.publicFiles.createFolder(path, null, this.publicLinkPassword) - resource = await this.$client.publicFiles.getFileInfo( - path, - this.publicLinkPassword, - DavProperties.PublicLink - ) - } - resource = buildResource(resource) - - this.UPSERT_RESOURCE(resource) - this.hideModal() - - if (this.isPersonalLocation) { - this.loadIndicators({ - client: this.$client, - currentFolder: this.currentFolder.path - }) - } - - this.showMessage({ - title: this.$gettextInterpolate( - this.$gettext('"%{folderName}" was created successfully'), - { - folderName - } - ) - }) - } catch (error) { - console.error(error) - this.showMessage({ - title: this.$gettext('Failed to create folder'), - status: 'danger' - }) - } - - this.fileFolderCreationLoading = false - }, - - checkNewFolderName(folderName) { - if (folderName === '') { - return this.$gettext('Folder name cannot be empty') - } - - if (/[/]/.test(folderName)) { - return this.$gettext('Folder name cannot contain "/"') - } - - if (folderName === '.') { - return this.$gettext('Folder name cannot be equal to "."') - } - - if (folderName === '..') { - return this.$gettext('Folder name cannot be equal to ".."') - } - - if (/\s+$/.test(folderName)) { - return this.$gettext('Folder name cannot end with whitespace') - } - - const exists = this.files.find((file) => file.name === folderName) - - if (exists) { - const translated = this.$gettext('%{name} already exists') - return this.$gettextInterpolate(translated, { name: folderName }, true) - } - - return null - }, - - async addNewFile(fileName) { - if (fileName === '') { - return - } - - this.fileFolderCreationLoading = true - - try { - let resource - let path = pathUtil.join(this.currentPath, fileName) - - if (this.isPersonalLocation) { - path = buildWebDavFilesPath(this.user.id, path) - await this.$client.files.putFileContents(path, '') - resource = await this.$client.files.fileInfo(path, DavProperties.Default) - } else if (this.isSpacesProjectLocation) { - path = buildWebDavSpacesPath(this.$route.params.spaceId, path) - await this.$client.files.putFileContents(path, '') - resource = await this.$client.files.fileInfo(path, DavProperties.Default) - } else { - await this.$client.publicFiles.putFileContents('', path, this.publicLinkPassword, '') - resource = await this.$client.publicFiles.getFileInfo( - path, - this.publicLinkPassword, - DavProperties.PublicLink - ) - } - - if (this.newFileAction) { - const fileId = resource.fileInfo[DavProperty.FileId] - - this.$_fileActions_openEditor(this.newFileAction, path, fileId, EDITOR_MODE_CREATE) - this.hideModal() - - return - } - - resource = buildResource(resource) - - this.UPSERT_RESOURCE(resource) - this.hideModal() - - if (this.isPersonalLocation) { - this.loadIndicators({ - client: this.$client, - currentFolder: this.currentFolder.path - }) - } - - this.showMessage({ - title: this.$gettextInterpolate(this.$gettext('"%{fileName}" was created successfully'), { - fileName - }) - }) - } catch (error) { - console.error(error) - this.showMessage({ - title: this.$gettext('Failed to create file'), - status: 'danger' - }) - } - - this.fileFolderCreationLoading = false - }, - async addAppProviderFile(fileName) { - // FIXME: this belongs in web-app-external, but the app provider handles file creation differently than other editor extensions. Needs more refactoring. - if (fileName === '') { - return - } - try { - const parent = this.currentFolder.fileId - const publicToken = (this.$router.currentRoute.params.item || '').split('/')[0] - - const configUrl = this.configuration.server - const appNewUrl = this.capabilities.files.app_providers[0].new_url.replace(/^\/+/, '') - const url = - configUrl + - appNewUrl + - `?parent_container_id=${parent}&filename=${encodeURIComponent(fileName)}` - - const headers = { - 'X-Requested-With': 'XMLHttpRequest', - ...(this.isPublicLocation && - publicToken && { - 'public-token': publicToken - }), - ...(this.isPublicLocation && - this.publicLinkPassword && { - Authorization: - 'Basic ' + - Buffer.from(['public', this.publicLinkPassword].join(':')).toString('base64') - }), - ...(this.getToken && { - Authorization: 'Bearer ' + this.getToken - }) - } - - const response = await fetch(url, { - method: 'POST', - headers - }) - - if (response.status !== 200) { - throw new Error(`An error has occurred: ${response.status}`) - } - - let resource - let path = pathUtil.join(this.currentPath, fileName) - - if (this.isPersonalLocation) { - path = buildWebDavFilesPath(this.user.id, path) - resource = await this.$client.files.fileInfo(path, DavProperties.Default) - } else if (this.isSpacesProjectLocation) { - path = buildWebDavSpacesPath(this.$route.params.spaceId, path) - resource = await this.$client.files.fileInfo(path, DavProperties.Default) - } else { - resource = await this.$client.publicFiles.getFileInfo( - path, - this.publicLinkPassword, - DavProperties.PublicLink - ) - } - resource = buildResource(resource) - this.$_fileActions_triggerDefaultAction(resource) - this.UPSERT_RESOURCE(resource) - this.hideModal() - - if (this.isPersonalLocation) { - this.loadIndicators({ - client: this.$client, - currentFolder: this.currentFolder.path - }) - } - this.showMessage({ - title: this.$gettextInterpolate(this.$gettext('"%{fileName}" was created successfully'), { - fileName - }) - }) - } catch (error) { - console.error(error) - this.showMessage({ - title: this.$gettext('Failed to create file'), - status: 'danger' - }) - } - }, - checkNewFileName(fileName) { - if (fileName === '') { - return this.$gettext('File name cannot be empty') - } - - if (/[/]/.test(fileName)) { - return this.$gettext('File name cannot contain "/"') - } - - if (fileName === '.') { - return this.$gettext('File name cannot be equal to "."') - } - - if (fileName === '..') { - return this.$gettext('File name cannot be equal to ".."') - } - - if (/\s+$/.test(fileName)) { - return this.$gettext('File name cannot end with whitespace') - } - - const exists = this.files.find((file) => file.name === fileName) - - if (exists) { - const translated = this.$gettext('%{name} already exists') - return this.$gettextInterpolate(translated, { name: fileName }, true) - } - - return null - }, async onFileSuccess(event, file) { try { if (file.name) { @@ -808,23 +326,6 @@ export default { display: flex; gap: var(--oc-space-small); justify-content: flex-end; - - #create-list { - li { - border: 1px solid transparent; - button { - gap: 10px; - justify-content: left; - width: 100%; - } - } - .create-list-folder { - border-bottom: 1px solid var(--oc-color-border); - } - .create-list-file button { - margin: 2px 0; - } - } } } diff --git a/packages/web-app-files/src/components/AppBar/CreateAndUpload.vue b/packages/web-app-files/src/components/AppBar/CreateAndUpload.vue new file mode 100644 index 00000000000..d95af1daff4 --- /dev/null +++ b/packages/web-app-files/src/components/AppBar/CreateAndUpload.vue @@ -0,0 +1,567 @@ + + + +