-
-->
<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: {
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
}
// 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']),
},
- }))
+ })
}
})
}
<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%;
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;
border: 2px var(--color-border-dark) dashed;
border-radius: var(--border-radius-large);
}
-
- &__close {
- position: absolute !important;
- top: 10px;
- right: 10px;
- }
}
</style>
-
-->
<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'
name: 'FilesListVirtual',
components: {
- DragAndDropNotice,
FilesListHeader,
FilesListTableFooter,
FilesListTableHeader,
- Fragment,
VirtualList,
},
FileEntryGrid,
headers: getFileListHeaders(),
scrollToIndex: 0,
- dragover: false,
dndNoticeHeight: 0,
}
},
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
// 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: {
// 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()
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,
},
<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'
},
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
<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"
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,
: 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