Fix multiple issues, clean up rendering, improve performance
This commit is contained in:
parent
40df22d791
commit
524c46a554
9 changed files with 79 additions and 68 deletions
|
@ -38,13 +38,5 @@
|
|||
<style>
|
||||
.row {
|
||||
display: flex;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
width: inherit;
|
||||
z-index: 10;
|
||||
height: var(--cell-height);
|
||||
}
|
||||
.row :global(> :last-child) {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -29,8 +29,6 @@
|
|||
<style>
|
||||
.row {
|
||||
display: flex;
|
||||
width: inherit;
|
||||
height: var(--cell-height);
|
||||
}
|
||||
:global(.sheet:not(.is-resizing):not(.is-reordering) .row:hover .cell) {
|
||||
background: var(--cell-background-hover);
|
||||
|
|
|
@ -20,22 +20,43 @@
|
|||
|
||||
// Calculate V scrollbar size and offset
|
||||
$: contentHeight = ($rows.length + 1) * cellHeight
|
||||
$: barHeight = Math.max(50, (height / contentHeight) * height)
|
||||
$: availHeight = height - barHeight - 2 * barOffset
|
||||
$: maxScrollTop = contentHeight - height
|
||||
$: renderHeight = height - 2 * barOffset
|
||||
$: barHeight = Math.max(50, (height / contentHeight) * renderHeight)
|
||||
$: availHeight = renderHeight - barHeight
|
||||
$: maxScrollTop = Math.max(contentHeight - height, 0)
|
||||
$: barTop = barOffset + cellHeight + availHeight * (scrollTop / maxScrollTop)
|
||||
|
||||
// Calculate H scrollbar size and offset
|
||||
$: contentWidth = calculateContentWidth($columns, $stickyColumn)
|
||||
$: totalWidth = width + 40 + $stickyColumn?.width || 0
|
||||
$: barWidth = Math.max(50, (totalWidth / contentWidth) * totalWidth)
|
||||
$: availWidth = totalWidth - barWidth - 2 * barOffset
|
||||
$: maxScrollLeft = contentWidth - totalWidth
|
||||
$: renderWidth = totalWidth - 2 * barOffset
|
||||
$: barWidth = Math.max(50, (totalWidth / contentWidth) * renderWidth)
|
||||
$: availWidth = renderWidth - barWidth
|
||||
$: maxScrollLeft = Math.max(contentWidth - totalWidth, 0)
|
||||
$: barLeft = barOffset + availWidth * (scrollLeft / maxScrollLeft)
|
||||
|
||||
// Calculate whether to show scrollbars or not
|
||||
$: 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) => {
|
||||
let width = 40 + stickyColumn?.width
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
import { createAPIClient } from "../../api"
|
||||
import ScrollOverlay from "./ScrollOverlay.svelte"
|
||||
import StickyColumn from "./StickyColumn.svelte"
|
||||
import SheetScrollWrapper from "./SheetScrollWrapper.svelte"
|
||||
|
||||
export let tableId
|
||||
export let filter
|
||||
|
@ -49,7 +48,6 @@
|
|||
rand,
|
||||
selectedCellId,
|
||||
selectedRows,
|
||||
|
||||
cellHeight,
|
||||
bounds,
|
||||
scroll,
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
export let selected = false
|
||||
export let reorderSource = false
|
||||
export let reorderTarget = false
|
||||
export let left
|
||||
export let width
|
||||
</script>
|
||||
|
||||
|
@ -25,7 +24,7 @@
|
|||
on:mouseenter
|
||||
on:click
|
||||
on:mousedown
|
||||
style="--width:{width}px; --left:{left}px;"
|
||||
style="--width:{width}px;"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
|
@ -49,9 +48,8 @@
|
|||
background: var(--cell-background);
|
||||
transition: border-color 130ms ease-out;
|
||||
flex: 0 0 var(--width);
|
||||
position: absolute;
|
||||
left: var(--left);
|
||||
width: var(--width);
|
||||
position: relative;
|
||||
width: 0;
|
||||
}
|
||||
.cell.selected {
|
||||
box-shadow: inset 0 0 0 2px var(--spectrum-global-color-blue-400);
|
||||
|
@ -72,7 +70,6 @@
|
|||
background: var(--background);
|
||||
padding: 0 var(--cell-padding);
|
||||
border-color: var(--spectrum-global-color-gray-200);
|
||||
font-weight: 600;
|
||||
gap: calc(2 * var(--cell-spacing));
|
||||
z-index: 10;
|
||||
}
|
||||
|
@ -86,10 +83,6 @@
|
|||
.cell.header:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.cell.header.sticky,
|
||||
.cell.header.label {
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
/* Reorder styles */
|
||||
.cell.reorder-source {
|
||||
|
|
|
@ -48,9 +48,6 @@
|
|||
<style>
|
||||
.row {
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: inherit;
|
||||
height: var(--cell-height);
|
||||
}
|
||||
:global(.sheet:not(.is-resizing):not(.is-reordering) .row:hover .cell) {
|
||||
background: var(--cell-background-hover);
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
|
||||
const { cellHeight, scroll, bounds, rows, columns, visibleRows } =
|
||||
getContext("spreadsheet")
|
||||
const {
|
||||
cellHeight,
|
||||
scroll,
|
||||
bounds,
|
||||
rows,
|
||||
columns,
|
||||
visibleRows,
|
||||
visibleColumns,
|
||||
} = getContext("spreadsheet")
|
||||
|
||||
export let scrollVertically = true
|
||||
export let scrollHorizontally = true
|
||||
|
@ -11,9 +18,10 @@
|
|||
$: scrollTop = $scroll.top
|
||||
$: scrollLeft = $scroll.left
|
||||
$: offsetY = scrollVertically ? -1 * (scrollTop % cellHeight) : 0
|
||||
$: offsetX = scrollHorizontally ? -1 * scrollLeft : 0
|
||||
$: hiddenWidths = calculateHiddenWidths($visibleColumns)
|
||||
$: offsetX = scrollHorizontally ? -1 * scrollLeft + hiddenWidths : 0
|
||||
$: rowCount = $visibleRows.length
|
||||
$: contentWidth = calculateContentWidth($columns, scrollHorizontally)
|
||||
$: contentWidth = calculateContentWidth($visibleColumns, scrollHorizontally)
|
||||
$: contentHeight = calculateContentHeight(rowCount, scrollVertically)
|
||||
$: style = getStyle(offsetX, offsetY, contentWidth, contentHeight)
|
||||
|
||||
|
@ -28,6 +36,17 @@
|
|||
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) => {
|
||||
if (!scroll) {
|
||||
return null
|
||||
|
@ -77,6 +96,5 @@
|
|||
overflow: hidden;
|
||||
width: var(--width);
|
||||
height: var(--height);
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,7 +4,7 @@ import { fetchData } from "../../../fetch/fetchData"
|
|||
import { notifications } from "@budibase/bbui"
|
||||
|
||||
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
|
||||
let loaded = false
|
||||
|
@ -61,12 +61,17 @@ export const createRowsStore = context => {
|
|||
loaded = true
|
||||
rowCacheMap = {}
|
||||
rows.set([])
|
||||
|
||||
// Enrich primary display into schema
|
||||
let newSchema = $$fetch.schema
|
||||
const primaryDisplay = $$fetch.definition?.primaryDisplay
|
||||
if (primaryDisplay && newSchema[primaryDisplay]) {
|
||||
newSchema[primaryDisplay].primaryDisplay = true
|
||||
}
|
||||
schema.set(newSchema)
|
||||
|
||||
// Reset scroll state for fresh dataset
|
||||
scroll.set({ left: 0, top: 0 })
|
||||
}
|
||||
|
||||
// Process new rows
|
||||
|
|
|
@ -1,64 +1,53 @@
|
|||
import { writable, derived, get } from "svelte/store"
|
||||
import { derived, get } from "svelte/store"
|
||||
|
||||
export const createViewportStores = context => {
|
||||
const { cellHeight, columns, rows, scroll, bounds } = context
|
||||
|
||||
// Use local variables to avoid needing to invoke 2 svelte getters each time
|
||||
// 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)
|
||||
const scrollTop = derived(scroll, $scroll => $scroll.top, 0)
|
||||
const scrollLeft = derived(scroll, $scroll => $scroll.left, 0)
|
||||
|
||||
// Derive height and width as primitives to avoid wasted computation
|
||||
const width = derived(bounds, $bounds => $bounds.width)
|
||||
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
|
||||
// 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(
|
||||
[rows, scrollTopStore, height],
|
||||
([$rows, $scrollTop, $height]) => {
|
||||
const maxRows = Math.ceil($height / cellHeight) + 1
|
||||
const firstRow = Math.max(0, Math.floor($scrollTop / cellHeight))
|
||||
return $rows.slice(firstRow, firstRow + maxRows)
|
||||
[rows, firstRowIdx, visibleRowCount],
|
||||
([$rows, $firstRowIdx, $visibleRowCount]) => {
|
||||
return $rows.slice($firstRowIdx, $firstRowIdx + $visibleRowCount)
|
||||
}
|
||||
)
|
||||
|
||||
// Derive visible columns
|
||||
const visibleColumns = derived(
|
||||
[columns, scrollLeftStore, width],
|
||||
[columns, scrollLeft, width],
|
||||
([$columns, $scrollLeft, $width]) => {
|
||||
if (!$columns.length) {
|
||||
return []
|
||||
}
|
||||
let startColIdx = 0
|
||||
let rightEdge = $columns[0].width
|
||||
while (rightEdge < $scrollLeft) {
|
||||
while (rightEdge < $scrollLeft && startColIdx < $columns.length - 1) {
|
||||
startColIdx++
|
||||
rightEdge += $columns[startColIdx].width
|
||||
}
|
||||
let endColIdx = startColIdx + 1
|
||||
let leftEdge = rightEdge
|
||||
while (leftEdge < $width + $scrollLeft) {
|
||||
leftEdge += $columns[endColIdx]?.width
|
||||
while (leftEdge < $width + $scrollLeft && endColIdx < $columns.length) {
|
||||
leftEdge += $columns[endColIdx].width
|
||||
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
|
||||
visibleRows.subscribe($visibleRows => {
|
||||
const lastVisible = $visibleRows[$visibleRows.length - 1]
|
||||
|
|
Loading…
Reference in a new issue