Skip to content

Commit e6760ec

Browse files
committed
fix(LogTable): implement virtual scrolling
- log polling or reading of heavy logfile renders a huge amount of entries, which increase CPU load - instead we can render items in viewport +- 50 items around them, rest are rendered as dummy tr -> less resources Signed-off-by: Maksim Sukharev <[email protected]>
1 parent 374c3f6 commit e6760ec

File tree

1 file changed

+41
-7
lines changed

1 file changed

+41
-7
lines changed

src/components/table/LogTable.vue

+41-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
SPDX-License-Identifier: AGPL-3.0-or-later
44
-->
55
<template>
6-
<div class="log-table">
6+
<div ref="table" class="log-table" @scroll="debouncedHandleScrollPosition">
77
<LogDetailsModal v-if="currentRow"
88
:open.sync="isModalOpen"
99
:current-entry.sync="currentRow"
@@ -35,10 +35,13 @@
3535
</td>
3636
</tr>
3737

38-
<LogTableRow v-for="row, rowNumber in sortedRows"
39-
:key="rowNumber"
40-
:row="row"
41-
@show-details="showDetailsForRow" />
38+
<template v-for="(row, rowNumber) in sortedRows">
39+
<tr v-if="row.showDummy" :key="rowNumber" class="log-table__dummy-row" />
40+
<LogTableRow v-else
41+
:key="rowNumber"
42+
:row="row"
43+
@show-details="showDetailsForRow" />
44+
</template>
4245
</tbody>
4346
<tfoot>
4447
<tr v-if="sortedByTime !== 'ascending'">
@@ -59,10 +62,11 @@
5962
<script setup lang="ts">
6063
import type { ILogEntry, ISortingOptions } from '../../interfaces'
6164

62-
import { computed, nextTick, ref } from 'vue'
65+
import { computed, nextTick, ref, watch } from 'vue'
6366
import { translate as t } from '@nextcloud/l10n'
6467
import { useSettingsStore } from '../../store/settings'
6568
import { useLogStore } from '../../store/logging'
69+
import { debounce } from '../../utils/debounce'
6670

6771
import IntersectionObserver from '../IntersectionObserver.vue'
6872
import LogDetailsModal from '../LogDetailsModal.vue'
@@ -109,11 +113,27 @@ const showDetailsForRow = (row: ILogEntry) => {
109113
isModalOpen.value = true
110114
}
111115

116+
/**
117+
* Reference to the table container, used to track current scroll position
118+
*/
119+
const table = ref<HTMLElement>()
112120
/**
113121
* Reference to the table body, used for keeping scroll position on loading more entries
114122
*/
115123
const tableBody = ref<HTMLElement>()
116124

125+
/**
126+
* Get index of the first visible row, to define visible scope around it
127+
* Rest entries should be rendered as dummy nodes, to reduce load while render
128+
*/
129+
const firstVisibleRowIndex = ref(0)
130+
131+
const handleScrollPosition = () => {
132+
firstVisibleRowIndex.value = Math.floor((table.value?.scrollTop ?? 0) / 42)
133+
}
134+
135+
const debouncedHandleScrollPosition = debounce(handleScrollPosition, 100)
136+
117137
/**
118138
* Load older log entries and ensure that the view sticks at the previous top element
119139
*/
@@ -144,7 +164,17 @@ const sortedRows = computed(() => {
144164
const order = (fn: SortLogFunction, type: string, a: ILogEntry, b: ILogEntry) => type === 'ascending' ? fn(a, b) : (type === 'descending' ? fn(b, a) : 0)
145165

146166
sorted.sort((a, b) => order(byLevel, sortedByLevel.value, a, b) || order(byApp, sortedByApp.value, a, b) || order(byTime, sortedByTime.value, a, b))
147-
return sorted
167+
return sorted.map((row, index) => ({
168+
...row,
169+
showDummy: index > firstVisibleRowIndex.value + 50 || index < firstVisibleRowIndex.value - 50,
170+
}))
171+
})
172+
173+
/**
174+
* Recalculate the visible scope of log entries on list update
175+
*/
176+
watch(sortedRows, () => {
177+
handleScrollPosition()
148178
})
149179
</script>
150180

@@ -160,6 +190,10 @@ const sortedRows = computed(() => {
160190
table-layout: fixed;
161191
}
162192

193+
&__dummy-row {
194+
height: 42px;
195+
}
196+
163197
&__load-more {
164198
text-align: center;
165199
padding-block: 4px;

0 commit comments

Comments
 (0)