Fix multiple issues, clean up rendering, improve performance
This commit is contained in:
parent
40df22d791
commit
524c46a554
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
Loading…
Reference in New Issue