diff options
author | skjnldsv <skjnldsv@protonmail.com> | 2024-12-13 12:04:01 +0100 |
---|---|---|
committer | skjnldsv <skjnldsv@protonmail.com> | 2024-12-17 09:59:56 +0100 |
commit | 376a7bba7b2911c53ff9524ac0b889e502250bff (patch) | |
tree | 70d988dc522973945c5d18be3b027bf168da0663 /apps | |
parent | e7001022c75b3a818356378bb53bbfe5129a10fe (diff) | |
download | nextcloud-server-376a7bba7b2911c53ff9524ac0b889e502250bff.tar.gz nextcloud-server-376a7bba7b2911c53ff9524ac0b889e502250bff.zip |
fix(files): virtual scroller item size computation
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
Diffstat (limited to 'apps')
-rw-r--r-- | apps/files/src/components/VirtualList.vue | 59 |
1 files changed, 44 insertions, 15 deletions
diff --git a/apps/files/src/components/VirtualList.vue b/apps/files/src/components/VirtualList.vue index d2b436344a5..b3d1b1002f4 100644 --- a/apps/files/src/components/VirtualList.vue +++ b/apps/files/src/components/VirtualList.vue @@ -55,9 +55,10 @@ import type { File, Folder, Node } from '@nextcloud/files' import type { PropType } from 'vue' -import { useFileListWidth } from '../composables/useFileListWidth.ts' import { defineComponent } from 'vue' import debounce from 'debounce' + +import { useFileListWidth } from '../composables/useFileListWidth.ts' import logger from '../logger.ts' interface RecycledPoolItem { @@ -66,7 +67,6 @@ interface RecycledPoolItem { } type DataSource = File | Folder - type DataSourceKey = keyof DataSource export default defineComponent({ @@ -133,20 +133,22 @@ export default defineComponent({ // Items to render before and after the visible area bufferItems() { if (this.gridMode) { + // 1 row before and after in grid mode return this.columnCount } + // 3 rows before and after return 3 }, itemHeight() { // Align with css in FilesListVirtual - // 166px + 32px (name) + 16px (mtime) + 16px (padding) - return this.gridMode ? (166 + 32 + 16 + 16) : 55 + // 166px + 32px (name) + 16px (mtime) + 16px (padding top and bottom) + return this.gridMode ? (166 + 32 + 16 + 16 + 16) : 55 }, // Grid mode only itemWidth() { - // 166px + 16px padding - return 166 + 16 + // 166px + 16px x 2 (padding left and right) + return 166 + 16 + 16 }, rowCount() { @@ -161,9 +163,13 @@ export default defineComponent({ /** * Index of the first item to be rendered + * The index can be any file, not just the first one + * But the start index is the first item to be rendered, + * which needs to align with the column count */ startIndex() { - return Math.max(0, this.index - this.bufferItems) + const firstColumnIndex = this.index - (this.index % this.columnCount) + return Math.max(0, firstColumnIndex - this.bufferItems) }, /** @@ -260,7 +266,7 @@ export default defineComponent({ this.tableHeight = root?.clientHeight ?? 0 logger.debug('VirtualList: resizeObserver updated') this.onScroll() - }, 100, false)) + }, 100, { immediate: false })) this.resizeObserver.observe(before) this.resizeObserver.observe(root) @@ -284,28 +290,51 @@ export default defineComponent({ methods: { scrollTo(index: number) { + if (!this.$el) { + return + } + + // Check if the content is smaller than the viewport, meaning no scrollbar const targetRow = Math.ceil(this.dataSources.length / this.columnCount) if (targetRow < this.rowCount) { - logger.debug('VirtualList: Skip scrolling. nothing to scroll', { index, targetRow, rowCount: this.rowCount }) + logger.debug('VirtualList: Skip scrolling, nothing to scroll', { index, targetRow, rowCount: this.rowCount }) return } - this.index = index + // Scroll to one row and a half before the index - const scrollTop = (Math.floor(index / this.columnCount) - 0.5) * this.itemHeight + this.beforeHeight - logger.debug('VirtualList: scrolling to index ' + index, { scrollTop, columnCount: this.columnCount }) + const scrollTop = this.indexToScrollPos(index) + logger.debug('VirtualList: scrolling to index ' + index, { scrollTop, columnCount: this.columnCount, beforeHeight: this.beforeHeight }) this.$el.scrollTop = scrollTop }, onScroll() { this._onScrollHandle ??= requestAnimationFrame(() => { this._onScrollHandle = null - const topScroll = this.$el.scrollTop - this.beforeHeight - const index = Math.floor(topScroll / this.itemHeight) * this.columnCount + + const index = this.scrollPosToIndex(this.$el.scrollTop) + if (index === this.index) { + return + } + // Max 0 to prevent negative index - this.index = Math.max(0, index) + this.index = Math.max(0, Math.floor(index)) this.$emit('scroll') }) }, + + // Convert scroll position to index + // It should be the opposite of `indexToScrollPos` + scrollPosToIndex(scrollPos: number): number { + const topScroll = scrollPos - this.beforeHeight + // Max 0 to prevent negative index + return Math.max(0, Math.floor(topScroll / this.itemHeight)) * this.columnCount + }, + + // Convert index to scroll position + // It should be the opposite of `scrollPosToIndex` + indexToScrollPos(index: number): number { + return (Math.floor(index / this.columnCount) - 0.5) * this.itemHeight + this.beforeHeight + }, }, }) </script> |