diff --git a/packages/frontend-core/src/components/sheet/ResizeOverlay.svelte b/packages/frontend-core/src/components/sheet/ResizeOverlay.svelte
index aca8f59ac1..2c40f34f7d 100644
--- a/packages/frontend-core/src/components/sheet/ResizeOverlay.svelte
+++ b/packages/frontend-core/src/components/sheet/ResizeOverlay.svelte
@@ -2,7 +2,7 @@
import { get } from "svelte/store"
import { getContext } from "svelte"
- const { columns, rand, visibleColumns } = getContext("spreadsheet")
+ const { columns, rand, scroll, visibleColumns } = getContext("spreadsheet")
const MinColumnWidth = 100
let initialMouseX = null
@@ -48,56 +48,7 @@
return state
})
- // let newStyle = `--col-${columnIdx}-width:${newWidth}px;`
- //
- // let offset = left + newWidth
- // for (let i = columnIdx + 1; i < columnCount; i++) {
- // const colWidth = parseInt(styles.getPropertyValue(`--col-${i}-width`))
- // newStyle += `--col-${i}-left:${offset}px;`
- // offset += colWidth
- // }
- //
- // sheet.style.cssText += newStyle
-
- // let cells = sheet.querySelectorAll(`[data-col="${columnIdx}"]`)
- // let left
- // cells.forEach(cell => {
- // cell.style.width = `${newWidth}px`
- // cell.dataset.width = newWidth
- // if (!left) {
- // left = parseInt(cell.dataset.left)
- // }
- // })
- //
- // let offset = left + newWidth
- // for (let i = columnIdx + 1; i < columnCount; i++) {
- // cells = sheet.querySelectorAll(`[data-col="${i}"]`)
- // let colWidth
- // cells.forEach(cell => {
- // cell.style.transform = `translateX(${offset}px)`
- // cell.dataset.left = offset
- // if (!colWidth) {
- // colWidth = parseInt(cell.dataset.width)
- // }
- // })
- // offset += colWidth
- // }
-
width = newWidth
-
- // Update width of column
- // columns.update(state => {
- // state[$resize.columnIdx].width = Math.round(newWidth)
- //
- // // Update left offset of other columns
- // let offset = 40
- // state.forEach(col => {
- // col.left = offset
- // offset += col.width
- // })
- //
- // return state
- // })
}
const stopResizing = () => {
@@ -108,12 +59,14 @@
}
-{#each $columns as col}
+{#each $visibleColumns as col}
startResizing(col.idx, e)}
- style="--left:{col.left + col.width}px;"
+ style="--left:{col.left +
+ col.width -
+ (col.idx === 0 ? 0 : $scroll.left)}px;"
>
diff --git a/packages/frontend-core/src/components/sheet/Sheet.svelte b/packages/frontend-core/src/components/sheet/Sheet.svelte
index 4c3c58e5e1..637b824cb6 100644
--- a/packages/frontend-core/src/components/sheet/Sheet.svelte
+++ b/packages/frontend-core/src/components/sheet/Sheet.svelte
@@ -5,6 +5,7 @@
import { LuceneUtils } from "../../utils"
import { Icon } from "@budibase/bbui"
import { createReorderStores } from "./stores/reorder"
+ import { createViewportStores } from "./stores/viewport"
import SpreadsheetHeader from "./SheetHeader.svelte"
import SpreadsheetBody from "./SheetBody.svelte"
import SpreadsheetCell from "./SheetCell.svelte"
@@ -31,8 +32,6 @@
const selectedRows = writable({})
const changeCache = writable({})
const newRows = writable([])
- const visibleRows = writable([0, 0])
- const visibleColumns = writable([0, 0])
const scroll = writable({
left: 0,
top: 0,
@@ -57,12 +56,11 @@
changeCache,
newRows,
cellHeight,
- visibleRows,
- visibleColumns,
bounds,
scroll,
}
const { reorder, reorderPlaceholder } = createReorderStores(context)
+ const { visibleRows, visibleColumns } = createViewportStores(context)
$: query = LuceneUtils.buildLuceneQuery(filter)
$: fetch = createFetch(tableId)
@@ -76,7 +74,6 @@
$: rowCount = $rows.length
$: selectedRowCount = Object.values($selectedRows).filter(x => !!x).length
$: updateSortedRows($fetch, $newRows)
- $: renderedRows = $rows.slice($visibleRows[0], $visibleRows[1])
const createFetch = tableId => {
return fetchData({
@@ -192,7 +189,7 @@
const bIndex = newRows.indexOf(b._id)
return aIndex < bIndex ? -1 : 1
})
- $rows = sortedRows
+ $rows = sortedRows.map((x, idx) => ({ ...x, __idx: idx }))
}
// API for children to consume
@@ -206,6 +203,8 @@
...context,
reorder,
reorderPlaceholder,
+ visibleRows,
+ visibleColumns,
spreadsheetAPI,
})
@@ -221,31 +220,31 @@
checked={rowCount && selectedRowCount === rowCount}
/>
- {#each $columns as field, fieldIdx}
+ {#each $visibleColumns as column}
reorder.actions.startReordering(fieldIdx, e)}
- width={field.width}
- left={field.left}
+ sticky={column.idx === 0}
+ reorderSource={$reorder.columnIdx === column.idx}
+ reorderTarget={$reorder.swapColumnIdx === column.idx}
+ on:mousedown={e => reorder.actions.startReordering(column.idx, e)}
+ width={column.width}
+ left={column.left}
>
- {field.name}
+ {column.name}
{/each}
- {#each renderedRows as row, rowIdx (row._id)}
-
+ {#each $visibleRows as row (row._id)}
+
{/each}
@@ -260,16 +259,16 @@
>
- {#each $columns as field, fieldIdx}
+ {#each $visibleColumns as column}
addRow(field)}
+ reorderSource={$reorder.columnIdx === column.idx}
+ reorderTarget={$reorder.swapColumnIdx === column.idx}
+ on:click={() => addRow(column)}
on:mouseenter={() => ($hoveredRowId = "new")}
- width={field.width}
- left={field.left}
+ width={column.width}
+ left={column.left}
/>
{/each}
diff --git a/packages/frontend-core/src/components/sheet/SheetBody.svelte b/packages/frontend-core/src/components/sheet/SheetBody.svelte
index 12bd2dff70..fc5ea694ac 100644
--- a/packages/frontend-core/src/components/sheet/SheetBody.svelte
+++ b/packages/frontend-core/src/components/sheet/SheetBody.svelte
@@ -2,104 +2,30 @@
import { getContext, onMount } from "svelte"
import { Utils } from "../../utils"
- const {
- columns,
- selectedCellId,
- rand,
- visibleRows,
- visibleColumns,
- cellHeight,
- rows,
- bounds,
- scroll,
- } = getContext("spreadsheet")
+ const { columns, selectedCellId, rand, cellHeight, rows, bounds, scroll } =
+ getContext("spreadsheet")
const padding = 180
let ref
- let scrollLeft = 0
- let scrollTop = 0
- $: updateVisibleRows($columns, scrollTop, $bounds.height)
- $: updateVisibleColumns($columns, scrollLeft, $bounds.width)
- $: contentHeight = ($rows.length + 2) * cellHeight + padding
+ $: contentHeight = ($rows.length + 2) * cellHeight
$: contentWidth = computeContentWidth($columns)
- $: horizontallyScrolled = scrollLeft > 0
const computeContentWidth = columns => {
- let total = 40 + padding
- columns.forEach(col => {
- total += col.width
- })
- return total
+ if (!columns.length) {
+ return 0
+ }
+ const last = columns[columns.length - 1]
+ return last.left + last.width
}
- // Store the current scroll position
- // let lastTop
- // let lastLeft
- // let ticking = false
- // const handleScroll = e => {
- // lastTop = e.target.scrollTop
- // lastLeft = e.target.scrollLeft
- // if (!ticking) {
- // ticking = true
- // requestAnimationFrame(() => {
- // if (Math.abs(lastTop - scrollTop) > 100) {
- // scrollTop = lastTop
- // }
- // if (lastLeft === 0 || Math.abs(lastLeft - scrollLeft) > 100) {
- // scrollLeft = lastLeft
- // }
- // ticking = false
- // })
- // }
- // }
+ const updateScrollStore = Utils.domDebounce((left, top) => {
+ scroll.set({ left, top })
+ })
- const handleScroll = Utils.domDebounce(
- ({ left, top }) => {
- // Only update local state when big changes occur
- if (Math.abs(top - scrollTop) > 100) {
- scrollTop = top
- }
- if (left === 0 || Math.abs(left - scrollLeft) > 100) {
- scrollLeft = left
- }
-
- // Always update store
- scroll.set({ left, top })
- },
- e => ({ left: e.target.scrollLeft, top: e.target.scrollTop })
- )
-
- const updateVisibleRows = (columns, scrollTop, height) => {
- if (!columns.length) {
- return
- }
- // Compute row visibility
- const rows = Math.ceil(height / cellHeight) + 8
- const firstRow = Math.max(0, Math.floor(scrollTop / cellHeight) - 4)
- visibleRows.set([firstRow, firstRow + rows])
- }
-
- const updateVisibleColumns = (columns, scrollLeft, width) => {
- if (!columns.length) {
- return
- }
-
- // Compute column visibility
- let startColIdx = 1
- let rightEdge = columns[1].width
- while (rightEdge < scrollLeft) {
- startColIdx++
- rightEdge += columns[startColIdx].width
- }
- let endColIdx = startColIdx + 1
- let leftEdge = columns[0].width + 40 + rightEdge
- while (leftEdge < width + scrollLeft) {
- leftEdge += columns[endColIdx]?.width
- endColIdx++
- }
- visibleColumns.set([Math.max(1, startColIdx - 2), endColIdx + 2])
+ const handleScroll = e => {
+ updateScrollStore(e.target.scrollLeft, e.target.scrollTop)
}
onMount(() => {
@@ -117,16 +43,22 @@
0}
on:click|self={() => ($selectedCellId = null)}
id={`sheet-${rand}-body`}
on:scroll={handleScroll}
>
@@ -140,11 +72,19 @@
height: 0;
}
.sheet-body::-webkit-scrollbar-track {
- background: var(--cell-background);
+ background: transparent;
+ }
+ .content,
+ .data-content {
+ position: absolute;
}
.content {
min-width: 100%;
min-height: 100%;
+ background: var(--background-alt);
+ }
+ .data-content {
+ background: var(--cell-background);
}
/* Add shadow to sticky cells when horizontally scrolled */
diff --git a/packages/frontend-core/src/components/sheet/SheetRow.svelte b/packages/frontend-core/src/components/sheet/SheetRow.svelte
index 0a174e1548..03c6336f95 100644
--- a/packages/frontend-core/src/components/sheet/SheetRow.svelte
+++ b/packages/frontend-core/src/components/sheet/SheetRow.svelte
@@ -9,13 +9,11 @@
import TextCell from "./cells/TextCell.svelte"
export let row
- export let rowIdx
const {
selectedCellId,
reorder,
hoveredRowId,
- columns,
selectedRows,
changeCache,
spreadsheetAPI,
@@ -26,10 +24,6 @@
$: rowSelected = !!$selectedRows[row._id]
$: rowHovered = $hoveredRowId === row._id
$: data = { ...row, ...$changeCache[row._id] }
- $: renderedColumns = [
- $columns[0],
- ...$columns.slice($visibleColumns[0], $visibleColumns[1]),
- ]
$: containsSelectedCell = $selectedCellId?.split("-")[0] === row._id
const getCellForField = field => {
@@ -58,7 +52,7 @@
{:else}
- {rowIdx + 1}
+ {row.__idx + 1}
{/if}
- {#each renderedColumns as column (column.name)}
+ {#each $visibleColumns as column (column.name)}
{@const cellIdx = `${row._id}-${column.name}`}
{
+ 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)
+
+ // 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 }) => {
+ // Only update local state when big changes occur
+ if (Math.abs(top - scrollTop) > cellHeight * 2) {
+ scrollTop = top
+ scrollTopStore.set(top)
+ }
+ if (Math.abs(left - scrollLeft) > 100) {
+ scrollLeft = left
+ scrollLeftStore.set(left)
+ }
+ })
+
+ // Derive visible rows
+ const visibleRows = derived(
+ [rows, scrollTopStore, height],
+ ([$rows, $scrollTop, $height]) => {
+ console.log("new rows")
+ const maxRows = Math.ceil($height / cellHeight) + 8
+ const firstRow = Math.max(0, Math.floor($scrollTop / cellHeight) - 4)
+ return $rows.slice(firstRow, firstRow + maxRows)
+ }
+ )
+
+ // Derive visible columns
+ const visibleColumns = derived(
+ [columns, scrollLeftStore, width],
+ ([$columns, $scrollLeft, $width]) => {
+ console.log("new columns")
+ if (!$columns.length) {
+ return []
+ }
+ let startColIdx = 1
+ let rightEdge = $columns[1].width
+ while (rightEdge < $scrollLeft) {
+ startColIdx++
+ rightEdge += $columns[startColIdx].width
+ }
+ let endColIdx = startColIdx + 1
+ let leftEdge = $columns[0].width + 40 + rightEdge
+ while (leftEdge < $width + $scrollLeft) {
+ leftEdge += $columns[endColIdx]?.width
+ endColIdx++
+ }
+ return [
+ $columns[0],
+ ...$columns.slice(Math.max(1, startColIdx - 2), endColIdx + 2),
+ ]
+ }
+ )
+
+ return { visibleRows, visibleColumns }
+}
diff --git a/packages/frontend-core/src/utils/utils.js b/packages/frontend-core/src/utils/utils.js
index 5b23fa4d93..46d8395f77 100644
--- a/packages/frontend-core/src/utils/utils.js
+++ b/packages/frontend-core/src/utils/utils.js
@@ -90,18 +90,17 @@ export const throttle = (callback, minDelay = 1000) => {
/**
* Utility to debounce DOM activities using requestAnimationFrame
* @param callback the function to run
- * @param extractParams
* @returns {Function}
*/
-export const domDebounce = (callback, extractParams = x => x) => {
+export const domDebounce = callback => {
let active = false
let lastParams
return (...params) => {
- lastParams = extractParams(...params)
+ lastParams = params
if (!active) {
active = true
requestAnimationFrame(() => {
- callback(lastParams)
+ callback(...lastParams)
active = false
})
}