1
0
Fork 0
mirror of synced 2024-09-08 05:31:47 +12:00

Reset scrolling when datasource changes and fix wasted pagination calls

This commit is contained in:
Andrew Kingston 2023-03-12 16:04:17 +00:00
parent d7666272e0
commit e76c541627
6 changed files with 151 additions and 115 deletions

View file

@ -15,6 +15,8 @@
import { createUserStores } from "./stores/users"
import { createResizeStores } from "./stores/resize"
import { createMenuStores } from "./stores/menu"
import { createMaxScrollStores } from "./stores/max-scroll"
import { createPaginationStores } from "./stores/pagination"
import DeleteButton from "./DeleteButton.svelte"
import SheetBody from "./SheetBody.svelte"
import ResizeOverlay from "./ResizeOverlay.svelte"
@ -57,16 +59,18 @@
config,
}
context = { ...context, ...createEventManagers() }
context = { ...context, ...createRowsStore(context) }
context = { ...context, ...createColumnsStores(context) }
context = { ...context, ...createResizeStores(context) }
context = { ...context, ...createBoundsStores(context) }
context = { ...context, ...createScrollStores(context) }
context = { ...context, ...createRowsStore(context) }
context = { ...context, ...createColumnsStores(context) }
context = { ...context, ...createMaxScrollStores(context) }
context = { ...context, ...createResizeStores(context) }
context = { ...context, ...createViewportStores(context) }
context = { ...context, ...createReorderStores(context) }
context = { ...context, ...createUIStores(context) }
context = { ...context, ...createUserStores(context) }
context = { ...context, ...createMenuStores(context) }
context = { ...context, ...createPaginationStores(context) }
// Reference some stores for local use
const { isResizing, isReordering, ui, loaded } = context

View file

@ -0,0 +1,86 @@
import { derived, get } from "svelte/store"
export const createMaxScrollStores = context => {
const { rows, visibleColumns, stickyColumn, bounds, cellHeight, scroll } =
context
const padding = 180
// Memoize store primitives
const scrollTop = derived(scroll, $scroll => $scroll.top, 0)
const scrollLeft = derived(scroll, $scroll => $scroll.left, 0)
// Derive vertical limits
const height = derived(bounds, $bounds => $bounds.height, 0)
const width = derived(bounds, $bounds => $bounds.width, 0)
const contentHeight = derived(
rows,
$rows => $rows.length * cellHeight + padding,
0
)
const maxScrollTop = derived(
[height, contentHeight],
([$height, $contentHeight]) => Math.max($contentHeight - $height, 0),
0
)
// Derive horizontal limits
const contentWidth = derived(
[visibleColumns, stickyColumn],
([$visibleColumns, $stickyColumn]) => {
let width = 40 + padding + ($stickyColumn?.width || 0)
$visibleColumns.forEach(col => {
width += col.width
})
return width
},
0
)
const screenWidth = derived(
[width, stickyColumn],
([$width, $stickyColumn]) => $width + 40 + ($stickyColumn?.width || 0),
0
)
const maxScrollLeft = derived(
[contentWidth, screenWidth],
([$contentWidth, $screenWidth]) => {
return Math.max($contentWidth - $screenWidth, 0)
},
0
)
// Ensure scroll state never goes invalid, which can happen when changing
// rows or tables
const overscrollTop = derived(
[scrollTop, maxScrollTop],
([$scrollTop, $maxScrollTop]) => $scrollTop > $maxScrollTop,
false
)
const overscrollLeft = derived(
[scrollLeft, maxScrollLeft],
([$scrollLeft, $maxScrollLeft]) => $scrollLeft > $maxScrollLeft,
false
)
overscrollTop.subscribe(overscroll => {
if (overscroll) {
scroll.update(state => ({
...state,
top: get(maxScrollTop),
}))
}
})
overscrollLeft.subscribe(overscroll => {
if (overscroll) {
scroll.update(state => ({
...state,
left: get(maxScrollLeft),
}))
}
})
return {
contentHeight,
contentWidth,
maxScrollTop,
maxScrollLeft,
}
}

View file

@ -0,0 +1,26 @@
import { derived } from "svelte/store"
export const createPaginationStores = context => {
const { scrolledRowCount, rows, visualRowCapacity } = context
// Derive how many rows we have in total
const rowCount = derived(rows, $rows => $rows.length, 0)
// Derive how many rows we have available to scroll
const remainingRows = derived(
[scrolledRowCount, rowCount, visualRowCapacity],
([$scrolledRowCount, $rowCount, $visualRowCapacity]) => {
return Math.max(0, $rowCount - $scrolledRowCount - $visualRowCapacity)
},
100
)
// Fetch next page when fewer than 25 remaining rows to scroll
remainingRows.subscribe(remaining => {
if (remaining < 25) {
rows.actions.loadNextPage()
}
})
return null
}

View file

@ -3,13 +3,14 @@ import { fetchData } from "../../../fetch/fetchData"
import { notifications } from "@budibase/bbui"
export const createRowsStore = context => {
const { config, API } = context
const { config, API, scroll } = context
const tableId = derived(config, $config => $config.tableId)
const rows = writable([])
const schema = writable({})
const table = writable(null)
const filter = writable([])
const loaded = writable(false)
const instanceLoaded = writable(false)
const fetch = writable(null)
const initialSortState = {
column: null,
@ -51,6 +52,7 @@ export const createRowsStore = context => {
// Unsub from previous fetch if one exists
unsubscribe?.()
fetch.set(null)
instanceLoaded.set(false)
// Reset state
sort.set(initialSortState)
@ -75,10 +77,16 @@ export const createRowsStore = context => {
// Subscribe to changes of this fetch model
unsubscribe = newFetch.subscribe($fetch => {
if ($fetch.loaded && !$fetch.loading) {
if ($fetch.pageNumber === 0) {
// Hydrate initial data
rowCacheMap = {}
rows.set([])
const resetRows = $fetch.pageNumber === 0
// Reset scroll state when data changes
if (!get(instanceLoaded)) {
// Reset both top and left for a new table ID
instanceLoaded.set(true)
scroll.set({ top: 0, left: 0 })
} else if (resetRows) {
// Only reset top scroll position when resetting rows
scroll.update(state => ({ ...state, top: 0 }))
}
// Update schema and enrich primary display into schema
@ -91,7 +99,7 @@ export const createRowsStore = context => {
table.set($fetch.definition)
// Process new rows
handleNewRows($fetch.rows)
handleNewRows($fetch.rows, resetRows)
// Notify that we're loaded
loaded.set(true)
@ -222,7 +230,10 @@ export const createRowsStore = context => {
// Local handler to process new rows inside the fetch, and append any new
// rows to state that we haven't encountered before
const handleNewRows = newRows => {
const handleNewRows = (newRows, resetRows) => {
if (resetRows) {
rowCacheMap = {}
}
let rowsToAppend = []
let newRow
for (let i = 0; i < newRows.length; i++) {
@ -232,7 +243,9 @@ export const createRowsStore = context => {
rowsToAppend.push(newRow)
}
}
if (rowsToAppend.length) {
if (resetRows) {
rows.set(rowsToAppend)
} else if (rowsToAppend.length) {
rows.update(state => [...state, ...rowsToAppend])
}
}

View file

@ -1,107 +1,11 @@
import { derived, get, writable } from "svelte/store"
import { writable } from "svelte/store"
export const createScrollStores = context => {
const { rows, visibleColumns, stickyColumn, bounds, cellHeight } = context
const padding = 180
export const createScrollStores = () => {
const scroll = writable({
left: 0,
top: 0,
})
// Memoize store primitives
const scrollTop = derived(scroll, $scroll => $scroll.top, 0)
const scrollLeft = derived(scroll, $scroll => $scroll.left, 0)
// Derive vertical limits
const height = derived(bounds, $bounds => $bounds.height, 0)
const width = derived(bounds, $bounds => $bounds.width, 0)
const contentHeight = derived(
rows,
$rows => $rows.length * cellHeight + padding,
0
)
const maxScrollTop = derived(
[height, contentHeight],
([$height, $contentHeight]) => Math.max($contentHeight - $height, 0),
0
)
// Derive horizontal limits
const contentWidth = derived(
[visibleColumns, stickyColumn],
([$visibleColumns, $stickyColumn]) => {
let width = 40 + padding + ($stickyColumn?.width || 0)
$visibleColumns.forEach(col => {
width += col.width
})
return width
},
0
)
const screenWidth = derived(
[width, stickyColumn],
([$width, $stickyColumn]) => $width + 40 + ($stickyColumn?.width || 0),
0
)
const maxScrollLeft = derived(
[contentWidth, screenWidth],
([$contentWidth, $screenWidth]) => {
return Math.max($contentWidth - $screenWidth, 0)
},
0
)
// Ensure scroll state never goes invalid, which can happen when changing
// rows or tables
const overscrollTop = derived(
[scrollTop, maxScrollTop],
([$scrollTop, $maxScrollTop]) => $scrollTop > $maxScrollTop,
false
)
const overscrollLeft = derived(
[scrollLeft, maxScrollLeft],
([$scrollLeft, $maxScrollLeft]) => $scrollLeft > $maxScrollLeft,
false
)
overscrollTop.subscribe(overscroll => {
if (overscroll) {
scroll.update(state => ({
...state,
top: get(maxScrollTop),
}))
}
})
overscrollLeft.subscribe(overscroll => {
if (overscroll) {
scroll.update(state => ({
...state,
left: get(maxScrollLeft),
}))
}
})
// Fetch next page when fewer than 50 scrollable rows remaining
const scrollableRows = derived(
[scrollTop, maxScrollTop],
([$scrollTop, $maxScrollTop]) => {
if (!$maxScrollTop) {
return 100
}
return ($maxScrollTop - $scrollTop) / cellHeight
},
100
)
scrollableRows.subscribe(count => {
if (count < 25) {
rows.actions.loadNextPage()
}
})
return {
scroll,
contentHeight,
contentWidth,
maxScrollTop,
maxScrollLeft,
}
}

View file

@ -12,14 +12,14 @@ export const createViewportStores = context => {
// Derive visible rows
// Split into multiple stores containing primitives to optimise invalidation
// as mich as possible
const firstRowIdx = derived(
const scrolledRowCount = derived(
scrollTop,
$scrollTop => {
return Math.floor($scrollTop / cellHeight)
},
0
)
const renderedRowCount = derived(
const visualRowCapacity = derived(
height,
$height => {
return Math.ceil($height / cellHeight)
@ -27,9 +27,12 @@ export const createViewportStores = context => {
0
)
const renderedRows = derived(
[rows, firstRowIdx, renderedRowCount],
([$rows, $firstRowIdx, $visibleRowCount]) => {
return $rows.slice($firstRowIdx, $firstRowIdx + $visibleRowCount)
[rows, scrolledRowCount, visualRowCapacity],
([$rows, $scrolledRowCount, $visualRowCapacity]) => {
return $rows.slice(
$scrolledRowCount,
$scrolledRowCount + $visualRowCapacity
)
},
[]
)
@ -74,5 +77,5 @@ export const createViewportStores = context => {
[]
)
return { renderedRows, renderedColumns }
return { scrolledRowCount, visualRowCapacity, renderedRows, renderedColumns }
}