fix(files): Allow to drag and drop new files also on empty directories

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
pull/41693/head
Ferdinand Thiessen 6 months ago
parent c13b748dea
commit e30ce44dac
No known key found for this signature in database
GPG Key ID: 45FAE7268762B400

@ -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>

@ -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
@ -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,
},

@ -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'

@ -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

@ -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

Loading…
Cancel
Save