]> source.dussan.org Git - nextcloud-server.git/commitdiff
fix(files): Allow to drag and drop new files also on empty directories
authorFerdinand Thiessen <opensource@fthiessen.de>
Thu, 23 Nov 2023 13:34:57 +0000 (14:34 +0100)
committerFerdinand Thiessen <opensource@fthiessen.de>
Mon, 27 Nov 2023 16:35:14 +0000 (17:35 +0100)
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
apps/files/src/components/DragAndDropNotice.vue
apps/files/src/components/FilesListVirtual.vue
apps/files/src/components/VirtualList.vue
apps/files/src/mixins/filesListWidth.ts
apps/files/src/views/FilesList.vue

index d5f93dac2566083fd870f8c37a9d685ba7d843f8..1e53556e1f547723b1ffd8ea57290bf59ef8baae 100644 (file)
@@ -20,8 +20,8 @@
        -
        -->
 <template>
-       <div class="files-list__drag-drop-notice"
-               :class="{ 'files-list__drag-drop-notice--dragover': dragover }"
+       <div v-show="dragover"
+               class="files-list__drag-drop-notice"
                @drop="onDrop">
                <div class="files-list__drag-drop-notice-wrapper">
                        <TrayArrowDownIcon :size="48" />
 
 <script lang="ts">
 import type { Upload } from '@nextcloud/upload'
-import { join } from 'path'
 import { showSuccess } from '@nextcloud/dialogs'
 import { translate as t } from '@nextcloud/l10n'
 import { getUploader } from '@nextcloud/upload'
-import Vue from 'vue'
+import { defineComponent } from 'vue'
 
 import TrayArrowDownIcon from 'vue-material-design-icons/TrayArrowDown.vue'
 
 import logger from '../logger.js'
 
-export default Vue.extend({
+export default defineComponent({
        name: 'DragAndDropNotice',
 
        components: {
@@ -56,16 +55,43 @@ export default Vue.extend({
                        type: Object,
                        required: true,
                },
-               dragover: {
-                       type: Boolean,
-                       default: false,
-               },
+       },
+
+       data() {
+               return {
+                       dragover: false,
+               }
+       },
+
+       mounted() {
+               // Add events on parent to cover both the table and DragAndDrop notice
+               const mainContent = window.document.querySelector('main.app-content') as HTMLElement
+               mainContent.addEventListener('dragover', this.onDragOver)
+               mainContent.addEventListener('dragleave', this.onDragLeave)
+       },
+
+       beforeDestroy() {
+               const mainContent = window.document.querySelector('main.app-content') as HTMLElement
+               mainContent.removeEventListener('dragover', this.onDragOver)
+               mainContent.removeEventListener('dragleave', this.onDragLeave)
        },
 
        methods: {
-               onDrop(event: DragEvent) {
-                       this.$emit('update:dragover', false)
+               onDragOver(event: DragEvent) {
+                       const isForeignFile = event.dataTransfer?.types.includes('Files')
+                       if (isForeignFile) {
+                               // Only handle uploading
+                               this.dragover = true
+                       }
+               },
 
+               onDragLeave(/* event: DragEvent */) {
+                       if (this.dragover) {
+                               this.dragover = false
+                       }
+               },
+
+               onDrop(event: DragEvent) {
                        if (this.$el.querySelector('tbody')?.contains(event.target as Node)) {
                                return
                        }
@@ -91,12 +117,13 @@ export default Vue.extend({
                                        // Scroll to last upload if terminated
                                        const lastUpload = uploads[uploads.length - 1]
                                        if (lastUpload?.response?.headers?.['oc-fileid']) {
-                                               this.$router.push(Object.assign({}, this.$route, {
+                                               this.$router.push({
+                                                       ...this.$route,
                                                        params: {
                                                                // Remove instanceid from header response
                                                                fileid: parseInt(lastUpload.response?.headers?.['oc-fileid']),
                                                        },
-                                               }))
+                                               })
                                        }
                                })
                        }
@@ -108,12 +135,7 @@ export default Vue.extend({
 
 <style lang="scss" scoped>
 .files-list__drag-drop-notice {
-       position: absolute;
-       z-index: 9999;
-       top: 0;
-       right: 0;
-       left: 0;
-       display: none;
+       display: flex;
        align-items: center;
        justify-content: center;
        width: 100%;
@@ -123,11 +145,7 @@ export default Vue.extend({
        user-select: none;
        color: var(--color-text-maxcontrast);
        background-color: var(--color-main-background);
-
-       &--dragover {
-               display: flex;
-               border-color: black;
-       }
+       border-color: black;
 
        h3 {
                margin-left: 16px;
@@ -144,12 +162,6 @@ export default Vue.extend({
                border: 2px var(--color-border-dark) dashed;
                border-radius: var(--border-radius-large);
        }
-
-       &__close {
-               position: absolute !important;
-               top: 10px;
-               right: 10px;
-       }
 }
 
 </style>
index 463c71d7c6d9efc3247d61ebc998aa172220aeae..0f772aedffcf5e45c6dffbfc1cb0aeb5c91804a7 100644 (file)
   -
   -->
 <template>
-       <Fragment>
-               <!-- Drag and drop notice -->
-               <DragAndDropNotice v-if="canUpload && filesListWidth >= 512"
-                       :current-folder="currentFolder"
-                       :dragover.sync="dragover"
-                       :style="{ height: dndNoticeHeight }" />
-
-               <VirtualList ref="table"
-                       :data-component="userConfig.grid_view ? FileEntryGrid : FileEntry"
-                       :data-key="'source'"
-                       :data-sources="nodes"
-                       :grid-mode="userConfig.grid_view"
-                       :extra-props="{
-                               isMtimeAvailable,
-                               isSizeAvailable,
-                               nodes,
-                               filesListWidth,
-                       }"
-                       :scroll-to-index="scrollToIndex"
-                       :caption="caption"
-                       @scroll="onScroll">
-                       <template #before>
-                               <!-- Headers -->
-                               <FilesListHeader v-for="header in sortedHeaders"
-                                       :key="header.id"
-                                       :current-folder="currentFolder"
-                                       :current-view="currentView"
-                                       :header="header" />
-                       </template>
-
-                       <!-- Thead-->
-                       <template #header>
-                               <!-- Table header and sort buttons -->
-                               <FilesListTableHeader ref="thead"
-                                       :files-list-width="filesListWidth"
-                                       :is-mtime-available="isMtimeAvailable"
-                                       :is-size-available="isSizeAvailable"
-                                       :nodes="nodes" />
-                       </template>
-
-                       <!-- Tfoot-->
-                       <template #footer>
-                               <FilesListTableFooter :files-list-width="filesListWidth"
-                                       :is-mtime-available="isMtimeAvailable"
-                                       :is-size-available="isSizeAvailable"
-                                       :nodes="nodes"
-                                       :summary="summary" />
-                       </template>
-               </VirtualList>
-       </Fragment>
+       <VirtualList ref="table"
+               :data-component="userConfig.grid_view ? FileEntryGrid : FileEntry"
+               :data-key="'source'"
+               :data-sources="nodes"
+               :grid-mode="userConfig.grid_view"
+               :extra-props="{
+                       isMtimeAvailable,
+                       isSizeAvailable,
+                       nodes,
+                       filesListWidth,
+               }"
+               :scroll-to-index="scrollToIndex"
+               :caption="caption">
+               <template #before>
+                       <!-- Headers -->
+                       <FilesListHeader v-for="header in sortedHeaders"
+                               :key="header.id"
+                               :current-folder="currentFolder"
+                               :current-view="currentView"
+                               :header="header" />
+               </template>
+
+               <!-- Thead-->
+               <template #header>
+                       <!-- Table header and sort buttons -->
+                       <FilesListTableHeader ref="thead"
+                               :files-list-width="filesListWidth"
+                               :is-mtime-available="isMtimeAvailable"
+                               :is-size-available="isSizeAvailable"
+                               :nodes="nodes" />
+               </template>
+
+               <!-- Tfoot-->
+               <template #footer>
+                       <FilesListTableFooter :files-list-width="filesListWidth"
+                               :is-mtime-available="isMtimeAvailable"
+                               :is-size-available="isSizeAvailable"
+                               :nodes="nodes"
+                               :summary="summary" />
+               </template>
+       </VirtualList>
 </template>
 
 <script lang="ts">
 import type { Node as NcNode } from '@nextcloud/files'
 import type { PropType } from 'vue'
-import type { UserConfig } from '../types.ts'
+import type { UserConfig } from '../types'
 
-import { Fragment } from 'vue-frag'
-import { getFileListHeaders, Folder, View, Permission, getFileActions } from '@nextcloud/files'
+import { getFileListHeaders, Folder, View, getFileActions } from '@nextcloud/files'
 import { showError } from '@nextcloud/dialogs'
 import { loadState } from '@nextcloud/initial-state'
 import { translate as t, translatePlural as n } from '@nextcloud/l10n'
 import Vue from 'vue'
 
-import { action as sidebarAction } from '../actions/sidebarAction.ts'
-import { useUserConfigStore } from '../store/userconfig.ts'
-import DragAndDropNotice from './DragAndDropNotice.vue'
+import { action as sidebarAction } from '../actions/sidebarAction.js'
+import { useUserConfigStore } from '../store/userconfig.js'
 import FileEntry from './FileEntry.vue'
 import FileEntryGrid from './FileEntryGrid.vue'
 import FilesListHeader from './FilesListHeader.vue'
 import FilesListTableFooter from './FilesListTableFooter.vue'
 import FilesListTableHeader from './FilesListTableHeader.vue'
-import filesListWidthMixin from '../mixins/filesListWidth.ts'
+import filesListWidthMixin from '../mixins/filesListWidth.js'
 import logger from '../logger.js'
 import VirtualList from './VirtualList.vue'
 
@@ -100,11 +89,9 @@ export default Vue.extend({
        name: 'FilesListVirtual',
 
        components: {
-               DragAndDropNotice,
                FilesListHeader,
                FilesListTableFooter,
                FilesListTableHeader,
-               Fragment,
                VirtualList,
        },
 
@@ -140,7 +127,6 @@ export default Vue.extend({
                        FileEntryGrid,
                        headers: getFileListHeaders(),
                        scrollToIndex: 0,
-                       dragover: false,
                        dndNoticeHeight: 0,
                }
        },
@@ -192,10 +178,6 @@ export default Vue.extend({
                        return [...this.headers].sort((a, b) => a.order - b.order)
                },
 
-               canUpload() {
-                       return this.currentFolder && (this.currentFolder.permissions & Permission.CREATE) !== 0
-               },
-
                caption() {
                        const defaultCaption = t('files', 'List of files and folders.')
                        const viewCaption = this.currentView.caption || defaultCaption
@@ -214,12 +196,15 @@ export default Vue.extend({
                // Add events on parent to cover both the table and DragAndDrop notice
                const mainContent = window.document.querySelector('main.app-content') as HTMLElement
                mainContent.addEventListener('dragover', this.onDragOver)
-               mainContent.addEventListener('dragleave', this.onDragLeave)
 
                this.scrollToFile(this.fileId)
                this.openSidebarForFile(this.fileId)
                this.handleOpenFile()
+       },
 
+       beforeDestroy() {
+               const mainContent = window.document.querySelector('main.app-content') as HTMLElement
+               mainContent.removeEventListener('dragover', this.onDragOver)
        },
 
        methods: {
@@ -273,9 +258,7 @@ export default Vue.extend({
                        // Detect if we're only dragging existing files or not
                        const isForeignFile = event.dataTransfer?.types.includes('Files')
                        if (isForeignFile) {
-                               this.dragover = true
-                       } else {
-                               this.dragover = false
+                               return
                        }
 
                        event.preventDefault()
@@ -295,21 +278,6 @@ export default Vue.extend({
                                this.$refs.table.$el.scrollTop = this.$refs.table.$el.scrollTop + 25
                        }
                },
-               onDragLeave(event: DragEvent) {
-                       // Counter bubbling, make sure we're ending the drag
-                       // only when we're leaving the current element
-                       const currentTarget = event.currentTarget as HTMLElement
-                       if (currentTarget?.contains(event.relatedTarget as HTMLElement)) {
-                               return
-                       }
-
-                       this.dragover = false
-               },
-
-               onScroll() {
-                       // Update the sticky position of the thead to adapt to the scroll
-                       this.dndNoticeHeight = (this.$refs.thead.$el?.getBoundingClientRect?.()?.top ?? 0) + 'px'
-               },
 
                t,
        },
index 2191d7904f5ca2040cd46b9067b85c4fe63e757b..0a99f19bb5f14628cd39b0d19e79b6c463d8264d 100644 (file)
 
 <script lang="ts">
 import type { File, Folder, Node } from '@nextcloud/files'
+import type { PropType } from 'vue'
+
 import { debounce } from 'debounce'
-import Vue, { PropType } from 'vue'
+import Vue from 'vue'
 
 import filesListWidthMixin from '../mixins/filesListWidth.ts'
 import logger from '../logger.js'
index 211ac8815403c28ea5756941937ad2b033af17aa..31f942e2d7bc5f08292caa89cad9ed40952d292d 100644 (file)
@@ -30,6 +30,8 @@ export default Vue.extend({
        },
        mounted() {
                const fileListEl = document.querySelector('#app-content-vue')
+               this.filesListWidth = fileListEl?.clientWidth ?? null
+
                this.$resizeObserver = new ResizeObserver((entries) => {
                        if (entries.length > 0 && entries[0].target === fileListEl) {
                                this.filesListWidth = entries[0].contentRect.width
index 0729e8a983a38e3d888a50f71c9a56a162186b08..d4c3e0b4d4cd3ca4f1c515a576c253a983ddea24 100644 (file)
                        <NcLoadingIcon v-if="isRefreshing" class="files-list__refresh-icon" />
                </div>
 
+               <!-- Drag and drop notice -->
+               <DragAndDropNotice v-if="!loading && canUpload"
+                       :current-folder="currentFolder" />
+
                <!-- Initial loading -->
                <NcLoadingIcon v-if="loading && !isRefreshing"
                        class="files-list__loading-icon"
@@ -121,26 +125,28 @@ import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
 import ShareVariantIcon from 'vue-material-design-icons/ShareVariant.vue'
 import ViewGridIcon from 'vue-material-design-icons/ViewGrid.vue'
 
-import { action as sidebarAction } from '../actions/sidebarAction.ts'
-import { useFilesStore } from '../store/files.ts'
-import { usePathsStore } from '../store/paths.ts'
-import { useSelectionStore } from '../store/selection.ts'
-import { useUploaderStore } from '../store/uploader.ts'
-import { useUserConfigStore } from '../store/userconfig.ts'
-import { useViewConfigStore } from '../store/viewConfig.ts'
+import { action as sidebarAction } from '../actions/sidebarAction.js'
+import { useFilesStore } from '../store/files.js'
+import { usePathsStore } from '../store/paths.js'
+import { useSelectionStore } from '../store/selection.js'
+import { useUploaderStore } from '../store/uploader.js'
+import { useUserConfigStore } from '../store/userconfig.js'
+import { useViewConfigStore } from '../store/viewConfig.js'
 import BreadCrumbs from '../components/BreadCrumbs.vue'
 import FilesListVirtual from '../components/FilesListVirtual.vue'
-import filesListWidthMixin from '../mixins/filesListWidth.ts'
-import filesSortingMixin from '../mixins/filesSorting.ts'
+import filesListWidthMixin from '../mixins/filesListWidth.js'
+import filesSortingMixin from '../mixins/filesSorting.js'
 import logger from '../logger.js'
+import DragAndDropNotice from '../components/DragAndDropNotice.vue'
 
-const isSharingEnabled = getCapabilities()?.files_sharing !== undefined
+const isSharingEnabled = (getCapabilities() as { files_sharing?: boolean })?.files_sharing !== undefined
 
 export default Vue.extend({
        name: 'FilesList',
 
        components: {
                BreadCrumbs,
+               DragAndDropNotice,
                FilesListVirtual,
                LinkIcon,
                ListViewIcon,
@@ -342,9 +348,16 @@ export default Vue.extend({
                                : this.t('files', 'Switch to grid view')
                },
 
+               /**
+                * Check if the current folder has create permissions
+                */
                canUpload() {
                        return this.currentFolder && (this.currentFolder.permissions & Permission.CREATE) !== 0
                },
+
+               /**
+                * Check if current folder has share permissions
+                */
                canShare() {
                        return isSharingEnabled
                                && this.currentFolder && (this.currentFolder.permissions & Permission.SHARE) !== 0