Fix multiple issues, clean up rendering, improve performance

This commit is contained in:
Andrew Kingston 2023-03-01 16:10:24 +00:00
parent 40df22d791
commit 524c46a554
9 changed files with 79 additions and 68 deletions

View File

@ -38,13 +38,5 @@
<style> <style>
.row { .row {
display: flex; display: flex;
position: sticky;
top: 0;
width: inherit;
z-index: 10;
height: var(--cell-height);
}
.row :global(> :last-child) {
border-right-width: 1px;
} }
</style> </style>

View File

@ -29,8 +29,6 @@
<style> <style>
.row { .row {
display: flex; display: flex;
width: inherit;
height: var(--cell-height);
} }
:global(.sheet:not(.is-resizing):not(.is-reordering) .row:hover .cell) { :global(.sheet:not(.is-resizing):not(.is-reordering) .row:hover .cell) {
background: var(--cell-background-hover); background: var(--cell-background-hover);

View File

@ -20,22 +20,43 @@
// Calculate V scrollbar size and offset // Calculate V scrollbar size and offset
$: contentHeight = ($rows.length + 1) * cellHeight $: contentHeight = ($rows.length + 1) * cellHeight
$: barHeight = Math.max(50, (height / contentHeight) * height) $: renderHeight = height - 2 * barOffset
$: availHeight = height - barHeight - 2 * barOffset $: barHeight = Math.max(50, (height / contentHeight) * renderHeight)
$: maxScrollTop = contentHeight - height $: availHeight = renderHeight - barHeight
$: maxScrollTop = Math.max(contentHeight - height, 0)
$: barTop = barOffset + cellHeight + availHeight * (scrollTop / maxScrollTop) $: barTop = barOffset + cellHeight + availHeight * (scrollTop / maxScrollTop)
// Calculate H scrollbar size and offset // Calculate H scrollbar size and offset
$: contentWidth = calculateContentWidth($columns, $stickyColumn) $: contentWidth = calculateContentWidth($columns, $stickyColumn)
$: totalWidth = width + 40 + $stickyColumn?.width || 0 $: totalWidth = width + 40 + $stickyColumn?.width || 0
$: barWidth = Math.max(50, (totalWidth / contentWidth) * totalWidth) $: renderWidth = totalWidth - 2 * barOffset
$: availWidth = totalWidth - barWidth - 2 * barOffset $: barWidth = Math.max(50, (totalWidth / contentWidth) * renderWidth)
$: maxScrollLeft = contentWidth - totalWidth $: availWidth = renderWidth - barWidth
$: maxScrollLeft = Math.max(contentWidth - totalWidth, 0)
$: barLeft = barOffset + availWidth * (scrollLeft / maxScrollLeft) $: barLeft = barOffset + availWidth * (scrollLeft / maxScrollLeft)
// Calculate whether to show scrollbars or not // Calculate whether to show scrollbars or not
$: showVScrollbar = contentHeight > height $: showVScrollbar = contentHeight > height
$: showHScrollbar = contentWidth > width $: showHScrollbar = contentWidth > totalWidth
// Ensure scroll state never goes invalid, which can happen when changing
// rows or tables
$: {
if (scrollTop > maxScrollTop) {
scroll.update(state => ({
...state,
top: maxScrollTop,
}))
}
}
$: {
if (scrollLeft > maxScrollLeft) {
scroll.update(state => ({
...state,
left: maxScrollLeft,
}))
}
}
const calculateContentWidth = (columns, stickyColumn) => { const calculateContentWidth = (columns, stickyColumn) => {
let width = 40 + stickyColumn?.width let width = 40 + stickyColumn?.width

View File

@ -16,7 +16,6 @@
import { createAPIClient } from "../../api" import { createAPIClient } from "../../api"
import ScrollOverlay from "./ScrollOverlay.svelte" import ScrollOverlay from "./ScrollOverlay.svelte"
import StickyColumn from "./StickyColumn.svelte" import StickyColumn from "./StickyColumn.svelte"
import SheetScrollWrapper from "./SheetScrollWrapper.svelte"
export let tableId export let tableId
export let filter export let filter
@ -49,7 +48,6 @@
rand, rand,
selectedCellId, selectedCellId,
selectedRows, selectedRows,
cellHeight, cellHeight,
bounds, bounds,
scroll, scroll,

View File

@ -8,7 +8,6 @@
export let selected = false export let selected = false
export let reorderSource = false export let reorderSource = false
export let reorderTarget = false export let reorderTarget = false
export let left
export let width export let width
</script> </script>
@ -25,7 +24,7 @@
on:mouseenter on:mouseenter
on:click on:click
on:mousedown on:mousedown
style="--width:{width}px; --left:{left}px;" style="--width:{width}px;"
> >
<slot /> <slot />
</div> </div>
@ -49,9 +48,8 @@
background: var(--cell-background); background: var(--cell-background);
transition: border-color 130ms ease-out; transition: border-color 130ms ease-out;
flex: 0 0 var(--width); flex: 0 0 var(--width);
position: absolute; position: relative;
left: var(--left); width: 0;
width: var(--width);
} }
.cell.selected { .cell.selected {
box-shadow: inset 0 0 0 2px var(--spectrum-global-color-blue-400); box-shadow: inset 0 0 0 2px var(--spectrum-global-color-blue-400);
@ -72,7 +70,6 @@
background: var(--background); background: var(--background);
padding: 0 var(--cell-padding); padding: 0 var(--cell-padding);
border-color: var(--spectrum-global-color-gray-200); border-color: var(--spectrum-global-color-gray-200);
font-weight: 600;
gap: calc(2 * var(--cell-spacing)); gap: calc(2 * var(--cell-spacing));
z-index: 10; z-index: 10;
} }
@ -86,10 +83,6 @@
.cell.header:hover { .cell.header:hover {
cursor: pointer; cursor: pointer;
} }
.cell.header.sticky,
.cell.header.label {
z-index: 11;
}
/* Reorder styles */ /* Reorder styles */
.cell.reorder-source { .cell.reorder-source {

View File

@ -48,9 +48,6 @@
<style> <style>
.row { .row {
display: flex; display: flex;
position: relative;
width: inherit;
height: var(--cell-height);
} }
:global(.sheet:not(.is-resizing):not(.is-reordering) .row:hover .cell) { :global(.sheet:not(.is-resizing):not(.is-reordering) .row:hover .cell) {
background: var(--cell-background-hover); background: var(--cell-background-hover);

View File

@ -1,8 +1,15 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
const { cellHeight, scroll, bounds, rows, columns, visibleRows } = const {
getContext("spreadsheet") cellHeight,
scroll,
bounds,
rows,
columns,
visibleRows,
visibleColumns,
} = getContext("spreadsheet")
export let scrollVertically = true export let scrollVertically = true
export let scrollHorizontally = true export let scrollHorizontally = true
@ -11,9 +18,10 @@
$: scrollTop = $scroll.top $: scrollTop = $scroll.top
$: scrollLeft = $scroll.left $: scrollLeft = $scroll.left
$: offsetY = scrollVertically ? -1 * (scrollTop % cellHeight) : 0 $: offsetY = scrollVertically ? -1 * (scrollTop % cellHeight) : 0
$: offsetX = scrollHorizontally ? -1 * scrollLeft : 0 $: hiddenWidths = calculateHiddenWidths($visibleColumns)
$: offsetX = scrollHorizontally ? -1 * scrollLeft + hiddenWidths : 0
$: rowCount = $visibleRows.length $: rowCount = $visibleRows.length
$: contentWidth = calculateContentWidth($columns, scrollHorizontally) $: contentWidth = calculateContentWidth($visibleColumns, scrollHorizontally)
$: contentHeight = calculateContentHeight(rowCount, scrollVertically) $: contentHeight = calculateContentHeight(rowCount, scrollVertically)
$: style = getStyle(offsetX, offsetY, contentWidth, contentHeight) $: style = getStyle(offsetX, offsetY, contentWidth, contentHeight)
@ -28,6 +36,17 @@
return style return style
} }
const calculateHiddenWidths = visibleColumns => {
const idx = visibleColumns[0]?.idx
let width = 0
if (idx > 0) {
for (let i = 0; i < idx; i++) {
width += $columns[i].width
}
}
return width
}
const calculateContentWidth = (columns, scroll) => { const calculateContentWidth = (columns, scroll) => {
if (!scroll) { if (!scroll) {
return null return null
@ -77,6 +96,5 @@
overflow: hidden; overflow: hidden;
width: var(--width); width: var(--width);
height: var(--height); height: var(--height);
position: relative;
} }
</style> </style>

View File

@ -4,7 +4,7 @@ import { fetchData } from "../../../fetch/fetchData"
import { notifications } from "@budibase/bbui" import { notifications } from "@budibase/bbui"
export const createRowsStore = context => { export const createRowsStore = context => {
const { tableId, filter, API } = context const { tableId, filter, API, scroll } = context
// Flag for whether this is the first time loading our fetch // Flag for whether this is the first time loading our fetch
let loaded = false let loaded = false
@ -61,12 +61,17 @@ export const createRowsStore = context => {
loaded = true loaded = true
rowCacheMap = {} rowCacheMap = {}
rows.set([]) rows.set([])
// Enrich primary display into schema
let newSchema = $$fetch.schema let newSchema = $$fetch.schema
const primaryDisplay = $$fetch.definition?.primaryDisplay const primaryDisplay = $$fetch.definition?.primaryDisplay
if (primaryDisplay && newSchema[primaryDisplay]) { if (primaryDisplay && newSchema[primaryDisplay]) {
newSchema[primaryDisplay].primaryDisplay = true newSchema[primaryDisplay].primaryDisplay = true
} }
schema.set(newSchema) schema.set(newSchema)
// Reset scroll state for fresh dataset
scroll.set({ left: 0, top: 0 })
} }
// Process new rows // Process new rows

View File

@ -1,64 +1,53 @@
import { writable, derived, get } from "svelte/store" import { derived, get } from "svelte/store"
export const createViewportStores = context => { export const createViewportStores = context => {
const { cellHeight, columns, rows, scroll, bounds } = context const { cellHeight, columns, rows, scroll, bounds } = context
const scrollTop = derived(scroll, $scroll => $scroll.top, 0)
// Use local variables to avoid needing to invoke 2 svelte getters each time const scrollLeft = derived(scroll, $scroll => $scroll.left, 0)
// scroll state changes, but also use stores to allow use of derived stores
let scrollTop = 0
let scrollLeft = 0
const scrollTopStore = writable(0)
const scrollLeftStore = writable(0)
// Derive height and width as primitives to avoid wasted computation // Derive height and width as primitives to avoid wasted computation
const width = derived(bounds, $bounds => $bounds.width) const width = derived(bounds, $bounds => $bounds.width)
const height = derived(bounds, $bounds => $bounds.height) const height = derived(bounds, $bounds => $bounds.height)
// Debounce scroll updates so we can slow down visible row computation
scroll.subscribe(({ left, top }) => {
scrollTop = top
scrollTopStore.set(top)
scrollLeft = left
scrollLeftStore.set(left)
})
// Derive visible rows // Derive visible rows
// Split into multiple stores containing primitives to optimise invalidation
// as mich as possible
const firstRowIdx = derived(scrollTop, $scrollTop => {
return Math.floor($scrollTop / cellHeight)
})
const visibleRowCount = derived(height, $height => {
return Math.ceil($height / cellHeight)
})
const visibleRows = derived( const visibleRows = derived(
[rows, scrollTopStore, height], [rows, firstRowIdx, visibleRowCount],
([$rows, $scrollTop, $height]) => { ([$rows, $firstRowIdx, $visibleRowCount]) => {
const maxRows = Math.ceil($height / cellHeight) + 1 return $rows.slice($firstRowIdx, $firstRowIdx + $visibleRowCount)
const firstRow = Math.max(0, Math.floor($scrollTop / cellHeight))
return $rows.slice(firstRow, firstRow + maxRows)
} }
) )
// Derive visible columns // Derive visible columns
const visibleColumns = derived( const visibleColumns = derived(
[columns, scrollLeftStore, width], [columns, scrollLeft, width],
([$columns, $scrollLeft, $width]) => { ([$columns, $scrollLeft, $width]) => {
if (!$columns.length) { if (!$columns.length) {
return [] return []
} }
let startColIdx = 0 let startColIdx = 0
let rightEdge = $columns[0].width let rightEdge = $columns[0].width
while (rightEdge < $scrollLeft) { while (rightEdge < $scrollLeft && startColIdx < $columns.length - 1) {
startColIdx++ startColIdx++
rightEdge += $columns[startColIdx].width rightEdge += $columns[startColIdx].width
} }
let endColIdx = startColIdx + 1 let endColIdx = startColIdx + 1
let leftEdge = rightEdge let leftEdge = rightEdge
while (leftEdge < $width + $scrollLeft) { while (leftEdge < $width + $scrollLeft && endColIdx < $columns.length) {
leftEdge += $columns[endColIdx]?.width leftEdge += $columns[endColIdx].width
endColIdx++ endColIdx++
} }
return $columns.slice(Math.max(0, startColIdx - 1), endColIdx + 1) return $columns.slice(startColIdx, endColIdx)
} }
) )
// visibleColumns.subscribe(state => {
// console.log(state)
// })
// Fetch next page when approaching end of data // Fetch next page when approaching end of data
visibleRows.subscribe($visibleRows => { visibleRows.subscribe($visibleRows => {
const lastVisible = $visibleRows[$visibleRows.length - 1] const lastVisible = $visibleRows[$visibleRows.length - 1]