diff --git a/packages/web-app-files/src/components/AppBar/AppBar.vue b/packages/web-app-files/src/components/AppBar/AppBar.vue index 22ae9d41c30..5ec5ab62a30 100644 --- a/packages/web-app-files/src/components/AppBar/AppBar.vue +++ b/packages/web-app-files/src/components/AppBar/AppBar.vue @@ -127,12 +127,14 @@ import { mapActions, mapGetters, mapState, mapMutations } from 'vuex' import pathUtil from 'path' import isEmpty from 'lodash-es/isEmpty' + import Mixins from '../../mixins' import MixinFileActions, { EDITOR_MODE_CREATE } from '../../mixins/fileActions' import MixinRoutes from '../../mixins/routes' import MixinScrollToResource from '../../mixins/filesListScrolling' import { buildResource } from '../../helpers/resources' import { bus } from 'web-pkg/src/instance' + import BatchActions from './SelectedResources/BatchActions.vue' import FileDrop from './Upload/FileDrop.vue' import FileUpload from './Upload/FileUpload.vue' @@ -140,6 +142,7 @@ 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' + export default { components: { BatchActions, @@ -149,6 +152,7 @@ export default { SizeInfo, ViewOptions }, + mixins: [Mixins, MixinFileActions, MixinRoutes, MixinScrollToResource], data: () => ({ newFileAction: null, @@ -182,6 +186,7 @@ export default { } return this.$gettext('Add files or folders') }, + currentPath() { const path = this.$route.params.item || '' if (path.endsWith('/')) { @@ -189,6 +194,7 @@ export default { } return path + '/' }, + currentPathSegments() { // remove potential leading and trailing slash from current path (so that the resulting array doesn't start with an empty string) const s = this.currentPath.replace(/^\/+/, '').replace(/\/+$/, '') @@ -197,31 +203,38 @@ export default { headers() { if (this.publicPage()) { const password = this.publicLinkPassword + if (password) { return { Authorization: 'Basic ' + Buffer.from('public:' + password).toString('base64') } } + return {} } return { Authorization: 'Bearer ' + this.getToken } }, + canUpload() { if (this.currentFolder === null) { return false } return this.currentFolder.canUpload() }, + showActions() { return this.$route.meta.hideFilelistActions !== true }, + showBreadcrumb() { return this.isPublicFilesRoute || this.isPersonalRoute }, + pageTitle() { const title = this.$route.meta.title return this.$gettext(title) }, + breadcrumbs() { const pathSegments = this.currentPathSegments const breadcrumbs = [] @@ -233,6 +246,7 @@ export default { breadcrumbs.push({ text: this.$gettext('All files') }) + pathSegments.length < 1 ? (breadcrumbs[0].onClick = () => bus.emit('app.files.list.load', this.$route.params.item)) @@ -245,23 +259,29 @@ export default { to: baseUrl + encodeURIComponent(pathUtil.join(...pathItems)) }) } + for (let i = 0; i < pathSegments.length; i++) { pathItems.push(pathSegments[i]) const to = baseUrl + encodeURIComponent(pathUtil.join(...pathItems)) + if (i === pathSegments.length - 1) { breadcrumbs.push({ text: pathSegments[i], onClick: () => bus.emit('app.files.list.load', this.$route.params.item) }) + continue } + breadcrumbs.push({ text: pathSegments[i], to: i + 1 === pathSegments.length ? null : to }) } + return breadcrumbs }, + hasFreeSpace() { return ( !this.quota || @@ -272,12 +292,15 @@ export default { this.publicPage() ) }, + areDefaultActionsVisible() { return this.selectedFiles.length < 1 }, + isNewBtnDisabled() { return !this.canUpload || !this.hasFreeSpace }, + selectedResourcesAnnouncement() { if (this.selectedFiles.length === 0) { return this.$gettext('No items selected.') @@ -290,6 +313,7 @@ export default { return this.$gettextInterpolate(translated, { amount: this.selectedFiles.length }) } }, + created() { // Storage returns a string so we need to convert it into a boolean const areHiddenFilesShown = window.localStorage.getItem('oc_hiddenFilesShown') || 'true' @@ -309,6 +333,7 @@ export default { ...mapActions(['openFile', 'showMessage', 'createModal', 'setModalInputErrorMessage']), ...mapMutations('Files', ['UPSERT_RESOURCE', 'SET_HIDDEN_FILES_VISIBILITY']), ...mapMutations(['SET_QUOTA']), + showCreateResourceModal( isFolder = true, ext = 'txt', @@ -323,10 +348,12 @@ export default { 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'), @@ -348,13 +375,17 @@ export default { } this.createModal(modal) }, + async addNewFolder(folderName) { if (folderName === '') { return } + this.fileFolderCreationLoading = true + try { const path = pathUtil.join(this.currentPath, folderName) + let resource if (this.isPersonalRoute) { await this.$client.files.createFolder(path) @@ -368,14 +399,17 @@ export default { ) } resource = buildResource(resource) + this.UPSERT_RESOURCE(resource) this.hideModal() + if (this.isPersonalRoute) { this.loadIndicators({ client: this.$client, currentFolder: this.currentFolder.path }) } + setTimeout(() => { this.setFileSelection([resource]) this.scrollToResource(resource) @@ -387,36 +421,48 @@ export default { 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 { const path = pathUtil.join(this.currentPath, fileName) let resource @@ -431,21 +477,27 @@ export default { DavProperties.Default ) } + 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.isPersonalRoute) { this.loadIndicators({ client: this.$client, currentFolder: this.currentFolder.path }) } + setTimeout(() => { this.setFileSelection([resource]) this.scrollToResource(resource) @@ -457,8 +509,10 @@ export default { status: 'danger' }) } + this.fileFolderCreationLoading = false }, + async createNewFile(fileName) { try { const path = pathUtil.join(this.currentPath, fileName) @@ -499,35 +553,46 @@ export default { }) } }, + 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') + ret + urn 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) { file = file.name } + await this.$nextTick() + const path = pathUtil.join(this.currentPath, file) let resource = this.isPersonalRoute ? await this.$client.files.fileInfo(path, DavProperties.Default) @@ -536,8 +601,10 @@ export default { this.publicLinkPassword, DavProperties.Default ) + resource = buildResource(resource) this.UPSERT_RESOURCE(resource) + if (this.isPersonalRoute) { this.loadIndicators({ client: this.$client, @@ -545,12 +612,15 @@ export default { encodePath: this.encodePath }) } + const user = await this.$client.users.getUser(this.user.id) + this.SET_QUOTA(user.quota) } catch (error) { console.error(error) } }, + onFileError(error) { this.showMessage({ title: this.$gettext('File upload failed…'), @@ -558,6 +628,7 @@ export default { status: 'danger' }) }, + onFileProgress(progress) { this.updateFileProgress(progress) } @@ -570,6 +641,7 @@ export default { background-color: var(--oc-color-background-default); box-sizing: border-box; z-index: 2; + &-actions { align-items: center; display: flex;