Skip to content

Commit

Permalink
feat: improve drag & drop support (#1204)
Browse files Browse the repository at this point in the history
Signed-off-by: Pedro Lamas <[email protected]>
  • Loading branch information
pedrolamas authored Oct 9, 2023
1 parent 3566723 commit b860ca0
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 90 deletions.
23 changes: 16 additions & 7 deletions src/components/widgets/filesystem/FileSystem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
:max-height="maxHeight"
:class="{ 'no-pointer-events': dragState.overlay }"
flat
@dragenter.capture.prevent="handleDragEnter"
@dragover.prevent
@dragover="handleDragOver"
@dragenter.self.prevent
@dragleave.self.prevent="handleDragLeave"
@drop.prevent.stop="handleDropFile"
@drop.self.prevent="handleDropFile"
>
<file-system-toolbar
v-if="selected.length <= 0"
Expand Down Expand Up @@ -40,6 +40,7 @@

<file-system-browser
v-if="headers"
v-model="selected"
:headers="visibleHeaders"
:root="currentRoot"
:dense="dense"
Expand All @@ -49,7 +50,6 @@
:files="files"
:drag-state.sync="dragState.browserState"
:bulk-actions="bulkActions"
:selected.sync="selected"
:large-thumbnails="currentRoot === 'timelapse'"
@row-click="handleRowClick"
@move="handleMove"
Expand Down Expand Up @@ -814,7 +814,7 @@ export default class FileSystem extends Mixins(StateMixin, FilesMixin, ServicesM
jobs: files.map(file => file.name)
}
dataTransfer.setData('jobs', JSON.stringify(data))
dataTransfer.setData('x-fluidd-jobs', JSON.stringify(data))
}
}
}
Expand Down Expand Up @@ -945,9 +945,18 @@ export default class FileSystem extends Mixins(StateMixin, FilesMixin, ServicesM
* Drag handling.
* ===========================================================================
*/
handleDragEnter (e: DragEvent) {
if (!this.rootProperties.readonly && !this.dragState.browserState && e.dataTransfer && hasFilesInDataTransfer(e.dataTransfer)) {
handleDragOver (e: DragEvent) {
if (
!this.rootProperties.readonly &&
!this.dragState.browserState &&
e.dataTransfer &&
hasFilesInDataTransfer(e.dataTransfer)
) {
e.preventDefault()
this.dragState.overlay = true
e.dataTransfer.dropEffect = 'copy'
}
}
Expand Down
155 changes: 82 additions & 73 deletions src/components/widgets/filesystem/FileSystemBrowser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</div> -->

<v-data-table
v-model="selectedItems"
:value="selected"
:headers="headers"
:items="files"
:dense="dense"
Expand All @@ -26,13 +26,10 @@
class="rounded-0"
fixed-header
@input="handleSelected"
@item-selected="handleItemSelected"
>
<template #item="{ item, isSelected, select }">
<tr
:class="{
'is-directory': (item.type === 'directory'),
'is-file': (item.type === 'file'),
'is-disabled': disabled,
'v-data-table__selected': (isSelected && item.name !== '..')
}"
Expand All @@ -42,9 +39,10 @@
@contextmenu.prevent="$emit('row-click', item, $event)"
@dragstart="handleDragStart(item, $event)"
@dragend="handleDragEnd"
@drop.prevent.stop="handleDrop(item, $event)"
@dragover.prevent="handleDragOver($event)"
@dragleave.prevent="handleDragLeave($event)"
@dragover="handleDragOver(item, $event)"
@dragenter.prevent
@dragleave.prevent="handleDragLeave"
@drop.prevent="handleDrop(item, $event)"
>
<td v-if="bulkActions">
<v-simple-checkbox
Expand Down Expand Up @@ -282,7 +280,7 @@
</template>

<script lang="ts">
import { Component, Prop, Mixins, Watch } from 'vue-property-decorator'
import { Component, Prop, Mixins, VModel } from 'vue-property-decorator'
import { FileBrowserEntry, RootProperties } from '@/store/files/types'
import { AppTableHeader } from '@/types'
import FilesMixin from '@/mixins/files'
Expand All @@ -296,6 +294,9 @@ import { SupportedImageFormats, SupportedVideoFormats } from '@/globals'
}
})
export default class FileSystemBrowser extends Mixins(FilesMixin) {
@VModel({ type: Array, required: true })
selected!: FileBrowserEntry[]
@Prop({ type: String, required: true })
readonly root!: string
Expand Down Expand Up @@ -324,12 +325,8 @@ export default class FileSystemBrowser extends Mixins(FilesMixin) {
@Prop({ type: Boolean, default: false })
readonly bulkActions!: boolean
@Prop({ type: Array, required: true })
readonly selected!: FileBrowserEntry[]
dragItem: FileBrowserEntry | null = null
ghost: HTMLDivElement | undefined = undefined
selectedItems: FileBrowserEntry[] = []
// Is the history component enabled
get showHistory () {
Expand Down Expand Up @@ -357,44 +354,36 @@ export default class FileSystemBrowser extends Mixins(FilesMixin) {
return this.$store.state.config.uiSettings.general.textSortOrder
}
customSort (items: FileBrowserEntry[], sortBy: string[], sortDesc: boolean[], locale: string) {
return this.$filters.fileSystemSort(items, sortBy, sortDesc, locale, this.textSortOrder)
}
get draggedItems () {
if (this.dragItem) {
const filteredSelectedItems = this.selected
.filter(item => item.name !== '..')
mounted () {
this.selectedItems = this.selected
}
const draggedItems = filteredSelectedItems.length > 0
? filteredSelectedItems
: [this.dragItem]
return draggedItems
}
// Make sure we update the selected items if it's changed.
@Watch('selected')
onSelected (selected: FileBrowserEntry[]) {
this.selectedItems = selected
return []
}
// When the selected items change, update the parent.
handleSelected (selected: FileBrowserEntry[]) {
this.$emit('update:selected', selected)
customSort (items: FileBrowserEntry[], sortBy: string[], sortDesc: boolean[], locale: string) {
return this.$filters.fileSystemSort(items, sortBy, sortDesc, locale, this.textSortOrder)
}
// We ignore our [..] dir, so handle faking our checkbox states.
handleItemSelected (item: { item: FileBrowserEntry; value: boolean }) {
// If last two, and filtered results in 0 - set to 0.
if (
!item.value &&
this.selectedItems.length <= 2 &&
this.selectedItems.filter(fileOrFolder => (fileOrFolder.name !== '..' && item.item !== fileOrFolder)).length === 0
) {
this.selectedItems = []
handleSelected (selected: FileBrowserEntry[]) {
if (selected.length === 1) {
if (selected[0].name === '..') {
selected = []
} else {
selected = this.files
.filter(item => item.name === selected[0].name || item.name === '..')
}
}
// If top two, and filtered results in count -1, set to all.
if (
item.value &&
this.selectedItems.length + 2 >= this.files.length &&
this.selectedItems.length + 1 === this.files.filter(fileOrFolder => (fileOrFolder.name !== '..')).length
) {
this.selectedItems = this.files
}
this.$emit('input', selected)
}
getItemIcon (item: FileBrowserEntry) {
Expand Down Expand Up @@ -444,64 +433,84 @@ export default class FileSystemBrowser extends Mixins(FilesMixin) {
}
if (e.dataTransfer) {
const filteredSelectedItems = this.selected
.filter(item => (item.name !== '..'))
const draggedItems = filteredSelectedItems.length > 0
? filteredSelectedItems
: [item]
const draggedItems = this.draggedItems
this.ghost = document.createElement('div')
this.ghost.classList.add('bulk-drag')
this.ghost.classList.add((this.$vuetify.theme.dark) ? 'theme--dark' : 'theme--light')
this.ghost.innerHTML = this.$tc('app.file_system.tooltip.move_item', draggedItems.length)
document.body.appendChild(this.ghost)
e.dataTransfer.dropEffect = 'move'
e.dataTransfer.effectAllowed = 'linkMove'
e.dataTransfer.setDragImage(this.ghost, 0, 0)
const source = draggedItems
this.$emit('drag-start', source, e.dataTransfer)
if (item.type === 'file') {
const filepath = item.path ? `${this.root}/${item.path}` : this.root
const url = this.createFileUrl(item.filename, filepath)
e.dataTransfer.setData('text/html', `<A HREF="${encodeURI(url)}">${item.filename}</A>`)
e.dataTransfer.setData('text/plain', url)
e.dataTransfer.setData('text/uri-list', url)
}
this.$emit('drag-start', draggedItems, e.dataTransfer)
}
}
// File was dropped on another table row.
handleDrop (destination: FileBrowserEntry, e: DragEvent) {
handleDrop (item: FileBrowserEntry, e: DragEvent) {
this.handleDragLeave(e)
if (
destination.type === 'directory' &&
item.type === 'directory' &&
e.dataTransfer &&
this.dragItem &&
this.dragItem !== destination
this.dragItem !== item
) {
const filteredSelectedItems = this.selected
.filter(item => (item.name !== '..'))
const draggedItems = filteredSelectedItems.length > 0
? filteredSelectedItems
: [this.dragItem]
const draggedItems = this.draggedItems
if (!draggedItems.includes(destination)) {
this.$emit('move', draggedItems, destination)
if (!draggedItems.includes(item)) {
this.$emit('move', draggedItems, item)
}
}
}
// Handles highlighting rows as drag over them
handleDragOver (e: DragEvent) {
const element = e.target as HTMLElement
if (element) {
if (
element.tagName === 'TD' &&
element.parentElement?.classList.contains('is-directory')
) {
const row = element.parentElement
if (row) row.classList.add('active')
handleDragOver (item: FileBrowserEntry, e: DragEvent) {
if (
item.type === 'directory' &&
e.dataTransfer &&
this.dragItem &&
this.dragItem !== item &&
!this.draggedItems.includes(item)
) {
e.preventDefault()
e.dataTransfer.dropEffect = 'move'
let element = e.target as HTMLElement | null
while (element) {
if (element.tagName === 'TR') {
element.classList.add('active')
return
}
element = element.parentElement
}
}
}
// Handles un highlighting rows as we drag out of them.
handleDragLeave (e: DragEvent) {
const element = e.target as HTMLElement
if (element?.tagName === 'TD') {
const row = element.parentElement
if (row) row.classList.remove('active')
let element = e.target as HTMLElement | null
while (element) {
if (element.tagName === 'TR') {
element.classList.remove('active')
return
}
element = element.parentElement
}
}
Expand Down
Loading

0 comments on commit b860ca0

Please sign in to comment.