Skip to content

Commit

Permalink
Implemented virtual scrolling for Shoot List (#1674)
Browse files Browse the repository at this point in the history
* Implemented virtual scrolling for Shoot List

* set items per page select width

* - Removed option to switch between virtual scrolling and paging
- Added workaround for vuetify rendering issue when virtual table scroll position is not initial

* Removed workaround for scroll position not initial issue

* PR Feedback
  • Loading branch information
grolu authored Jan 23, 2025
1 parent 8402b90 commit 4bab298
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 78 deletions.
82 changes: 82 additions & 0 deletions frontend/__tests__/components/Vuetify.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,88 @@ describe('components', () => {
})
})

describe('v-data-table-virtual', () => {
// These tests basically test that nothing changes in the way vuetify renders the virtual table
// as the way it behaves in case no item-height is defined is kind of undeterministic
const TestTableRow = {
name: 'TestTableRow',
props: {
item: {
type: Object,
required: true,
},
},
template: '<tr><td>{{ item.name }}</td></tr>',
}

function mountDataTableVirtual ({ itemHeight, itemsCount = 50 } = {}) {
const items = Array.from({ length: itemsCount }).map((_, i) => ({
id: i,
name: `Item ${i}`,
}))
const columns = [
{ key: 'name', title: 'Name' },
]

const Component = {
name: 'TestVirtualTable',
components: {
TestTableRow,
},
template: `
<v-app>
<v-main>
<v-data-table-virtual
:items="items"
:columns="columns"
:item-height="itemHeight"
:height="400"
>
<template #item="{ item }">
<TestTableRow :item="item" />
</template>
</v-data-table-virtual>
</v-main>
</v-app>
`,
data () {
return {
items,
columns,
itemHeight,
}
},
}

return mount(Component, {
global: {
plugins: [
createVuetifyPlugin(),
],
},
})
}

it('should render default number of rows if item-height is not defined', async () => {
const wrapper = mountDataTableVirtual({ itemsCount: 50 })
await nextTick()

const rows = wrapper.findAllComponents(TestTableRow)
expect(rows).toHaveLength(25) // by default, the table expects 16px tall rows
})

it('should only render the visible rows if item-height is defined', async () => {
const wrapper = mountDataTableVirtual({
itemsCount: 50,
itemHeight: 40,
})
await nextTick()

const rows = wrapper.findAllComponents(TestTableRow)
expect(rows).toHaveLength(10) // 400px / 40px = 10 rows
})
})

describe('v-breadcrumbs', () => {
it('should be able to find v-breadcrumbs-item v-breadcrumbs-item--disabled class', () => {
const Component = {
Expand Down
107 changes: 58 additions & 49 deletions frontend/src/components/GDataTableFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,52 +5,62 @@ SPDX-License-Identifier: Apache-2.0
-->

<template>
<v-divider />
<div class="v-data-table-footer">
<div class="v-data-table-footer__items-per-page">
<span>Rows per page:</span>
<v-select
:items="itemsPerPageOptions"
:model-value="itemsPerPage"
density="compact"
variant="solo"
flat
single-line
hide-details
@update:model-value="value => setItemsPerPage(Number(value))"
/>
</div>
<div class="v-data-table-footer__info">
{{ !itemsLength ? 0 : startIndex + 1 }}-{{ stopIndex }} of {{ itemsLength }}
</div>
<div class="v-data-table-footer__pagination">
<v-btn
icon="mdi-page-first"
variant="plain"
:disabled="page === 1"
aria-label="First page"
@click="setPage(1)"
/>
<v-btn
icon="mdi-chevron-left"
variant="plain"
:disabled="page === 1"
aria-label="Previous page"
@click="setPage(Math.max(1, page - 1))"
/>
<v-btn
icon="mdi-chevron-right"
variant="plain"
:disabled="page === pageCount"
aria-label="Next page"
@click="setPage(Math.min(pageCount, page + 1))"
/>
<v-btn
icon="mdi-page-last"
variant="plain"
:disabled="page === pageCount"
aria-label="Last page"
@click="setPage(pageCount)"
/>
<template v-if="pageCount">
<div class="v-data-table-footer__items-per-page">
<span>Rows per page:</span>
<v-select
:items="itemsPerPageOptions"
:model-value="itemsPerPage"
density="compact"
variant="solo"
flat
single-line
hide-details
width="105px"
@update:model-value="value => setItemsPerPage(Number(value))"
/>
</div>
<div class="v-data-table-footer__info">
{{ !itemsLength ? 0 : startIndex + 1 }}-{{ stopIndex }} of {{ itemsLength }}
</div>
<div class="v-data-table-footer__pagination">
<v-btn
icon="mdi-page-first"
variant="plain"
:disabled="page === 1"
aria-label="First page"
@click="setPage(1)"
/>
<v-btn
icon="mdi-chevron-left"
variant="plain"
:disabled="page === 1"
aria-label="Previous page"
@click="setPage(Math.max(1, page - 1))"
/>
<v-btn
icon="mdi-chevron-right"
variant="plain"
:disabled="page === pageCount"
aria-label="Next page"
@click="setPage(Math.min(pageCount, page + 1))"
/>
<v-btn
icon="mdi-page-last"
variant="plain"
:disabled="page === pageCount"
aria-label="Last page"
@click="setPage(pageCount)"
/>
</div>
</template>
<div
v-else
class="v-data-table-footer__info"
>
{{ itemsLength }} Rows
</div>
</div>
</template>
Expand All @@ -68,7 +78,7 @@ const props = defineProps({
},
itemsPerPage: {
type: Number,
required: true,
required: false,
},
itemsPerPageOptions: {
type: Array,
Expand All @@ -77,16 +87,15 @@ const props = defineProps({
{ value: 25, title: '25' },
{ value: 50, title: '50' },
{ value: 100, title: '100' },
{ value: -1, title: 'All' },
]),
},
page: {
type: Number,
required: true,
required: false,
},
pageCount: {
type: Number,
required: true,
required: false,
},
})

Expand Down
6 changes: 0 additions & 6 deletions frontend/src/store/localStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,11 +222,6 @@ export const useLocalStorageStore = defineStore('localStorage', () => {
writeDefaults: false,
})

const shootItemsPerPage = useLocalStorage('projects/shoot-list/itemsPerPage', 10, {
serializer: StorageSerializers.integer,
writeDefaults: false,
})

const shootSortBy = useLocalStorage('projects/shoot-list/sortBy', [], {
serializer: StorageSerializers.json,
writeDefaults: false,
Expand Down Expand Up @@ -293,7 +288,6 @@ export const useLocalStorageStore = defineStore('localStorage', () => {
dnsSecretItemsPerPage,
dnsSecretSortBy,
shootSelectedColumns,
shootItemsPerPage,
shootSortBy,
allProjectsShootFilter,
shootListFetchFromCache,
Expand Down
31 changes: 8 additions & 23 deletions frontend/src/views/GShootList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -154,18 +154,19 @@ SPDX-License-Identifier: Apache-2.0
/>
</template>
</g-toolbar>
<v-data-table
v-model:page="page"
<v-data-table-virtual
ref="shootTable"
v-model:sort-by="sortByInternal"
v-model:items-per-page="shootItemsPerPage"
:headers="visibleHeaders"
:items="sortedAndFilteredItems"
hover
:loading="loading || !connected"
:items-per-page-options="itemsPerPageOptions"
:custom-key-sort="customKeySort"
hover
must-sort
fixed-header
class="g-table"
height="calc(100vh - 240px)"
item-height="52px"
>
<template #progress>
<g-shoot-list-progress />
Expand All @@ -182,16 +183,12 @@ SPDX-License-Identifier: Apache-2.0
:visible-headers="visibleHeaders"
/>
</template>
<template #bottom="{ pageCount }">
<template #bottom>
<g-data-table-footer
v-model:page="page"
v-model:items-per-page="shootItemsPerPage"
:items-length="sortedAndFilteredItems.length"
:items-per-page-options="itemsPerPageOptions"
:page-count="pageCount"
/>
</template>
</v-data-table>
</v-data-table-virtual>
</v-card>
<g-shoot-list-actions />
</v-container>
Expand Down Expand Up @@ -328,7 +325,6 @@ export default {

function onUpdateShootSearch (value) {
shootSearch.value = value

setDebouncedShootSearch()
}

Expand All @@ -346,13 +342,7 @@ export default {
data () {
return {
dialog: null,
page: 1,
selectedColumns: undefined,
itemsPerPageOptions: [
{ value: 5, title: '5' },
{ value: 10, title: '10' },
{ value: 20, title: '20' },
],
}
},
computed: {
Expand Down Expand Up @@ -388,7 +378,6 @@ export default {
]),
...mapWritableState(useLocalStorageStore, [
'shootSelectedColumns',
'shootItemsPerPage',
'shootSortBy',
'shootCustomSelectedColumns',
'shootCustomSortBy',
Expand All @@ -398,9 +387,6 @@ export default {
defaultSortBy () {
return [{ key: 'name', order: 'asc' }]
},
defaultItemsPerPage () {
return 10
},
focusModeInternal: {
get () {
return this.focusMode
Expand Down Expand Up @@ -805,7 +791,6 @@ export default {
...this.defaultCustomSelectedColumns,
}
this.saveSelectedColumns()
this.shootItemsPerPage = this.defaultItemsPerPage
this.sortByInternal = this.defaultSortBy
},
updateTableSettings () {
Expand Down

0 comments on commit 4bab298

Please sign in to comment.