Skip to content

Commit

Permalink
fix(files): drag and drop events chain and cancel
Browse files Browse the repository at this point in the history
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
  • Loading branch information
skjnldsv committed Dec 20, 2023
1 parent 1184902 commit 8025f26
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 14 deletions.
76 changes: 67 additions & 9 deletions apps/files/src/components/DragAndDropNotice.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,33 @@
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',
Expand All @@ -52,7 +62,7 @@ export default defineComponent({

props: {
currentFolder: {
type: Object,
type: Folder,
required: true,
},
},
Expand All @@ -63,35 +73,83 @@ 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')
}
return this.t('files', 'You don’t have permission to upload or create files here')
},
},

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) {
if (!this.canUpload || this.isQuotaExceeded) {
showError(this.cantUploadLabel)
return
}

logger.debug('Dropped on DragAndDropNotice', { event })
if (this.$el.querySelector('tbody')?.contains(event.target as Node)) {
return
}
Expand Down
5 changes: 4 additions & 1 deletion apps/files/src/components/FilesListVirtual.vue
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ export default defineComponent({
FileEntryGrid,
headers: getFileListHeaders(),
scrollToIndex: 0,
dndNoticeHeight: 0,
}
},

Expand Down Expand Up @@ -259,7 +258,11 @@ export default defineComponent({
onDragOver(event: DragEvent) {
// Detect if we're only dragging existing files or not
const isForeignFile = event.dataTransfer?.types.includes('Files')

console.debug('Drag over FilesListVirtual', { isForeignFile, event })
if (isForeignFile) {
// Only handle uploading of existing Nextcloud files
// See DragAndDropNotice for handling of foreign files
return
}

Expand Down
9 changes: 5 additions & 4 deletions apps/files/src/services/DropService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@
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
Expand Down

0 comments on commit 8025f26

Please sign in to comment.