diff options
Diffstat (limited to 'apps/files/src/components/DragAndDropNotice.vue')
-rw-r--r-- | apps/files/src/components/DragAndDropNotice.vue | 157 |
1 files changed, 99 insertions, 58 deletions
diff --git a/apps/files/src/components/DragAndDropNotice.vue b/apps/files/src/components/DragAndDropNotice.vue index 22de0f662de..c7684d5c205 100644 --- a/apps/files/src/components/DragAndDropNotice.vue +++ b/apps/files/src/components/DragAndDropNotice.vue @@ -1,27 +1,10 @@ <!-- - - @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com> - - - - @author John Molakvoæ <skjnldsv@protonmail.com> - - @author Ferdinand Thiessen <opensource@fthiessen.de> - - - - @license AGPL-3.0-or-later - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU Affero General Public License as - - published by the Free Software Foundation, either version 3 of the - - License, or (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU Affero General Public License for more details. - - - - You should have received a copy of the GNU Affero General Public License - - along with this program. If not, see <http://www.gnu.org/licenses/>. - - - --> + - SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> <template> <div v-show="dragover" + data-cy-files-drag-drop-area class="files-list__drag-drop-notice" @drop="onDrop"> <div class="files-list__drag-drop-notice-wrapper"> @@ -43,15 +26,21 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue' -import { Folder, Permission } from '@nextcloud/files' -import { showError, showSuccess } from '@nextcloud/dialogs' +import type { Folder } from '@nextcloud/files' + +import { Permission } from '@nextcloud/files' +import { showError } from '@nextcloud/dialogs' import { translate as t } from '@nextcloud/l10n' +import { UploadStatus } from '@nextcloud/upload' +import { defineComponent, type PropType } from 'vue' +import debounce from 'debounce' import TrayArrowDownIcon from 'vue-material-design-icons/TrayArrowDown.vue' -import logger from '../logger.js' -import { handleDrop } from '../services/DropService' +import { useNavigation } from '../composables/useNavigation' +import { dataTransferToFileTree, onDropExternalFiles } from '../services/DropService' +import logger from '../logger.ts' +import type { RawLocation } from 'vue-router' export default defineComponent({ name: 'DragAndDropNotice', @@ -62,11 +51,19 @@ export default defineComponent({ props: { currentFolder: { - type: Folder, + type: Object as PropType<Folder>, required: true, }, }, + setup() { + const { currentView } = useNavigation() + + return { + currentView, + } + }, + data() { return { dragover: false, @@ -88,22 +85,33 @@ export default defineComponent({ if (this.isQuotaExceeded) { return this.t('files', 'Your have used your space quota and cannot upload files anymore') } else if (!this.canUpload) { - return this.t('files', 'You don’t have permission to upload or create files here') + return this.t('files', 'You do not have permission to upload or create files here.') } return null }, + + /** + * Debounced function to reset the drag over state + * Required as Firefox has a bug where no dragleave is emitted: + * https://bugzilla.mozilla.org/show_bug.cgi?id=656164 + */ + resetDragOver() { + return debounce(() => { + this.dragover = false + }, 3000) + }, }, mounted() { // Add events on parent to cover both the table and DragAndDrop notice - const mainContent = window.document.querySelector('main.app-content') as HTMLElement + const mainContent = window.document.getElementById('app-content-vue') as HTMLElement mainContent.addEventListener('dragover', this.onDragOver) mainContent.addEventListener('dragleave', this.onDragLeave) mainContent.addEventListener('drop', this.onContentDrop) }, beforeDestroy() { - const mainContent = window.document.querySelector('main.app-content') as HTMLElement + const mainContent = window.document.getElementById('app-content-vue') as HTMLElement mainContent.removeEventListener('dragover', this.onDragOver) mainContent.removeEventListener('dragleave', this.onDragLeave) mainContent.removeEventListener('drop', this.onContentDrop) @@ -118,6 +126,7 @@ export default defineComponent({ if (isForeignFile) { // Only handle uploading of outside files (not Nextcloud files) this.dragover = true + this.resetDragOver() } }, @@ -126,12 +135,13 @@ export default defineComponent({ // only when we're leaving the current element // Avoid flickering const currentTarget = event.currentTarget as HTMLElement - if (currentTarget?.contains(event.relatedTarget as HTMLElement)) { + if (currentTarget?.contains((event.relatedTarget ?? event.target) as HTMLElement)) { return } if (this.dragover) { this.dragover = false + this.resetDragOver.clear() } }, @@ -140,13 +150,13 @@ export default defineComponent({ event.preventDefault() if (this.dragover) { this.dragover = false + this.resetDragOver.clear() } }, - onDrop(event: DragEvent) { - logger.debug('Dropped on DragAndDropNotice', { event, error: this.cantUploadLabel }) - - if (!this.canUpload || this.isQuotaExceeded) { + async onDrop(event: DragEvent) { + // cantUploadLabel is null if we can upload + if (this.cantUploadLabel) { showError(this.cantUploadLabel) return } @@ -158,30 +168,61 @@ export default defineComponent({ event.preventDefault() event.stopPropagation() - if (event.dataTransfer && event.dataTransfer.items.length > 0) { - // Start upload - logger.debug(`Uploading files to ${this.currentFolder.path}`) - // Process finished uploads - handleDrop(event.dataTransfer).then((uploads) => { - logger.debug('Upload terminated', { uploads }) - showSuccess(t('files', 'Upload successful')) - - // Scroll to last upload in current directory if terminated - const lastUpload = uploads.findLast((upload) => !upload.file.webkitRelativePath.includes('/') && upload.response?.headers?.['oc-fileid']) - if (lastUpload !== undefined) { - this.$router.push({ - ...this.$route, - params: { - view: this.$route.params?.view ?? 'files', - // Remove instanceid from header response - fileid: parseInt(lastUpload.response!.headers['oc-fileid']), - }, - }) - } - }) + // Caching the selection + const items: DataTransferItem[] = [...event.dataTransfer?.items || []] + + // We need to process the dataTransfer ASAP before the + // browser clears it. This is why we cache the items too. + const fileTree = await dataTransferToFileTree(items) + + // We might not have the target directory fetched yet + const contents = await this.currentView?.getContents(this.currentFolder.path) + const folder = contents?.folder + if (!folder) { + showError(this.t('files', 'Target folder does not exist any more')) + return + } + + // If another button is pressed, cancel it. This + // allows cancelling the drag with the right click. + if (event.button) { + return + } + + logger.debug('Dropped', { event, folder, fileTree }) + + // Check whether we're uploading files + const uploads = await onDropExternalFiles(fileTree, folder, contents.contents) + + // Scroll to last successful upload in current directory if terminated + const lastUpload = uploads.findLast((upload) => upload.status !== UploadStatus.FAILED + && !upload.file.webkitRelativePath.includes('/') + && upload.response?.headers?.['oc-fileid'] + // Only use the last ID if it's in the current folder + && upload.source.replace(folder.source, '').split('/').length === 2) + + if (lastUpload !== undefined) { + logger.debug('Scrolling to last upload in current folder', { lastUpload }) + const location: RawLocation = { + path: this.$route.path, + // Keep params but change file id + params: { + ...this.$route.params, + fileid: String(lastUpload.response!.headers['oc-fileid']), + }, + query: { + ...this.$route.query, + }, + } + // Remove open file from query + delete location.query.openfile + this.$router.push(location) } + this.dragover = false + this.resetDragOver.clear() }, + t, }, }) @@ -194,7 +235,7 @@ export default defineComponent({ justify-content: center; width: 100%; // Breadcrumbs height + row thead height - min-height: calc(58px + 55px); + min-height: calc(58px + 44px); margin: 0; user-select: none; color: var(--color-text-maxcontrast); @@ -202,7 +243,7 @@ export default defineComponent({ border-color: black; h3 { - margin-left: 16px; + margin-inline-start: 16px; color: inherit; } |