Refactor stores to fix dependency issues, use modals for adding rows, simplify sheet
This commit is contained in:
parent
81a28eb4da
commit
fe70793e75
|
@ -4,7 +4,7 @@
|
|||
import { getCellRenderer } from "../lib/renderers"
|
||||
import { derived, writable } from "svelte/store"
|
||||
|
||||
const { rows, focusedCellId, menu, sheetAPI, config, validation } =
|
||||
const { rows, focusedCellId, focusedCellAPI, menu, config, validation } =
|
||||
getContext("sheet")
|
||||
|
||||
export let rowSelected
|
||||
|
@ -25,13 +25,6 @@
|
|||
|
||||
let api
|
||||
|
||||
$: {
|
||||
// Wipe error if row is unfocused
|
||||
if (!rowFocused && $error) {
|
||||
validation.actions.setError(cellId, null)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the error for this cell if the row is focused
|
||||
$: error = getErrorStore(rowFocused, cellId)
|
||||
|
||||
|
@ -40,8 +33,8 @@
|
|||
|
||||
// Register this cell API if the row is focused
|
||||
$: {
|
||||
if (rowFocused) {
|
||||
sheetAPI.actions.registerCellAPI(cellId, cellAPI)
|
||||
if (focused) {
|
||||
focusedCellAPI.set(cellAPI)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,30 +50,9 @@
|
|||
blur: () => api?.blur(),
|
||||
onKeyDown: (...params) => api?.onKeyDown(...params),
|
||||
isReadonly: () => readonly,
|
||||
isRequired: () => !!column.schema.constraints?.presence,
|
||||
validate: value => {
|
||||
// Validate the current value if no new value is provided
|
||||
if (value === undefined) {
|
||||
value = row[column.name]
|
||||
}
|
||||
let newError = null
|
||||
if (cellAPI.isReadonly() && !(value == null || value === "")) {
|
||||
// Ensure cell isn't readonly
|
||||
newError = "Auto columns can't be edited"
|
||||
} else if (cellAPI.isRequired() && (value == null || value === "")) {
|
||||
// Sanity check required fields
|
||||
newError = "Required field"
|
||||
} else {
|
||||
newError = null
|
||||
}
|
||||
validation.actions.setError(cellId, newError)
|
||||
return newError
|
||||
},
|
||||
updateValue: value => {
|
||||
cellAPI.validate(value)
|
||||
if (!$error) {
|
||||
updateRow(row._id, column.name, value)
|
||||
}
|
||||
validation.actions.setError(cellId, null)
|
||||
updateRow(row._id, column.name, value)
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<script>
|
||||
import { ActionButton } from "@budibase/bbui"
|
||||
import { getContext } from "svelte"
|
||||
|
||||
const { config, dispatch } = getContext("sheet")
|
||||
</script>
|
||||
|
||||
<ActionButton
|
||||
icon="TableColumnAddRight"
|
||||
quiet
|
||||
size="M"
|
||||
on:click={() => dispatch("add-column")}
|
||||
disabled={!$config.allowAddColumns}
|
||||
>
|
||||
Create column
|
||||
</ActionButton>
|
|
@ -2,15 +2,15 @@
|
|||
import { ActionButton } from "@budibase/bbui"
|
||||
import { getContext } from "svelte"
|
||||
|
||||
const { dispatch, columns, stickyColumn } = getContext("sheet")
|
||||
const { dispatch, columns, stickyColumn, config } = getContext("sheet")
|
||||
</script>
|
||||
|
||||
<ActionButton
|
||||
icon="Add"
|
||||
icon="TableRowAddBottom"
|
||||
quiet
|
||||
size="M"
|
||||
on:click={() => dispatch("add-row-inline")}
|
||||
disabled={!$columns.length && !$stickyColumn}
|
||||
on:click={() => dispatch("add-row")}
|
||||
disabled={!$config.allowAddRows || (!$columns.length && !$stickyColumn)}
|
||||
>
|
||||
Create row
|
||||
</ActionButton>
|
||||
|
|
|
@ -2,14 +2,8 @@
|
|||
import { getContext } from "svelte"
|
||||
import SheetScrollWrapper from "./SheetScrollWrapper.svelte"
|
||||
import HeaderCell from "../cells/HeaderCell.svelte"
|
||||
import { Icon } from "@budibase/bbui"
|
||||
|
||||
const { renderedColumns, dispatch, config, ui } = getContext("sheet")
|
||||
|
||||
const addColumn = () => {
|
||||
ui.actions.blur()
|
||||
dispatch("add-column")
|
||||
}
|
||||
const { renderedColumns } = getContext("sheet")
|
||||
</script>
|
||||
|
||||
<div class="header">
|
||||
|
@ -20,11 +14,6 @@
|
|||
{/each}
|
||||
</div>
|
||||
</SheetScrollWrapper>
|
||||
{#if $config.allowAddColumns}
|
||||
<div class="new-column" on:click={addColumn}>
|
||||
<Icon size="S" name="Add" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
@ -38,20 +27,4 @@
|
|||
.row {
|
||||
display: flex;
|
||||
}
|
||||
.new-column {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
height: var(--row-height);
|
||||
background: var(--spectrum-global-color-gray-100);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 46px;
|
||||
border-left: var(--cell-border);
|
||||
border-bottom: var(--cell-border);
|
||||
}
|
||||
.new-column:hover {
|
||||
cursor: pointer;
|
||||
background: var(--spectrum-global-color-gray-200);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import SheetCell from "../cells/SheetCell.svelte"
|
||||
import { getContext, onMount, tick } from "svelte"
|
||||
import { Icon, Button } from "@budibase/bbui"
|
||||
import { getContext, onMount } from "svelte"
|
||||
import { Icon, Button, clickOutside } from "@budibase/bbui"
|
||||
import SheetScrollWrapper from "./SheetScrollWrapper.svelte"
|
||||
import DataCell from "../cells/DataCell.svelte"
|
||||
import { fly } from "svelte/transition"
|
||||
|
@ -19,7 +19,7 @@
|
|||
showHScrollbar,
|
||||
tableId,
|
||||
subscribe,
|
||||
sheetAPI,
|
||||
scrollLeft,
|
||||
} = getContext("sheet")
|
||||
|
||||
let isAdding = false
|
||||
|
@ -30,22 +30,24 @@
|
|||
$: rowHovered = $hoveredRowId === "new"
|
||||
$: rowFocused = $focusedCellId?.startsWith("new-")
|
||||
$: width = gutterWidth + ($stickyColumn?.width || 0)
|
||||
$: scrollLeft = $scroll.left
|
||||
$: $tableId, (isAdding = false)
|
||||
|
||||
const addRow = async () => {
|
||||
// Create row
|
||||
const savedRow = await rows.actions.addRow(newRow, 0)
|
||||
if (savedRow && firstColumn) {
|
||||
$focusedCellId = `${savedRow._id}-${firstColumn.name}`
|
||||
isAdding = false
|
||||
}
|
||||
if (savedRow) {
|
||||
// Select the first cell if possible
|
||||
if (firstColumn) {
|
||||
$focusedCellId = `${savedRow._id}-${firstColumn.name}`
|
||||
}
|
||||
|
||||
// Reset scroll
|
||||
scroll.set({
|
||||
left: 0,
|
||||
top: 0,
|
||||
})
|
||||
// Reset state
|
||||
isAdding = false
|
||||
scroll.set({
|
||||
left: 0,
|
||||
top: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
|
@ -151,17 +153,28 @@
|
|||
position: absolute;
|
||||
top: var(--row-height);
|
||||
left: 0;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
var(--cell-background) 20%,
|
||||
transparent 100%
|
||||
);
|
||||
/*background: linear-gradient(*/
|
||||
/* to bottom,*/
|
||||
/* var(--cell-background) 20%,*/
|
||||
/* transparent 100%*/
|
||||
/*);*/
|
||||
width: 100%;
|
||||
padding-bottom: 100px;
|
||||
padding-bottom: 800px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.container:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: var(--cell-background);
|
||||
opacity: 0.8;
|
||||
z-index: -1;
|
||||
}
|
||||
.content {
|
||||
pointer-events: all;
|
||||
background: var(--background);
|
||||
|
@ -227,7 +240,7 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
margin: 24px 0 0 16px;
|
||||
margin: 24px 0 0 var(--gutter-width);
|
||||
pointer-events: all;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
|
|
@ -3,20 +3,7 @@
|
|||
import { writable } from "svelte/store"
|
||||
import { createEventManagers } from "../lib/events"
|
||||
import { createAPIClient } from "../../../api"
|
||||
import { createReorderStores } from "../stores/reorder"
|
||||
import { createViewportStores } from "../stores/viewport"
|
||||
import { createRowsStore } from "../stores/rows"
|
||||
import { createColumnsStores } from "../stores/columns"
|
||||
import { createScrollStores } from "../stores/scroll"
|
||||
import { createBoundsStores } from "../stores/bounds"
|
||||
import { createUIStores } from "../stores/ui"
|
||||
import { createUserStores } from "../stores/users"
|
||||
import { createResizeStores } from "../stores/resize"
|
||||
import { createMenuStores } from "../stores/menu"
|
||||
import { createMaxScrollStores } from "../stores/max-scroll"
|
||||
import { createPaginationStores } from "../stores/pagination"
|
||||
import { createSheetAPIStores } from "../stores/sheet-api"
|
||||
import { createValidationStores } from "../stores/validation"
|
||||
import { createStores } from "../stores"
|
||||
import DeleteButton from "../controls/DeleteButton.svelte"
|
||||
import SheetBody from "./SheetBody.svelte"
|
||||
import ResizeOverlay from "../overlays/ResizeOverlay.svelte"
|
||||
|
@ -28,8 +15,11 @@
|
|||
import KeyboardManager from "../overlays/KeyboardManager.svelte"
|
||||
import { clickOutside } from "@budibase/bbui"
|
||||
import SheetControls from "./SheetControls.svelte"
|
||||
import NewRowTop from "./NewRowTop.svelte"
|
||||
import { MaxCellRenderHeight } from "../lib/constants"
|
||||
import SortButton from "../controls/SortButton.svelte"
|
||||
import AddColumnButton from "../controls/AddColumnButton.svelte"
|
||||
import HideColumnsButton from "../controls/HideColumnsButton.svelte"
|
||||
import AddRowButton from "../controls/AddRowButton.svelte"
|
||||
|
||||
export let API
|
||||
export let tableId
|
||||
|
@ -56,7 +46,6 @@
|
|||
})
|
||||
|
||||
// Build up spreadsheet context
|
||||
// Stores are listed in order of dependency on each other
|
||||
let context = {
|
||||
API: API || createAPIClient(),
|
||||
rand,
|
||||
|
@ -65,20 +54,7 @@
|
|||
tableId: tableIdStore,
|
||||
}
|
||||
context = { ...context, ...createEventManagers() }
|
||||
context = { ...context, ...createValidationStores(context) }
|
||||
context = { ...context, ...createBoundsStores(context) }
|
||||
context = { ...context, ...createScrollStores(context) }
|
||||
context = { ...context, ...createRowsStore(context) }
|
||||
context = { ...context, ...createColumnsStores(context) }
|
||||
context = { ...context, ...createUIStores(context) }
|
||||
context = { ...context, ...createSheetAPIStores(context) }
|
||||
context = { ...context, ...createResizeStores(context) }
|
||||
context = { ...context, ...createViewportStores(context) }
|
||||
context = { ...context, ...createMaxScrollStores(context) }
|
||||
context = { ...context, ...createReorderStores(context) }
|
||||
context = { ...context, ...createUserStores(context) }
|
||||
context = { ...context, ...createMenuStores(context) }
|
||||
context = { ...context, ...createPaginationStores(context) }
|
||||
context = { ...context, ...createStores(context) }
|
||||
|
||||
// Reference some stores for local use
|
||||
const { isResizing, isReordering, ui, loaded, rowHeight } = context
|
||||
|
@ -113,8 +89,12 @@
|
|||
>
|
||||
<div class="controls">
|
||||
<div class="controls-left">
|
||||
<AddRowButton />
|
||||
<AddColumnButton />
|
||||
<SheetControls />
|
||||
<slot name="controls" />
|
||||
<HideColumnsButton />
|
||||
<SortButton />
|
||||
</div>
|
||||
<div class="controls-right">
|
||||
<DeleteButton />
|
||||
|
@ -130,9 +110,6 @@
|
|||
<SheetBody />
|
||||
</div>
|
||||
<div class="overlays">
|
||||
{#if $config.allowAddRows}
|
||||
<NewRowTop />
|
||||
{/if}
|
||||
<ResizeOverlay />
|
||||
<ScrollOverlay />
|
||||
<MenuOverlay />
|
||||
|
|
|
@ -2,10 +2,5 @@
|
|||
import SortButton from "../controls/SortButton.svelte"
|
||||
import HideColumnsButton from "../controls/HideColumnsButton.svelte"
|
||||
import AddRowButton from "../controls/AddRowButton.svelte"
|
||||
import RowHeightButton from "../controls/RowHeightButton.svelte"
|
||||
import AddColumnButton from "../controls/AddColumnButton.svelte"
|
||||
</script>
|
||||
|
||||
<AddRowButton />
|
||||
<HideColumnsButton />
|
||||
<SortButton />
|
||||
<RowHeightButton />
|
||||
|
|
|
@ -20,9 +20,9 @@
|
|||
focusedRow,
|
||||
gutterWidth,
|
||||
dispatch,
|
||||
scrollLeft,
|
||||
} = getContext("sheet")
|
||||
|
||||
$: scrollLeft = $scroll.left
|
||||
$: rowCount = $rows.length
|
||||
$: selectedRowCount = Object.values($selectedRows).filter(x => !!x).length
|
||||
$: width = gutterWidth + ($stickyColumn?.width || 0)
|
||||
|
@ -91,6 +91,7 @@
|
|||
{@const rowSelected = !!$selectedRows[row._id]}
|
||||
{@const rowHovered = $hoveredRowId === row._id}
|
||||
{@const rowFocused = $focusedRow?._id === row._id}
|
||||
{@const cellId = `${row._id}-${$stickyColumn.name}`}
|
||||
<div
|
||||
class="row"
|
||||
on:mouseenter={() => ($hoveredRowId = row._id)}
|
||||
|
@ -132,9 +133,7 @@
|
|||
{/if}
|
||||
</div>
|
||||
</SheetCell>
|
||||
|
||||
{#if $stickyColumn}
|
||||
{@const cellId = `${row._id}-${$stickyColumn.name}`}
|
||||
<DataCell
|
||||
{rowSelected}
|
||||
{rowHovered}
|
||||
|
@ -176,7 +175,7 @@
|
|||
/*z-index: 1;*/
|
||||
}
|
||||
.sticky-column.scrolled {
|
||||
/*box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.1);*/
|
||||
box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Don't show borders between cells in the sticky column */
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<script>
|
||||
import { getContext, onMount } from "svelte"
|
||||
import { debounce } from "../../../utils/utils"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
|
||||
const {
|
||||
rows,
|
||||
|
@ -9,7 +8,7 @@
|
|||
visibleColumns,
|
||||
focusedRow,
|
||||
stickyColumn,
|
||||
selectedCellAPI,
|
||||
focusedCellAPI,
|
||||
} = getContext("sheet")
|
||||
|
||||
const handleKeyDown = e => {
|
||||
|
@ -22,7 +21,7 @@
|
|||
}
|
||||
|
||||
// Always intercept certain key presses
|
||||
const api = $selectedCellAPI
|
||||
const api = $focusedCellAPI
|
||||
if (e.key === "Escape") {
|
||||
api?.blur?.()
|
||||
} else if (e.key === "Tab") {
|
||||
|
@ -112,17 +111,17 @@
|
|||
|
||||
// Debounce to avoid holding down delete and spamming requests
|
||||
const deleteSelectedCell = debounce(() => {
|
||||
if (!$focusedCellId) {
|
||||
if ($focusedCellAPI?.isReadonly()) {
|
||||
return
|
||||
}
|
||||
$selectedCellAPI.updateValue(null)
|
||||
$focusedCellAPI.updateValue(null)
|
||||
}, 100)
|
||||
|
||||
const focusSelectedCell = () => {
|
||||
if ($selectedCellAPI?.isReadonly()) {
|
||||
if ($focusedCellAPI?.isReadonly()) {
|
||||
return
|
||||
}
|
||||
$selectedCellAPI?.focus?.()
|
||||
$focusedCellAPI?.focus?.()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
stickyColumn,
|
||||
isReordering,
|
||||
gutterWidth,
|
||||
scrollLeft,
|
||||
} = getContext("sheet")
|
||||
|
||||
$: scrollLeft = $scroll.left
|
||||
$: cutoff = scrollLeft + gutterWidth + ($columns[0]?.width || 0)
|
||||
$: offset = gutterWidth + ($stickyColumn?.width || 0)
|
||||
$: column = $resize.column
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
const {
|
||||
scroll,
|
||||
bounds,
|
||||
rowHeight,
|
||||
contentHeight,
|
||||
maxScrollTop,
|
||||
|
@ -13,6 +12,9 @@
|
|||
screenWidth,
|
||||
showHScrollbar,
|
||||
showVScrollbar,
|
||||
scrollLeft,
|
||||
scrollTop,
|
||||
height,
|
||||
} = getContext("sheet")
|
||||
|
||||
// Bar config
|
||||
|
@ -22,32 +24,27 @@
|
|||
let initialMouse
|
||||
let initialScroll
|
||||
|
||||
// Memoize store primitives to reduce reactive statement invalidations
|
||||
$: scrollTop = $scroll.top
|
||||
$: scrollLeft = $scroll.left
|
||||
$: height = $bounds.height
|
||||
$: width = $bounds.width
|
||||
|
||||
// Calculate V scrollbar size and offset
|
||||
// Terminology is the same for both axes:
|
||||
// renderX - the space available to render the bar in, edge to edge
|
||||
// availX - the space available to render the bar in, until the edge
|
||||
$: renderHeight = height - 2 * barOffset
|
||||
$: barHeight = Math.max(50, (height / $contentHeight) * renderHeight)
|
||||
$: renderHeight = $height - 2 * barOffset
|
||||
$: barHeight = Math.max(50, ($height / $contentHeight) * renderHeight)
|
||||
$: availHeight = renderHeight - barHeight
|
||||
$: barTop = barOffset + $rowHeight + availHeight * (scrollTop / $maxScrollTop)
|
||||
$: barTop =
|
||||
barOffset + $rowHeight + availHeight * ($scrollTop / $maxScrollTop)
|
||||
|
||||
// Calculate H scrollbar size and offset
|
||||
$: renderWidth = $screenWidth - 2 * barOffset
|
||||
$: barWidth = Math.max(50, ($screenWidth / $contentWidth) * renderWidth)
|
||||
$: availWidth = renderWidth - barWidth
|
||||
$: barLeft = barOffset + availWidth * (scrollLeft / $maxScrollLeft)
|
||||
$: barLeft = barOffset + availWidth * ($scrollLeft / $maxScrollLeft)
|
||||
|
||||
// V scrollbar drag handlers
|
||||
const startVDragging = e => {
|
||||
e.preventDefault()
|
||||
initialMouse = e.clientY
|
||||
initialScroll = scrollTop
|
||||
initialScroll = $scrollTop
|
||||
document.addEventListener("mousemove", moveVDragging)
|
||||
document.addEventListener("mouseup", stopVDragging)
|
||||
}
|
||||
|
@ -69,7 +66,7 @@
|
|||
const startHDragging = e => {
|
||||
e.preventDefault()
|
||||
initialMouse = e.clientX
|
||||
initialScroll = scrollLeft
|
||||
initialScroll = $scrollLeft
|
||||
document.addEventListener("mousemove", moveHDragging)
|
||||
document.addEventListener("mouseup", stopHDragging)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import { writable } from "svelte/store"
|
||||
import { derived, writable } from "svelte/store"
|
||||
|
||||
export const createBoundsStores = () => {
|
||||
export const createStores = () => {
|
||||
const bounds = writable({
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
})
|
||||
return { bounds }
|
||||
|
||||
// Derive height and width as primitives to avoid wasted computation
|
||||
const width = derived(bounds, $bounds => $bounds.width, 0)
|
||||
const height = derived(bounds, $bounds => $bounds.height, 0)
|
||||
|
||||
return { bounds, height, width }
|
||||
}
|
||||
|
|
|
@ -2,8 +2,7 @@ import { derived, get, writable } from "svelte/store"
|
|||
|
||||
export const DefaultColumnWidth = 200
|
||||
|
||||
export const createColumnsStores = context => {
|
||||
const { table, gutterWidth } = context
|
||||
export const createStores = () => {
|
||||
const columns = writable([])
|
||||
const stickyColumn = writable(null)
|
||||
|
||||
|
@ -36,6 +35,19 @@ export const createColumnsStores = context => {
|
|||
[]
|
||||
)
|
||||
|
||||
return {
|
||||
columns: {
|
||||
...columns,
|
||||
subscribe: enrichedColumns.subscribe,
|
||||
},
|
||||
stickyColumn,
|
||||
visibleColumns,
|
||||
}
|
||||
}
|
||||
|
||||
export const deriveStores = context => {
|
||||
const { table, gutterWidth, columns, stickyColumn } = context
|
||||
|
||||
// Merge new schema fields with existing schema in order to preserve widths
|
||||
table.subscribe($table => {
|
||||
const schema = $table?.schema
|
||||
|
@ -109,12 +121,5 @@ export const createColumnsStores = context => {
|
|||
})
|
||||
})
|
||||
|
||||
return {
|
||||
columns: {
|
||||
...columns,
|
||||
subscribe: enrichedColumns.subscribe,
|
||||
},
|
||||
stickyColumn,
|
||||
visibleColumns,
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
import * as Bounds from "./bounds"
|
||||
import * as Columns from "./columns"
|
||||
import * as Menu from "./menu"
|
||||
import * as Pagination from "./pagination"
|
||||
import * as Reorder from "./reorder"
|
||||
import * as Resize from "./resize"
|
||||
import * as Rows from "./rows"
|
||||
import * as Scroll from "./scroll"
|
||||
import * as UI from "./ui"
|
||||
import * as Users from "./users"
|
||||
import * as Validation from "./validation"
|
||||
import * as Viewport from "./viewport"
|
||||
|
||||
const DependencyOrderedStores = [
|
||||
Bounds,
|
||||
Scroll,
|
||||
Rows,
|
||||
Columns,
|
||||
UI,
|
||||
Validation,
|
||||
Resize,
|
||||
Viewport,
|
||||
Reorder,
|
||||
Users,
|
||||
Menu,
|
||||
Pagination,
|
||||
]
|
||||
|
||||
export const createStores = context => {
|
||||
let stores = {}
|
||||
|
||||
// Atomic store creation
|
||||
for (let store of DependencyOrderedStores) {
|
||||
stores = { ...stores, ...store.createStores?.({ ...context, ...stores }) }
|
||||
}
|
||||
|
||||
// Derived store creation
|
||||
for (let store of DependencyOrderedStores) {
|
||||
stores = { ...stores, ...store.deriveStores?.({ ...context, ...stores }) }
|
||||
}
|
||||
|
||||
return stores
|
||||
}
|
|
@ -1,182 +0,0 @@
|
|||
import { derived, get } from "svelte/store"
|
||||
import { tick } from "svelte"
|
||||
|
||||
export const createMaxScrollStores = context => {
|
||||
const {
|
||||
rows,
|
||||
visibleColumns,
|
||||
stickyColumn,
|
||||
bounds,
|
||||
rowHeight,
|
||||
scroll,
|
||||
focusedRow,
|
||||
focusedCellId,
|
||||
gutterWidth,
|
||||
} = context
|
||||
const padding = 264
|
||||
|
||||
// Memoize store primitives
|
||||
const scrollTop = derived(scroll, $scroll => $scroll.top, 0)
|
||||
const scrollLeft = derived(scroll, $scroll => $scroll.left, 0)
|
||||
const stickyColumnWidth = derived(stickyColumn, $col => $col?.width || 0, 0)
|
||||
|
||||
// Derive vertical limits
|
||||
const height = derived(bounds, $bounds => $bounds.height, 0)
|
||||
const width = derived(bounds, $bounds => $bounds.width, 0)
|
||||
const contentHeight = derived(
|
||||
[rows, rowHeight],
|
||||
([$rows, $rowHeight]) => $rows.length * $rowHeight + padding,
|
||||
0
|
||||
)
|
||||
const maxScrollTop = derived(
|
||||
[height, contentHeight],
|
||||
([$height, $contentHeight]) => Math.max($contentHeight - $height, 0),
|
||||
0
|
||||
)
|
||||
|
||||
// Derive horizontal limits
|
||||
const contentWidth = derived(
|
||||
[visibleColumns, stickyColumnWidth],
|
||||
([$visibleColumns, $stickyColumnWidth]) => {
|
||||
let width = gutterWidth + padding + $stickyColumnWidth
|
||||
$visibleColumns.forEach(col => {
|
||||
width += col.width
|
||||
})
|
||||
return width
|
||||
},
|
||||
0
|
||||
)
|
||||
const screenWidth = derived(
|
||||
[width, stickyColumnWidth],
|
||||
([$width, $stickyColumnWidth]) => $width + gutterWidth + $stickyColumnWidth,
|
||||
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),
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
// Ensure the selected cell is visible
|
||||
focusedCellId.subscribe(async $focusedCellId => {
|
||||
await tick()
|
||||
const $focusedRow = get(focusedRow)
|
||||
const $scroll = get(scroll)
|
||||
const $bounds = get(bounds)
|
||||
const $rowHeight = get(rowHeight)
|
||||
const verticalOffset = $rowHeight * 1.5
|
||||
|
||||
// Ensure vertical position is viewable
|
||||
if ($focusedRow) {
|
||||
// Ensure row is not below bottom of screen
|
||||
const rowYPos = $focusedRow.__idx * $rowHeight
|
||||
const bottomCutoff =
|
||||
$scroll.top + $bounds.height - $rowHeight - verticalOffset
|
||||
let delta = rowYPos - bottomCutoff
|
||||
if (delta > 0) {
|
||||
scroll.update(state => ({
|
||||
...state,
|
||||
top: state.top + delta,
|
||||
}))
|
||||
}
|
||||
|
||||
// Ensure row is not above top of screen
|
||||
else {
|
||||
const delta = $scroll.top - rowYPos + verticalOffset
|
||||
if (delta > 0) {
|
||||
scroll.update(state => ({
|
||||
...state,
|
||||
top: Math.max(0, state.top - delta),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure horizontal position is viewable
|
||||
// Check horizontal position of columns next
|
||||
const $visibleColumns = get(visibleColumns)
|
||||
const columnName = $focusedCellId?.split("-")[1]
|
||||
const column = $visibleColumns.find(col => col.name === columnName)
|
||||
const horizontalOffset = 24
|
||||
if (!column) {
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure column is not cutoff on left edge
|
||||
let delta = $scroll.left - column.left + horizontalOffset
|
||||
if (delta > 0) {
|
||||
scroll.update(state => ({
|
||||
...state,
|
||||
left: Math.max(0, state.left - delta),
|
||||
}))
|
||||
}
|
||||
|
||||
// Ensure column is not cutoff on right edge
|
||||
else {
|
||||
const rightEdge = column.left + column.width
|
||||
const rightBound = $bounds.width + $scroll.left - horizontalOffset
|
||||
delta = rightEdge - rightBound
|
||||
if (delta > 0) {
|
||||
scroll.update(state => ({
|
||||
...state,
|
||||
left: state.left + delta,
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Derive whether to show scrollbars or not
|
||||
const showVScrollbar = derived(
|
||||
[contentHeight, height],
|
||||
([$contentHeight, $height]) => {
|
||||
return $contentHeight > $height
|
||||
}
|
||||
)
|
||||
const showHScrollbar = derived(
|
||||
[contentWidth, screenWidth],
|
||||
([$contentWidth, $screenWidth]) => {
|
||||
return $contentWidth > $screenWidth
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
contentHeight,
|
||||
contentWidth,
|
||||
screenWidth,
|
||||
maxScrollTop,
|
||||
maxScrollLeft,
|
||||
showHScrollbar,
|
||||
showVScrollbar,
|
||||
}
|
||||
}
|
|
@ -1,13 +1,19 @@
|
|||
import { writable, get } from "svelte/store"
|
||||
|
||||
export const createMenuStores = context => {
|
||||
const { bounds, focusedCellId, stickyColumn, rowHeight } = context
|
||||
export const createStores = () => {
|
||||
const menu = writable({
|
||||
x: 0,
|
||||
y: 0,
|
||||
visible: false,
|
||||
selectedRow: null,
|
||||
})
|
||||
return {
|
||||
menu,
|
||||
}
|
||||
}
|
||||
|
||||
export const deriveStores = context => {
|
||||
const { menu, bounds, focusedCellId, stickyColumn, rowHeight } = context
|
||||
|
||||
const open = (cellId, e) => {
|
||||
const $bounds = get(bounds)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { derived } from "svelte/store"
|
||||
|
||||
export const createPaginationStores = context => {
|
||||
export const deriveStores = context => {
|
||||
const { scrolledRowCount, rows, visualRowCapacity } = context
|
||||
|
||||
// Derive how many rows we have in total
|
||||
|
|
|
@ -1,21 +1,29 @@
|
|||
import { get, writable, derived } from "svelte/store"
|
||||
|
||||
export const createReorderStores = context => {
|
||||
const { columns, scroll, bounds, stickyColumn, ui } = context
|
||||
const reorderInitialState = {
|
||||
sourceColumn: null,
|
||||
targetColumn: null,
|
||||
breakpoints: [],
|
||||
initialMouseX: null,
|
||||
scrollLeft: 0,
|
||||
sheetLeft: 0,
|
||||
}
|
||||
const reorderInitialState = {
|
||||
sourceColumn: null,
|
||||
targetColumn: null,
|
||||
breakpoints: [],
|
||||
initialMouseX: null,
|
||||
scrollLeft: 0,
|
||||
sheetLeft: 0,
|
||||
}
|
||||
|
||||
export const createStores = () => {
|
||||
const reorder = writable(reorderInitialState)
|
||||
const isReordering = derived(
|
||||
reorder,
|
||||
$reorder => !!$reorder.sourceColumn,
|
||||
false
|
||||
)
|
||||
return {
|
||||
reorder,
|
||||
isReordering,
|
||||
}
|
||||
}
|
||||
|
||||
export const deriveStores = context => {
|
||||
const { reorder, columns, scroll, bounds, stickyColumn, ui } = context
|
||||
|
||||
// Callback when dragging on a colum header and starting reordering
|
||||
const startReordering = (column, e) => {
|
||||
|
@ -142,6 +150,5 @@ export const createReorderStores = context => {
|
|||
moveColumnRight,
|
||||
},
|
||||
},
|
||||
isReordering,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,18 +3,26 @@ import { DefaultColumnWidth } from "./columns"
|
|||
|
||||
export const MinColumnWidth = 100
|
||||
|
||||
export const createResizeStores = context => {
|
||||
const { columns, stickyColumn, ui } = context
|
||||
const initialState = {
|
||||
initialMouseX: null,
|
||||
initialWidth: null,
|
||||
column: null,
|
||||
columnIdx: null,
|
||||
width: 0,
|
||||
left: 0,
|
||||
}
|
||||
const initialState = {
|
||||
initialMouseX: null,
|
||||
initialWidth: null,
|
||||
column: null,
|
||||
columnIdx: null,
|
||||
width: 0,
|
||||
left: 0,
|
||||
}
|
||||
|
||||
export const createStores = () => {
|
||||
const resize = writable(initialState)
|
||||
const isResizing = derived(resize, $resize => $resize.column != null, false)
|
||||
return {
|
||||
resize,
|
||||
isResizing,
|
||||
}
|
||||
}
|
||||
|
||||
export const deriveStores = context => {
|
||||
const { resize, columns, stickyColumn, ui } = context
|
||||
|
||||
// Starts resizing a certain column
|
||||
const startResizing = (column, e) => {
|
||||
|
@ -105,6 +113,5 @@ export const createResizeStores = context => {
|
|||
resetSize,
|
||||
},
|
||||
},
|
||||
isResizing,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,18 +2,16 @@ import { writable, derived, get } from "svelte/store"
|
|||
import { fetchData } from "../../../fetch/fetchData"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
|
||||
export const createRowsStore = context => {
|
||||
const { tableId, API, scroll, validation } = context
|
||||
const initialSortState = {
|
||||
column: null,
|
||||
order: "ascending",
|
||||
}
|
||||
|
||||
export const createStores = () => {
|
||||
const rows = writable([])
|
||||
const table = writable(null)
|
||||
const filter = writable([])
|
||||
const loaded = writable(false)
|
||||
const instanceLoaded = writable(false)
|
||||
const fetch = writable(null)
|
||||
const initialSortState = {
|
||||
column: null,
|
||||
order: "ascending",
|
||||
}
|
||||
const sort = writable(initialSortState)
|
||||
|
||||
// Enrich rows with an index property
|
||||
|
@ -41,6 +39,37 @@ export const createRowsStore = context => {
|
|||
{}
|
||||
)
|
||||
|
||||
return {
|
||||
rows: {
|
||||
...rows,
|
||||
subscribe: enrichedRows.subscribe,
|
||||
},
|
||||
rowLookupMap,
|
||||
table,
|
||||
filter,
|
||||
loaded,
|
||||
sort,
|
||||
}
|
||||
}
|
||||
|
||||
export const deriveStores = context => {
|
||||
const {
|
||||
rows,
|
||||
rowLookupMap,
|
||||
table,
|
||||
filter,
|
||||
loaded,
|
||||
sort,
|
||||
tableId,
|
||||
API,
|
||||
scroll,
|
||||
validation,
|
||||
focusedCellId,
|
||||
columns,
|
||||
} = context
|
||||
const instanceLoaded = writable(false)
|
||||
const fetch = writable(null)
|
||||
|
||||
// Local cache of row IDs to speed up checking if a row exists
|
||||
let rowCacheMap = {}
|
||||
|
||||
|
@ -117,19 +146,32 @@ export const createRowsStore = context => {
|
|||
// Gets a row by ID
|
||||
const getRow = id => {
|
||||
const index = get(rowLookupMap)[id]
|
||||
return index >= 0 ? get(enrichedRows)[index] : null
|
||||
return index >= 0 ? get(rows)[index] : null
|
||||
}
|
||||
|
||||
// Handles validation errors from the rows API and updates local validation
|
||||
// state, storing error messages against relevant cells
|
||||
const handleValidationError = (rowId, error) => {
|
||||
if (error?.json?.validationErrors) {
|
||||
for (let column of Object.keys(error.json.validationErrors)) {
|
||||
const keys = Object.keys(error.json.validationErrors)
|
||||
const $columns = get(columns)
|
||||
for (let column of keys) {
|
||||
validation.actions.setError(
|
||||
`${rowId}-${column}`,
|
||||
`${column} ${error.json.validationErrors[column]}`
|
||||
)
|
||||
|
||||
// Ensure the column is visible
|
||||
const index = $columns.findIndex(x => x.name === column)
|
||||
if (index !== -1 && !$columns[index].visible) {
|
||||
columns.update(state => {
|
||||
state[index].visible = true
|
||||
return state.slice()
|
||||
})
|
||||
}
|
||||
}
|
||||
// Focus the first cell with an error
|
||||
focusedCellId.set(`${rowId}-${keys[0]}`)
|
||||
} else {
|
||||
notifications.error(`Error saving row: ${error?.message}`)
|
||||
}
|
||||
|
@ -299,7 +341,6 @@ export const createRowsStore = context => {
|
|||
return {
|
||||
rows: {
|
||||
...rows,
|
||||
subscribe: enrichedRows.subscribe,
|
||||
actions: {
|
||||
addRow,
|
||||
getRow,
|
||||
|
@ -312,10 +353,5 @@ export const createRowsStore = context => {
|
|||
refreshTableDefinition,
|
||||
},
|
||||
},
|
||||
rowLookupMap,
|
||||
table,
|
||||
sort,
|
||||
filter,
|
||||
loaded,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,199 @@
|
|||
import { writable } from "svelte/store"
|
||||
import { writable, derived, get } from "svelte/store"
|
||||
import { tick } from "svelte"
|
||||
|
||||
export const createScrollStores = () => {
|
||||
export const createStores = () => {
|
||||
const scroll = writable({
|
||||
left: 0,
|
||||
top: 0,
|
||||
})
|
||||
|
||||
// Derive height and width as primitives to avoid wasted computation
|
||||
const scrollTop = derived(scroll, $scroll => $scroll.top, 0)
|
||||
const scrollLeft = derived(scroll, $scroll => $scroll.left, 0)
|
||||
|
||||
return {
|
||||
scroll,
|
||||
scrollTop,
|
||||
scrollLeft,
|
||||
}
|
||||
}
|
||||
|
||||
export const deriveStores = context => {
|
||||
const {
|
||||
scroll,
|
||||
rows,
|
||||
visibleColumns,
|
||||
stickyColumn,
|
||||
bounds,
|
||||
rowHeight,
|
||||
focusedRow,
|
||||
focusedCellId,
|
||||
gutterWidth,
|
||||
} = context
|
||||
const padding = 264
|
||||
|
||||
// Memoize store primitives
|
||||
const scrollTop = derived(scroll, $scroll => $scroll.top, 0)
|
||||
const scrollLeft = derived(scroll, $scroll => $scroll.left, 0)
|
||||
const stickyColumnWidth = derived(stickyColumn, $col => $col?.width || 0, 0)
|
||||
|
||||
// Derive vertical limits
|
||||
const height = derived(bounds, $bounds => $bounds.height, 0)
|
||||
const width = derived(bounds, $bounds => $bounds.width, 0)
|
||||
const contentHeight = derived(
|
||||
[rows, rowHeight],
|
||||
([$rows, $rowHeight]) => $rows.length * $rowHeight + padding,
|
||||
0
|
||||
)
|
||||
const maxScrollTop = derived(
|
||||
[height, contentHeight],
|
||||
([$height, $contentHeight]) => Math.max($contentHeight - $height, 0),
|
||||
0
|
||||
)
|
||||
|
||||
// Derive horizontal limits
|
||||
const contentWidth = derived(
|
||||
[visibleColumns, stickyColumnWidth],
|
||||
([$visibleColumns, $stickyColumnWidth]) => {
|
||||
let width = gutterWidth + padding + $stickyColumnWidth
|
||||
$visibleColumns.forEach(col => {
|
||||
width += col.width
|
||||
})
|
||||
return width
|
||||
},
|
||||
0
|
||||
)
|
||||
const screenWidth = derived(
|
||||
[width, stickyColumnWidth],
|
||||
([$width, $stickyColumnWidth]) => $width + gutterWidth + $stickyColumnWidth,
|
||||
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),
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
// Ensure the selected cell is visible
|
||||
focusedCellId.subscribe(async $focusedCellId => {
|
||||
await tick()
|
||||
const $focusedRow = get(focusedRow)
|
||||
const $scroll = get(scroll)
|
||||
const $bounds = get(bounds)
|
||||
const $rowHeight = get(rowHeight)
|
||||
const verticalOffset = $rowHeight * 1.5
|
||||
|
||||
// Ensure vertical position is viewable
|
||||
if ($focusedRow) {
|
||||
// Ensure row is not below bottom of screen
|
||||
const rowYPos = $focusedRow.__idx * $rowHeight
|
||||
const bottomCutoff =
|
||||
$scroll.top + $bounds.height - $rowHeight - verticalOffset
|
||||
let delta = rowYPos - bottomCutoff
|
||||
if (delta > 0) {
|
||||
scroll.update(state => ({
|
||||
...state,
|
||||
top: state.top + delta,
|
||||
}))
|
||||
}
|
||||
|
||||
// Ensure row is not above top of screen
|
||||
else {
|
||||
const delta = $scroll.top - rowYPos + verticalOffset
|
||||
if (delta > 0) {
|
||||
scroll.update(state => ({
|
||||
...state,
|
||||
top: Math.max(0, state.top - delta),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure horizontal position is viewable
|
||||
// Check horizontal position of columns next
|
||||
const $visibleColumns = get(visibleColumns)
|
||||
const columnName = $focusedCellId?.split("-")[1]
|
||||
const column = $visibleColumns.find(col => col.name === columnName)
|
||||
const horizontalOffset = 24
|
||||
if (!column) {
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure column is not cutoff on left edge
|
||||
let delta = $scroll.left - column.left + horizontalOffset
|
||||
if (delta > 0) {
|
||||
scroll.update(state => ({
|
||||
...state,
|
||||
left: Math.max(0, state.left - delta),
|
||||
}))
|
||||
}
|
||||
|
||||
// Ensure column is not cutoff on right edge
|
||||
else {
|
||||
const rightEdge = column.left + column.width
|
||||
const rightBound = $bounds.width + $scroll.left - horizontalOffset
|
||||
delta = rightEdge - rightBound
|
||||
if (delta > 0) {
|
||||
scroll.update(state => ({
|
||||
...state,
|
||||
left: state.left + delta,
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Derive whether to show scrollbars or not
|
||||
const showVScrollbar = derived(
|
||||
[contentHeight, height],
|
||||
([$contentHeight, $height]) => {
|
||||
return $contentHeight > $height
|
||||
}
|
||||
)
|
||||
const showHScrollbar = derived(
|
||||
[contentWidth, screenWidth],
|
||||
([$contentWidth, $screenWidth]) => {
|
||||
return $contentWidth > $screenWidth
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
contentHeight,
|
||||
contentWidth,
|
||||
screenWidth,
|
||||
maxScrollTop,
|
||||
maxScrollLeft,
|
||||
showHScrollbar,
|
||||
showVScrollbar,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
import { derived, get, writable } from "svelte/store"
|
||||
|
||||
export const createSheetAPIStores = context => {
|
||||
const { focusedCellId } = context
|
||||
const cellAPIs = writable({})
|
||||
|
||||
const registerCellAPI = (cellId, api) => {
|
||||
// Ignore registration if cell is not selected
|
||||
const [rowId, column] = cellId.split("-")
|
||||
if (rowId !== "new" && !get(focusedCellId)?.startsWith(rowId)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Store API
|
||||
cellAPIs.update(state => ({
|
||||
...state,
|
||||
[column]: api,
|
||||
}))
|
||||
}
|
||||
|
||||
const getCellAPI = column => {
|
||||
return get(cellAPIs)[column]
|
||||
}
|
||||
|
||||
// Derive the selected cell API
|
||||
const selectedCellAPI = derived(
|
||||
[cellAPIs, focusedCellId],
|
||||
([$apis, $focusedCellId]) => {
|
||||
if (!$focusedCellId) {
|
||||
return null
|
||||
}
|
||||
const [, column] = $focusedCellId.split("-")
|
||||
return $apis[column]
|
||||
},
|
||||
null
|
||||
)
|
||||
|
||||
const focusedRowAPI = derived(cellAPIs, $apis => {
|
||||
return {
|
||||
validate: () => {
|
||||
let errors = null
|
||||
for (let [column, api] of Object.entries($apis || {})) {
|
||||
const error = api.validate()
|
||||
if (error) {
|
||||
errors = {
|
||||
...errors,
|
||||
[column]: error,
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
selectedCellAPI,
|
||||
focusedRowAPI,
|
||||
sheetAPI: {
|
||||
actions: {
|
||||
registerCellAPI,
|
||||
getCellAPI,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,19 +1,38 @@
|
|||
import { writable, get, derived } from "svelte/store"
|
||||
|
||||
export const createUIStores = context => {
|
||||
const { rows, rowLookupMap } = context
|
||||
export const createStores = () => {
|
||||
const focusedCellId = writable(null)
|
||||
const focusedCellAPI = writable(null)
|
||||
const selectedRows = writable({})
|
||||
const hoveredRowId = writable(null)
|
||||
const rowHeight = writable(36)
|
||||
return {
|
||||
focusedCellId,
|
||||
focusedCellAPI,
|
||||
selectedRows,
|
||||
hoveredRowId,
|
||||
rowHeight,
|
||||
}
|
||||
}
|
||||
|
||||
export const deriveStores = context => {
|
||||
const { focusedCellId, selectedRows, hoveredRowId, rows, rowLookupMap } =
|
||||
context
|
||||
|
||||
// Derive the row that contains the selected cell
|
||||
const focusedRow = derived(
|
||||
[focusedCellId, rowLookupMap, rows],
|
||||
([$focusedCellId, $rowLookupMap, $rows]) => {
|
||||
const rowId = $focusedCellId?.split("-")[0]
|
||||
const index = $rowLookupMap[rowId]
|
||||
return $rows[index]
|
||||
|
||||
if (rowId === "new") {
|
||||
// Edge case for new row
|
||||
return { _id: rowId }
|
||||
} else {
|
||||
// All normal rows
|
||||
const index = $rowLookupMap[rowId]
|
||||
return $rows[index]
|
||||
}
|
||||
},
|
||||
null
|
||||
)
|
||||
|
@ -80,11 +99,7 @@ export const createUIStores = context => {
|
|||
})
|
||||
|
||||
return {
|
||||
focusedCellId,
|
||||
selectedRows,
|
||||
hoveredRowId,
|
||||
focusedRow,
|
||||
rowHeight,
|
||||
ui: {
|
||||
actions: {
|
||||
blur,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { writable, get, derived } from "svelte/store"
|
||||
|
||||
export const createUserStores = () => {
|
||||
export const createStores = () => {
|
||||
const users = writable([])
|
||||
const userId = writable(null)
|
||||
|
||||
|
@ -55,10 +55,22 @@ export const createUserStores = () => {
|
|||
[]
|
||||
)
|
||||
|
||||
return {
|
||||
users: {
|
||||
...users,
|
||||
subscribe: enrichedUsers.subscribe,
|
||||
},
|
||||
userId,
|
||||
}
|
||||
}
|
||||
|
||||
export const deriveStores = context => {
|
||||
const { users, userId } = context
|
||||
|
||||
// Generate a lookup map of cell ID to the user that has it selected, to make
|
||||
// lookups inside sheet cells extremely fast
|
||||
const selectedCellMap = derived(
|
||||
[enrichedUsers, userId],
|
||||
[users, userId],
|
||||
([$enrichedUsers, $userId]) => {
|
||||
let map = {}
|
||||
$enrichedUsers.forEach(user => {
|
||||
|
@ -92,15 +104,12 @@ export const createUserStores = () => {
|
|||
|
||||
return {
|
||||
users: {
|
||||
...enrichedUsers,
|
||||
set: users.set,
|
||||
update: users.update,
|
||||
...users,
|
||||
actions: {
|
||||
updateUser,
|
||||
removeUser,
|
||||
},
|
||||
},
|
||||
selectedCellMap,
|
||||
userId,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,52 @@
|
|||
import { writable, get } from "svelte/store"
|
||||
import { writable, get, derived } from "svelte/store"
|
||||
|
||||
export const createValidationStores = () => {
|
||||
export const createStores = () => {
|
||||
const validation = writable({})
|
||||
|
||||
const setError = (cellId, error) => {
|
||||
if (!cellId) {
|
||||
return
|
||||
}
|
||||
validation.update(state => ({
|
||||
...state,
|
||||
[cellId]: error,
|
||||
}))
|
||||
}
|
||||
|
||||
return {
|
||||
validation: {
|
||||
subscribe: validation.subscribe,
|
||||
...validation,
|
||||
actions: {
|
||||
setError: (cellId, error) => {
|
||||
if (!cellId) {
|
||||
return
|
||||
}
|
||||
validation.update(state => ({
|
||||
...state,
|
||||
[cellId]: error,
|
||||
}))
|
||||
},
|
||||
getError: cellId => {
|
||||
return get(validation)[cellId]
|
||||
},
|
||||
setError,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const deriveStores = context => {
|
||||
const { validation, focusedRow, columns, stickyColumn } = context
|
||||
const focusedRowId = derived(focusedRow, $focusedRow => $focusedRow?._id)
|
||||
|
||||
// Store the row ID that was previously focused, so we can remove errors from
|
||||
// it when we focus a new row
|
||||
let previousFocusedRowId = null
|
||||
focusedRowId.subscribe(id => {
|
||||
// Remove validation errors from previous focused row
|
||||
if (previousFocusedRowId) {
|
||||
const $columns = get(columns)
|
||||
const $stickyColumn = get(stickyColumn)
|
||||
validation.update(state => {
|
||||
$columns.forEach(column => {
|
||||
state[`${previousFocusedRowId}-${column.name}`] = null
|
||||
})
|
||||
if ($stickyColumn) {
|
||||
state[`${previousFocusedRowId}-${$stickyColumn.name}`] = null
|
||||
}
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
// Store row ID
|
||||
previousFocusedRowId = id
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import { derived, get } from "svelte/store"
|
||||
|
||||
export const createViewportStores = context => {
|
||||
const { rowHeight, visibleColumns, rows, scroll, bounds } = context
|
||||
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, 0)
|
||||
const height = derived(bounds, $bounds => $bounds.height, 0)
|
||||
export const deriveStores = context => {
|
||||
const {
|
||||
rowHeight,
|
||||
visibleColumns,
|
||||
rows,
|
||||
scrollTop,
|
||||
scrollLeft,
|
||||
width,
|
||||
height,
|
||||
} = context
|
||||
|
||||
// Derive visible rows
|
||||
// Split into multiple stores containing primitives to optimise invalidation
|
||||
|
|
Loading…
Reference in New Issue