summaryrefslogtreecommitdiffstats
path: root/apps/files/src
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@protonmail.com>2023-08-22 19:32:05 +0200
committerJohn Molakvoæ <skjnldsv@protonmail.com>2023-09-26 20:15:59 +0200
commit16094c7db52253a0875eaab31ae820efa6c1a386 (patch)
tree774fb0b5cd400320ac1b5c0f941d06bd47a6c430 /apps/files/src
parent4621198744c69ebe0351144e23e0fdc2deb4488d (diff)
downloadnextcloud-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.vue77
-rw-r--r--apps/files/src/store/dragging.ts46
-rw-r--r--apps/files/src/types.ts6
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[]
+}
+