]> source.dussan.org Git - nextcloud-server.git/commitdiff
fix(files): drag and drop events chain and cancel
authorJohn Molakvoæ <skjnldsv@protonmail.com>
Wed, 20 Dec 2023 08:25:04 +0000 (09:25 +0100)
committerFerdinand Thiessen <opensource@fthiessen.de>
Sun, 24 Dec 2023 00:21:49 +0000 (01:21 +0100)
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
apps/files/src/components/DragAndDropNotice.vue
apps/files/src/components/FilesListVirtual.vue
apps/files/src/services/DropService.ts

index 66cddcaff977a8175d5026fed1d8d7aa7ad2e1aa..df03713601d106a24c616c69d28de14a9d281bd0 100644 (file)
                class="files-list__drag-drop-notice"
                @drop="onDrop">
                <div class="files-list__drag-drop-notice-wrapper">
-                       <TrayArrowDownIcon :size="48" />
-                       <h3 class="files-list-drag-drop-notice__title">
-                               {{ t('files', 'Drag and drop files here to upload') }}
-                       </h3>
+                       <template v-if="canUpload && !isQuotaExceeded">
+                               <TrayArrowDownIcon :size="48" />
+                               <h3 class="files-list-drag-drop-notice__title">
+                                       {{ t('files', 'Drag and drop files here to upload') }}
+                               </h3>
+                       </template>
+
+                       <!-- Not permitted to drop files here -->
+                       <template v-else>
+                               <h3 class="files-list-drag-drop-notice__title">
+                                       {{ cantUploadLabel }}
+                               </h3>
+                       </template>
                </div>
        </div>
 </template>
 
 <script lang="ts">
-import { translate as t } from '@nextcloud/l10n'
 import { defineComponent } from 'vue'
+import { Folder, Permission } from '@nextcloud/files'
+import { showError, showSuccess } from '@nextcloud/dialogs'
+import { translate as t } from '@nextcloud/l10n'
 
 import TrayArrowDownIcon from 'vue-material-design-icons/TrayArrowDown.vue'
 
 import logger from '../logger.js'
 import { handleDrop } from '../services/DropService'
-import { showSuccess } from '@nextcloud/dialogs'
 
 export default defineComponent({
        name: 'DragAndDropNotice',
@@ -52,7 +62,7 @@ export default defineComponent({
 
        props: {
                currentFolder: {
-                       type: Object,
+                       type: Folder,
                        required: true,
                },
        },
@@ -63,35 +73,86 @@ export default defineComponent({
                }
        },
 
+       computed: {
+               /**
+                * Check if the current folder has create permissions
+                */
+               canUpload() {
+                       return this.currentFolder && (this.currentFolder.permissions & Permission.CREATE) !== 0
+               },
+               isQuotaExceeded() {
+                       return this.currentFolder?.attributes?.['quota-available-bytes'] === 0
+               },
+
+               cantUploadLabel() {
+                       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 null
+               },
+       },
+
        mounted() {
                // Add events on parent to cover both the table and DragAndDrop notice
                const mainContent = window.document.querySelector('main.app-content') 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
                mainContent.removeEventListener('dragover', this.onDragOver)
                mainContent.removeEventListener('dragleave', this.onDragLeave)
+               mainContent.removeEventListener('drop', this.onContentDrop)
        },
 
        methods: {
                onDragOver(event: DragEvent) {
+                       // Needed to keep the drag/drop events chain working
+                       event.preventDefault()
+
                        const isForeignFile = event.dataTransfer?.types.includes('Files')
+
+                       logger.debug('Drag over DragAndDropNotice', { isForeignFile, event })
                        if (isForeignFile) {
-                               // Only handle uploading
+                               // Only handle uploading of outside files (not Nextcloud files)
                                this.dragover = true
                        }
                },
 
-               onDragLeave(/* event: DragEvent */) {
+               onDragLeave(event: DragEvent) {
+                       // Counter bubbling, make sure we're ending the drag
+                       // only when we're leaving the current element
+                       // Avoid flickering
+                       const currentTarget = event.currentTarget as HTMLElement
+                       if (currentTarget?.contains(event.relatedTarget as HTMLElement)) {
+                               return
+                       }
+
+                       if (this.dragover) {
+                               this.dragover = false
+                       }
+               },
+
+               onContentDrop(event: DragEvent) {
+                       logger.debug('Drag and drop cancelled, dropped on empty space', { event })
+                       event.preventDefault()
                        if (this.dragover) {
                                this.dragover = false
                        }
                },
 
                onDrop(event: DragEvent) {
+                       logger.debug('Dropped on DragAndDropNotice', { event, error: this.cantUploadLabel })
+
+                       if (!this.canUpload || this.isQuotaExceeded) {
+                               showError(this.cantUploadLabel)
+                               return
+                       }
+
                        if (this.$el.querySelector('tbody')?.contains(event.target as Node)) {
                                return
                        }
index 7c8a867b0d4013cd17239604e2c5f1eb0573a7d1..8a80015bc7cd61bd1d05c8e90a75ab9270483fe6 100644 (file)
@@ -128,7 +128,6 @@ export default defineComponent({
                        FileEntryGrid,
                        headers: getFileListHeaders(),
                        scrollToIndex: 0,
-                       dndNoticeHeight: 0,
                }
        },
 
@@ -259,7 +258,10 @@ export default defineComponent({
                onDragOver(event: DragEvent) {
                        // Detect if we're only dragging existing files or not
                        const isForeignFile = event.dataTransfer?.types.includes('Files')
+
                        if (isForeignFile) {
+                               // Only handle uploading of existing Nextcloud files
+                               // See DragAndDropNotice for handling of foreign files
                                return
                        }
 
index 075c5c397c77646df5e12ca16f4b67aaa4cd33e4..372b849bcc4ec09e1df0cbd02671ef60f6ecd553 100644 (file)
 import type { Upload } from '@nextcloud/upload'
 import type { FileStat, ResponseDataDetailed } from 'webdav'
 
-import { showError } from '@nextcloud/dialogs'
-import { emit } from '@nextcloud/event-bus'
 import { davGetClient, davGetDefaultPropfind, davResultToNode, davRootPath } from '@nextcloud/files'
-import { translate as t } from '@nextcloud/l10n'
+import { emit } from '@nextcloud/event-bus'
 import { getUploader } from '@nextcloud/upload'
-import logger from '../logger.js'
 import { joinPaths } from '@nextcloud/paths'
+import { showError } from '@nextcloud/dialogs'
+import { translate as t } from '@nextcloud/l10n'
+
+import logger from '../logger.js'
 
 export const handleDrop = async (data: DataTransfer) => {
        // TODO: Maybe handle `getAsFileSystemHandle()` in the future