From 16094c7db52253a0875eaab31ae820efa6c1a386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6?= Date: Tue, 22 Aug 2023 19:32:05 +0200 Subject: [PATCH] feat(files): add drag and drop support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ --- apps/files/src/components/FileEntry.vue | 77 +++++++++++++++++++++++-- apps/files/src/store/dragging.ts | 46 +++++++++++++++ apps/files/src/types.ts | 6 ++ 3 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 apps/files/src/store/dragging.ts diff --git a/apps/files/src/components/FileEntry.vue b/apps/files/src/components/FileEntry.vue index 2b97e88cdc9..ede7f1fad2b 100644 --- a/apps/files/src/components/FileEntry.vue +++ b/apps/files/src/components/FileEntry.vue @@ -45,10 +45,13 @@ @@ -194,6 +197,7 @@ import Vue from 'vue' import AccountGroupIcon from 'vue-material-design-icons/AccountGroup.vue' 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 KeyIcon from 'vue-material-design-icons/Key.vue' import TagIcon from 'vue-material-design-icons/Tag.vue' import LinkIcon from 'vue-material-design-icons/Link.vue' @@ -209,6 +213,7 @@ import { action as sidebarAction } 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' @@ -235,6 +240,7 @@ export default Vue.extend({ FavoriteIcon, FileIcon, FolderIcon, + FolderOpenIcon, KeyIcon, LinkIcon, NcActionButton, @@ -279,6 +285,7 @@ export default Vue.extend({ setup() { const actionsMenuStore = useActionsMenuStore() + const draggingStore = useDragAndDropStore() const filesStore = useFilesStore() const keyboardStore = useKeyboardStore() const renamingStore = useRenamingStore() @@ -286,6 +293,7 @@ export default Vue.extend({ const userConfigStore = useUserConfigStore() return { actionsMenuStore, + draggingStore, filesStore, keyboardStore, renamingStore, @@ -299,6 +307,7 @@ export default Vue.extend({ backgroundFailed: false, backgroundImage: '', loading: '', + dragover: false, } }, @@ -445,6 +454,9 @@ export default Vue.extend({ } }, + draggingFiles() { + return this.draggingStore.dragging + }, selectedFiles() { return this.selectionStore.selected }, @@ -567,6 +579,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: { @@ -930,6 +959,46 @@ export default Vue.extend({ return action.displayName([this.source], this.currentView) }, + 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, }, diff --git a/apps/files/src/store/dragging.ts b/apps/files/src/store/dragging.ts new file mode 100644 index 00000000000..e189d552046 --- /dev/null +++ b/apps/files/src/store/dragging.ts @@ -0,0 +1,46 @@ +/** + * @copyright Copyright (c) 2023 John Molakvoæ + * + * @author John Molakvoæ + * + * @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 . + * + */ +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', []) + }, + }, +}) diff --git a/apps/files/src/types.ts b/apps/files/src/types.ts index bf9f3a09648..4ea49e9d1ac 100644 --- a/apps/files/src/types.ts +++ b/apps/files/src/types.ts @@ -106,3 +106,9 @@ export interface RenamingStore { export interface UploaderStore { queue: Upload[] } + +// Drag and drop store +export interface DragAndDropStore { + dragging: FileId[] +} +