aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/src/components/DragAndDropNotice.vue
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files/src/components/DragAndDropNotice.vue')
-rw-r--r--apps/files/src/components/DragAndDropNotice.vue157
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;
}