diff options
author | John Molakvoæ <skjnldsv@protonmail.com> | 2023-08-22 19:32:05 +0200 |
---|---|---|
committer | John Molakvoæ <skjnldsv@protonmail.com> | 2023-09-26 20:15:59 +0200 |
commit | 16094c7db52253a0875eaab31ae820efa6c1a386 (patch) | |
tree | 774fb0b5cd400320ac1b5c0f941d06bd47a6c430 /apps/files/src | |
parent | 4621198744c69ebe0351144e23e0fdc2deb4488d (diff) | |
download | nextcloud-server-16094c7db52253a0875eaab31ae820efa6c1a386.tar.gz nextcloud-server-16094c7db52253a0875eaab31ae820efa6c1a386.zip |
feat(files): add drag and drop support
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
Diffstat (limited to 'apps/files/src')
-rw-r--r-- | apps/files/src/components/FileEntry.vue | 77 | ||||
-rw-r--r-- | apps/files/src/store/dragging.ts | 46 | ||||
-rw-r--r-- | apps/files/src/types.ts | 6 |
3 files changed, 125 insertions, 4 deletions
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 @@ <!-- Icon or preview --> <span class="files-list__row-icon" @click="execDefaultAction"> <template v-if="source.type === 'folder'"> - <FolderIcon /> - <OverlayIcon :is="folderOverlay" - v-if="folderOverlay" - class="files-list__row-icon-overlay" /> + <FolderOpenIcon v-if="dragover" /> + <template v-else> + <FolderIcon /> + <OverlayIcon :is="folderOverlay" + v-if="folderOverlay" + class="files-list__row-icon-overlay" /> + </template> </template> <!-- Decorative image, should not be aria documented --> @@ -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æ <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', []) + }, + }, +}) 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[] +} + |