diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2023-11-23 14:34:57 +0100 |
---|---|---|
committer | Ferdinand Thiessen <opensource@fthiessen.de> | 2023-12-05 13:04:29 +0100 |
commit | 89e01eafee1a14798ad9f87ddd32751c9eaa0490 (patch) | |
tree | 78e6884504c291beab1b20eabcd78254bee51131 /apps | |
parent | 7ae780be6ba7bf635665ff7033ebc11da38a97a9 (diff) | |
download | nextcloud-server-89e01eafee1a14798ad9f87ddd32751c9eaa0490.tar.gz nextcloud-server-89e01eafee1a14798ad9f87ddd32751c9eaa0490.zip |
fix(files): Allow to drag and drop new files also on empty directories
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Diffstat (limited to 'apps')
-rw-r--r-- | apps/files/src/components/DragAndDropNotice.vue | 72 | ||||
-rw-r--r-- | apps/files/src/components/FilesListVirtual.vue | 134 | ||||
-rw-r--r-- | apps/files/src/components/VirtualList.vue | 4 | ||||
-rw-r--r-- | apps/files/src/mixins/filesListWidth.ts | 2 | ||||
-rw-r--r-- | apps/files/src/views/FilesList.vue | 33 |
5 files changed, 121 insertions, 124 deletions
diff --git a/apps/files/src/components/DragAndDropNotice.vue b/apps/files/src/components/DragAndDropNotice.vue index d5f93dac256..1e53556e1f5 100644 --- a/apps/files/src/components/DragAndDropNotice.vue +++ b/apps/files/src/components/DragAndDropNotice.vue @@ -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" /> @@ -34,17 +34,16 @@ <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> diff --git a/apps/files/src/components/FilesListVirtual.vue b/apps/files/src/components/FilesListVirtual.vue index 925048d36c8..cd1d385e886 100644 --- a/apps/files/src/components/FilesListVirtual.vue +++ b/apps/files/src/components/FilesListVirtual.vue @@ -20,79 +20,68 @@ - --> <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 @@ -215,12 +197,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: { @@ -274,9 +259,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() @@ -296,21 +279,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, }, diff --git a/apps/files/src/components/VirtualList.vue b/apps/files/src/components/VirtualList.vue index 2191d7904f5..0a99f19bb5f 100644 --- a/apps/files/src/components/VirtualList.vue +++ b/apps/files/src/components/VirtualList.vue @@ -41,8 +41,10 @@ <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' diff --git a/apps/files/src/mixins/filesListWidth.ts b/apps/files/src/mixins/filesListWidth.ts index 211ac881540..31f942e2d7b 100644 --- a/apps/files/src/mixins/filesListWidth.ts +++ b/apps/files/src/mixins/filesListWidth.ts @@ -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 diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue index 0729e8a983a..d4c3e0b4d4c 100644 --- a/apps/files/src/views/FilesList.vue +++ b/apps/files/src/views/FilesList.vue @@ -62,6 +62,10 @@ <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 |