aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/src
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@protonmail.com>2023-10-14 15:52:07 +0200
committerJohn Molakvoæ <skjnldsv@protonmail.com>2023-10-17 11:19:02 +0200
commitc4b8abd343bab47cff241b2746f8e81e004d6041 (patch)
tree843b59fc429cc32355656dd7be8745dd379415c9 /apps/files/src
parent16975ae45720945776155f026835cfdaf8491383 (diff)
downloadnextcloud-server-c4b8abd343bab47cff241b2746f8e81e004d6041.tar.gz
nextcloud-server-c4b8abd343bab47cff241b2746f8e81e004d6041.zip
perf(files): reuse keys for virtual scrolling
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
Diffstat (limited to 'apps/files/src')
-rw-r--r--apps/files/src/components/VirtualList.vue41
1 files changed, 35 insertions, 6 deletions
diff --git a/apps/files/src/components/VirtualList.vue b/apps/files/src/components/VirtualList.vue
index 6a415799034..dae3c8d3f42 100644
--- a/apps/files/src/components/VirtualList.vue
+++ b/apps/files/src/components/VirtualList.vue
@@ -16,8 +16,8 @@
:class="gridMode ? 'files-list__tbody--grid' : 'files-list__tbody--list'"
data-cy-files-list-tbody>
<component :is="dataComponent"
- v-for="(item, i) in renderedItems"
- :key="i"
+ v-for="({key, item}, i) in renderedItems"
+ :key="key"
:visible="(i >= bufferItems || index <= bufferItems) && (i < shownItems - bufferItems)"
:source="item"
:index="i"
@@ -34,13 +34,18 @@
</template>
<script lang="ts">
-import type { File, Folder } from '@nextcloud/files'
+import type { File, Folder, Node } from '@nextcloud/files'
import { debounce } from 'debounce'
import Vue, { PropType } from 'vue'
import filesListWidthMixin from '../mixins/filesListWidth.ts'
import logger from '../logger.js'
+interface RecycledPoolItem {
+ key: string,
+ item: Node,
+}
+
export default Vue.extend({
name: 'VirtualList',
@@ -98,7 +103,8 @@ export default Vue.extend({
},
itemHeight() {
- // 160px + 44px (name) + 15px (grid gap)
+ // Align with css in FilesListVirtual
+ // 138px + 44px (name) + 15px (grid gap)
return this.gridMode ? (160 + 44 + 15) : 56
},
// Grid mode only
@@ -128,11 +134,32 @@ export default Vue.extend({
return this.rowCount
},
- renderedItems(): (File | Folder)[] {
+ renderedItems(): RecycledPoolItem[] {
if (!this.isReady) {
return []
}
- return this.dataSources.slice(this.startIndex, this.startIndex + this.shownItems)
+
+ const items = this.dataSources.slice(this.startIndex, this.startIndex + this.shownItems) as Node[]
+
+ const oldItems = items.filter(item => Object.values(this.$_recycledPool).includes(item[this.dataKey]))
+ const oldItemsKeys = oldItems.map(item => item[this.dataKey] as string)
+ const unusedKeys = Object.keys(this.$_recycledPool).filter(key => !oldItemsKeys.includes(this.$_recycledPool[key]))
+
+ return items.map(item => {
+ const index = Object.values(this.$_recycledPool).indexOf(item[this.dataKey])
+ // If defined, let's keep the key
+ if (index !== -1) {
+ return {
+ key: Object.keys(this.$_recycledPool)[index],
+ item,
+ }
+ }
+
+ // Get and consume reusable key or generate a new one
+ const key = unusedKeys.pop() || Math.random().toString(36).substr(2)
+ this.$_recycledPool[key] = item[this.dataKey]
+ return { key, item }
+ })
},
tbodyStyle() {
@@ -174,6 +201,8 @@ export default Vue.extend({
if (this.scrollToIndex) {
this.$el.scrollTop = Math.floor((this.index * this.itemHeight) / this.rowCount) + this.beforeHeight
}
+
+ this.$_recycledPool = {} as Record<string, any>
},
beforeDestroy() {