Skip to content

Commit

Permalink
feat(files): add drag and drop support
Browse files Browse the repository at this point in the history
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
  • Loading branch information
skjnldsv committed Aug 24, 2023
1 parent c7bd5ba commit f8c5371
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 3 deletions.
81 changes: 78 additions & 3 deletions apps/files/src/components/FileEntry.vue
Expand Up @@ -21,12 +21,18 @@
-->

<template>
<tr :class="{'files-list__row--visible': visible, 'files-list__row--active': isActive}"
<tr :class="{'files-list__row--visible': visible, 'files-list__row--active': dragover || isActive}"
data-cy-files-list-row
:data-cy-files-list-row-fileid="fileid"
:data-cy-files-list-row-name="source.basename"
:draggable="canDrag"
class="list__row"
@contextmenu="onRightClick">
@contextmenu="onRightClick"
@dragenter="onDragEnter"
@dragleave="onDragLeave"
@dragstart="onDragStart"
@dragend="onDragEnd"
@drop="onDrop">
<!-- Failed indicator -->
<span v-if="source.attributes.failed" class="files-list__row--failed" />

Expand All @@ -44,7 +50,10 @@
<td class="files-list__row-name" data-cy-files-list-row-name>
<!-- Icon or preview -->
<span class="files-list__row-icon" @click="execDefaultAction">
<FolderIcon v-if="source.type === 'folder'" />
<template v-if="source.type === 'folder'">
<FolderOpenIcon v-if="dragover" />
<FolderIcon v-else />
</template>

<!-- Decorative image, should not be aria documented -->
<span v-else-if="previewUrl && !backgroundFailed"
Expand Down Expand Up @@ -178,6 +187,7 @@ import { vOnClickOutside } from '@vueuse/components'
import axios from '@nextcloud/axios'
import FileIcon from 'vue-material-design-icons/File.vue'
import FolderIcon from 'vue-material-design-icons/Folder.vue'
import FolderOpenIcon from 'vue-material-design-icons/FolderOpen.vue'
import moment from '@nextcloud/moment'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
Expand All @@ -190,6 +200,7 @@ import { ACTION_DETAILS } from '../actions/sidebarAction.ts'
import { hashCode } from '../utils/hashUtils.ts'
import { isCachedPreview } from '../services/PreviewService.ts'
import { useActionsMenuStore } from '../store/actionsmenu.ts'
import { useDragAndDropStore } from '../store/dragging.ts'
import { useFilesStore } from '../store/files.ts'
import { useKeyboardStore } from '../store/keyboard.ts'
import { useRenamingStore } from '../store/renaming.ts'
Expand All @@ -214,6 +225,7 @@ export default Vue.extend({
FavoriteIcon,
FileIcon,
FolderIcon,
FolderOpenIcon,
NcActionButton,
NcActions,
NcCheckboxRadioSwitch,
Expand Down Expand Up @@ -254,13 +266,15 @@ export default Vue.extend({
setup() {
const actionsMenuStore = useActionsMenuStore()
const draggingStore = useDragAndDropStore()
const filesStore = useFilesStore()
const keyboardStore = useKeyboardStore()
const renamingStore = useRenamingStore()
const selectionStore = useSelectionStore()
const userConfigStore = useUserConfigStore()
return {
actionsMenuStore,
draggingStore,
filesStore,
keyboardStore,
renamingStore,
Expand All @@ -274,6 +288,7 @@ export default Vue.extend({
backgroundFailed: false,
backgroundImage: '',
loading: '',
dragover: false,
}
},
Expand Down Expand Up @@ -383,6 +398,9 @@ export default Vue.extend({
}
},
draggingFiles() {
return this.draggingStore.dragging
},
selectedFiles() {
return this.selectionStore.selected
},
Expand Down Expand Up @@ -497,6 +515,23 @@ export default Vue.extend({
isActive() {
return this.fileid === this.currentFileId?.toString?.()
},
canDrag() {
return (this.source.permissions & Permission.UPDATE) !== 0
},
canDrop() {
if (this.source.type !== FileType.Folder) {
return false
}
// If the current folder is also being dragged, we can't drop it on itself
if (this.draggingFiles.find(fileId => fileId === this.fileid)) {
return false
}
return (this.source.permissions & Permission.CREATE) !== 0
},
},
watch: {
Expand Down Expand Up @@ -843,6 +878,46 @@ export default Vue.extend({
return document.querySelector('.app-content > .files-list')
},
onDragEnter() {
this.dragover = this.canDrop
},
onDragLeave() {
this.dragover = false
},
onDragStart(event) {
if (!this.canDrag) {
event.preventDefault()
event.stopPropagation()
return
}
logger.debug('Drag started')
// Dragging set of files
if (this.selectedFiles.length > 0) {
this.draggingStore.set(this.selectedFiles)
return
}
this.draggingStore.set([this.fileid])
},
onDragEnd() {
this.draggingStore.reset()
this.dragover = false
logger.debug('Drag ended')
},
onDrop(event) {
// If another button is pressed, cancel it
// This allows cancelling the drag with the right click
if (!this.canDrop || event.button !== 0) {
return
}
logger.debug('Dropped', { event, selection: this.draggingFiles })
},
t: translate,
formatFileSize,
},
Expand Down
46 changes: 46 additions & 0 deletions apps/files/src/store/dragging.ts
@@ -0,0 +1,46 @@
/**
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @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/>.
*
*/
import { defineStore } from 'pinia'
import Vue from 'vue'
import type { FileId, DragAndDropStore } from '../types'

export const useDragAndDropStore = defineStore('dragging', {
state: () => ({
dragging: [],
} as DragAndDropStore),

actions: {
/**
* Set the selection of fileIds
*/
set(selection = [] as FileId[]) {
Vue.set(this, 'dragging', selection)
},

/**
* Reset the selection
*/
reset() {
Vue.set(this, 'dragging', [])
},
},
})
5 changes: 5 additions & 0 deletions apps/files/src/types.ts
Expand Up @@ -100,3 +100,8 @@ export interface RenamingStore {
renamingNode?: Node
newName: string
}

// Drag and drop store
export interface DragAndDropStore {
dragging: FileId[]
}

0 comments on commit f8c5371

Please sign in to comment.