aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/src/components/FilesListVirtual.vue
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files/src/components/FilesListVirtual.vue')
-rw-r--r--apps/files/src/components/FilesListVirtual.vue123
1 files changed, 79 insertions, 44 deletions
diff --git a/apps/files/src/components/FilesListVirtual.vue b/apps/files/src/components/FilesListVirtual.vue
index 014f0a89f00..69cab260963 100644
--- a/apps/files/src/components/FilesListVirtual.vue
+++ b/apps/files/src/components/FilesListVirtual.vue
@@ -20,28 +20,18 @@
-
-->
<template>
- <RecycleScroller ref="recycleScroller"
- class="files-list"
- key-field="source"
- :items="nodes"
- :item-size="55"
- :table-mode="true"
- item-class="files-list__row"
- item-tag="tr"
- list-class="files-list__body"
- list-tag="tbody"
- role="table">
- <template #default="{ item, active, index }">
- <!-- File row -->
- <FileEntry :active="active"
- :index="index"
- :is-mtime-available="isMtimeAvailable"
- :is-size-available="isSizeAvailable"
- :files-list-width="filesListWidth"
- :nodes="nodes"
- :source="item" />
- </template>
-
+ <VirtualList :data-component="FileEntry"
+ :data-key="'source'"
+ :data-sources="nodes"
+ :item-height="56"
+ :extra-props="{
+ isMtimeAvailable,
+ isSizeAvailable,
+ nodes,
+ filesListWidth,
+ }"
+ :scroll-to-index="scrollToIndex">
+ <!-- Accessibility description and headers -->
<template #before>
<!-- Accessibility description -->
<caption class="hidden-visually">
@@ -49,42 +39,54 @@
{{ t('files', 'This list is not fully rendered for performance reasons. The files will be rendered as you navigate through the list.') }}
</caption>
- <!-- Thead-->
- <FilesListHeader :files-list-width="filesListWidth"
+ <!-- Headers -->
+ <FilesListHeader v-for="header in sortedHeaders"
+ :key="header.id"
+ :current-folder="currentFolder"
+ :current-view="currentView"
+ :header="header" />
+ </template>
+
+ <!-- Thead-->
+ <template #header>
+ <FilesListTableHeader :files-list-width="filesListWidth"
:is-mtime-available="isMtimeAvailable"
:is-size-available="isSizeAvailable"
:nodes="nodes" />
</template>
- <template #after>
- <!-- Tfoot-->
- <FilesListFooter :files-list-width="filesListWidth"
+ <!-- Tfoot-->
+ <template #footer>
+ <FilesListTableFooter :files-list-width="filesListWidth"
:is-mtime-available="isMtimeAvailable"
:is-size-available="isSizeAvailable"
:nodes="nodes"
:summary="summary" />
</template>
- </RecycleScroller>
+ </VirtualList>
</template>
<script lang="ts">
-import { RecycleScroller } from 'vue-virtual-scroller'
import { translate, translatePlural } from '@nextcloud/l10n'
+import { getFileListHeaders } from '@nextcloud/files'
import Vue from 'vue'
+import VirtualList from './VirtualList.vue'
import FileEntry from './FileEntry.vue'
-import FilesListFooter from './FilesListFooter.vue'
import FilesListHeader from './FilesListHeader.vue'
+import FilesListTableFooter from './FilesListTableFooter.vue'
+import FilesListTableHeader from './FilesListTableHeader.vue'
import filesListWidthMixin from '../mixins/filesListWidth.ts'
+import { showError } from '@nextcloud/dialogs'
export default Vue.extend({
name: 'FilesListVirtual',
components: {
- RecycleScroller,
- FileEntry,
FilesListHeader,
- FilesListFooter,
+ FilesListTableHeader,
+ FilesListTableFooter,
+ VirtualList,
},
mixins: [
@@ -96,6 +98,10 @@ export default Vue.extend({
type: Object,
required: true,
},
+ currentFolder: {
+ type: Object,
+ required: true,
+ },
nodes: {
type: Array,
required: true,
@@ -105,6 +111,7 @@ export default Vue.extend({
data() {
return {
FileEntry,
+ headers: getFileListHeaders(),
}
},
@@ -113,6 +120,21 @@ export default Vue.extend({
return this.nodes.filter(node => node.type === 'file')
},
+ fileId() {
+ return parseInt(this.$route.params.fileid || this.$route.query.fileid) || null
+ },
+
+ scrollToIndex() {
+ if (!this.fileId) {
+ return
+ }
+ const index = this.nodes.findIndex(node => node.fileid === this.fileId)
+ if (index === -1) {
+ showError(this.t('files', 'File not found'))
+ }
+ return Math.max(0, index)
+ },
+
summaryFile() {
const count = this.files.length
return translatePlural('files', '{count} file', '{count} files', count, { count })
@@ -138,13 +160,14 @@ export default Vue.extend({
}
return this.nodes.some(node => node.attributes.size !== undefined)
},
- },
- mounted() {
- // Make the root recycle scroller a table for proper semantics
- const slots = this.$el.querySelectorAll('.vue-recycle-scroller__slot')
- slots[0].setAttribute('role', 'thead')
- slots[1].setAttribute('role', 'tfoot')
+ sortedHeaders() {
+ if (!this.currentFolder || !this.currentView) {
+ return []
+ }
+
+ return [...this.headers].sort((a, b) => a.order - b.order)
+ },
},
methods: {
@@ -173,7 +196,7 @@ export default Vue.extend({
&::v-deep {
// Table head, body and footer
- tbody, .vue-recycle-scroller__slot {
+ tbody {
display: flex;
flex-direction: column;
width: 100%;
@@ -181,23 +204,35 @@ export default Vue.extend({
position: relative;
}
+ // Before table and thead
+ .files-list__before {
+ display: flex;
+ flex-direction: column;
+ }
+
// Table header
- .vue-recycle-scroller__slot[role='thead'] {
+ .files-list__thead {
// Pinned on top when scrolling
position: sticky;
z-index: 10;
top: 0;
- height: var(--row-height);
+ }
+
+ .files-list__thead,
+ .files-list__tfoot {
+ display: flex;
+ width: 100%;
background-color: var(--color-main-background);
+
}
tr {
- position: absolute;
+ position: relative;
display: flex;
align-items: center;
width: 100%;
- border-bottom: 1px solid var(--color-border);
user-select: none;
+ border-bottom: 1px solid var(--color-border);
}
td, th {