Reset scrolling when datasource changes and fix wasted pagination calls
This commit is contained in:
parent
d7666272e0
commit
e76c541627
|
@ -15,6 +15,8 @@
|
||||||
import { createUserStores } from "./stores/users"
|
import { createUserStores } from "./stores/users"
|
||||||
import { createResizeStores } from "./stores/resize"
|
import { createResizeStores } from "./stores/resize"
|
||||||
import { createMenuStores } from "./stores/menu"
|
import { createMenuStores } from "./stores/menu"
|
||||||
|
import { createMaxScrollStores } from "./stores/max-scroll"
|
||||||
|
import { createPaginationStores } from "./stores/pagination"
|
||||||
import DeleteButton from "./DeleteButton.svelte"
|
import DeleteButton from "./DeleteButton.svelte"
|
||||||
import SheetBody from "./SheetBody.svelte"
|
import SheetBody from "./SheetBody.svelte"
|
||||||
import ResizeOverlay from "./ResizeOverlay.svelte"
|
import ResizeOverlay from "./ResizeOverlay.svelte"
|
||||||
|
@ -57,16 +59,18 @@
|
||||||
config,
|
config,
|
||||||
}
|
}
|
||||||
context = { ...context, ...createEventManagers() }
|
context = { ...context, ...createEventManagers() }
|
||||||
context = { ...context, ...createRowsStore(context) }
|
|
||||||
context = { ...context, ...createColumnsStores(context) }
|
|
||||||
context = { ...context, ...createResizeStores(context) }
|
|
||||||
context = { ...context, ...createBoundsStores(context) }
|
context = { ...context, ...createBoundsStores(context) }
|
||||||
context = { ...context, ...createScrollStores(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, ...createViewportStores(context) }
|
||||||
context = { ...context, ...createReorderStores(context) }
|
context = { ...context, ...createReorderStores(context) }
|
||||||
context = { ...context, ...createUIStores(context) }
|
context = { ...context, ...createUIStores(context) }
|
||||||
context = { ...context, ...createUserStores(context) }
|
context = { ...context, ...createUserStores(context) }
|
||||||
context = { ...context, ...createMenuStores(context) }
|
context = { ...context, ...createMenuStores(context) }
|
||||||
|
context = { ...context, ...createPaginationStores(context) }
|
||||||
|
|
||||||
// Reference some stores for local use
|
// Reference some stores for local use
|
||||||
const { isResizing, isReordering, ui, loaded } = context
|
const { isResizing, isReordering, ui, loaded } = context
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -3,13 +3,14 @@ import { fetchData } from "../../../fetch/fetchData"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
|
|
||||||
export const createRowsStore = context => {
|
export const createRowsStore = context => {
|
||||||
const { config, API } = context
|
const { config, API, scroll } = context
|
||||||
const tableId = derived(config, $config => $config.tableId)
|
const tableId = derived(config, $config => $config.tableId)
|
||||||
const rows = writable([])
|
const rows = writable([])
|
||||||
const schema = writable({})
|
const schema = writable({})
|
||||||
const table = writable(null)
|
const table = writable(null)
|
||||||
const filter = writable([])
|
const filter = writable([])
|
||||||
const loaded = writable(false)
|
const loaded = writable(false)
|
||||||
|
const instanceLoaded = writable(false)
|
||||||
const fetch = writable(null)
|
const fetch = writable(null)
|
||||||
const initialSortState = {
|
const initialSortState = {
|
||||||
column: null,
|
column: null,
|
||||||
|
@ -51,6 +52,7 @@ export const createRowsStore = context => {
|
||||||
// Unsub from previous fetch if one exists
|
// Unsub from previous fetch if one exists
|
||||||
unsubscribe?.()
|
unsubscribe?.()
|
||||||
fetch.set(null)
|
fetch.set(null)
|
||||||
|
instanceLoaded.set(false)
|
||||||
|
|
||||||
// Reset state
|
// Reset state
|
||||||
sort.set(initialSortState)
|
sort.set(initialSortState)
|
||||||
|
@ -75,10 +77,16 @@ export const createRowsStore = context => {
|
||||||
// Subscribe to changes of this fetch model
|
// Subscribe to changes of this fetch model
|
||||||
unsubscribe = newFetch.subscribe($fetch => {
|
unsubscribe = newFetch.subscribe($fetch => {
|
||||||
if ($fetch.loaded && !$fetch.loading) {
|
if ($fetch.loaded && !$fetch.loading) {
|
||||||
if ($fetch.pageNumber === 0) {
|
const resetRows = $fetch.pageNumber === 0
|
||||||
// Hydrate initial data
|
|
||||||
rowCacheMap = {}
|
// Reset scroll state when data changes
|
||||||
rows.set([])
|
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
|
// Update schema and enrich primary display into schema
|
||||||
|
@ -91,7 +99,7 @@ export const createRowsStore = context => {
|
||||||
table.set($fetch.definition)
|
table.set($fetch.definition)
|
||||||
|
|
||||||
// Process new rows
|
// Process new rows
|
||||||
handleNewRows($fetch.rows)
|
handleNewRows($fetch.rows, resetRows)
|
||||||
|
|
||||||
// Notify that we're loaded
|
// Notify that we're loaded
|
||||||
loaded.set(true)
|
loaded.set(true)
|
||||||
|
@ -222,7 +230,10 @@ export const createRowsStore = context => {
|
||||||
|
|
||||||
// Local handler to process new rows inside the fetch, and append any new
|
// Local handler to process new rows inside the fetch, and append any new
|
||||||
// rows to state that we haven't encountered before
|
// rows to state that we haven't encountered before
|
||||||
const handleNewRows = newRows => {
|
const handleNewRows = (newRows, resetRows) => {
|
||||||
|
if (resetRows) {
|
||||||
|
rowCacheMap = {}
|
||||||
|
}
|
||||||
let rowsToAppend = []
|
let rowsToAppend = []
|
||||||
let newRow
|
let newRow
|
||||||
for (let i = 0; i < newRows.length; i++) {
|
for (let i = 0; i < newRows.length; i++) {
|
||||||
|
@ -232,7 +243,9 @@ export const createRowsStore = context => {
|
||||||
rowsToAppend.push(newRow)
|
rowsToAppend.push(newRow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rowsToAppend.length) {
|
if (resetRows) {
|
||||||
|
rows.set(rowsToAppend)
|
||||||
|
} else if (rowsToAppend.length) {
|
||||||
rows.update(state => [...state, ...rowsToAppend])
|
rows.update(state => [...state, ...rowsToAppend])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,107 +1,11 @@
|
||||||
import { derived, get, writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
export const createScrollStores = context => {
|
export const createScrollStores = () => {
|
||||||
const { rows, visibleColumns, stickyColumn, bounds, cellHeight } = context
|
|
||||||
const padding = 180
|
|
||||||
const scroll = writable({
|
const scroll = writable({
|
||||||
left: 0,
|
left: 0,
|
||||||
top: 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 {
|
return {
|
||||||
scroll,
|
scroll,
|
||||||
contentHeight,
|
|
||||||
contentWidth,
|
|
||||||
maxScrollTop,
|
|
||||||
maxScrollLeft,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,14 +12,14 @@ export const createViewportStores = context => {
|
||||||
// Derive visible rows
|
// Derive visible rows
|
||||||
// Split into multiple stores containing primitives to optimise invalidation
|
// Split into multiple stores containing primitives to optimise invalidation
|
||||||
// as mich as possible
|
// as mich as possible
|
||||||
const firstRowIdx = derived(
|
const scrolledRowCount = derived(
|
||||||
scrollTop,
|
scrollTop,
|
||||||
$scrollTop => {
|
$scrollTop => {
|
||||||
return Math.floor($scrollTop / cellHeight)
|
return Math.floor($scrollTop / cellHeight)
|
||||||
},
|
},
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
const renderedRowCount = derived(
|
const visualRowCapacity = derived(
|
||||||
height,
|
height,
|
||||||
$height => {
|
$height => {
|
||||||
return Math.ceil($height / cellHeight)
|
return Math.ceil($height / cellHeight)
|
||||||
|
@ -27,9 +27,12 @@ export const createViewportStores = context => {
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
const renderedRows = derived(
|
const renderedRows = derived(
|
||||||
[rows, firstRowIdx, renderedRowCount],
|
[rows, scrolledRowCount, visualRowCapacity],
|
||||||
([$rows, $firstRowIdx, $visibleRowCount]) => {
|
([$rows, $scrolledRowCount, $visualRowCapacity]) => {
|
||||||
return $rows.slice($firstRowIdx, $firstRowIdx + $visibleRowCount)
|
return $rows.slice(
|
||||||
|
$scrolledRowCount,
|
||||||
|
$scrolledRowCount + $visualRowCapacity
|
||||||
|
)
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
@ -74,5 +77,5 @@ export const createViewportStores = context => {
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
return { renderedRows, renderedColumns }
|
return { scrolledRowCount, visualRowCapacity, renderedRows, renderedColumns }
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue