Refactor spreadsheet into more discreet components
This commit is contained in:
parent
43eadf2ec6
commit
40df22d791
|
@ -2,65 +2,37 @@
|
||||||
import SheetCell from "./SheetCell.svelte"
|
import SheetCell from "./SheetCell.svelte"
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { Icon } from "@budibase/bbui"
|
import { Icon } from "@budibase/bbui"
|
||||||
import { Checkbox } from "@budibase/bbui"
|
import { getIconForField } from "./utils"
|
||||||
|
import SheetScrollWrapper from "./SheetScrollWrapper.svelte"
|
||||||
|
|
||||||
const { visibleColumns, reorder, selectedRows, rows } =
|
const { visibleColumns, reorder, selectedRows, rows } =
|
||||||
getContext("spreadsheet")
|
getContext("spreadsheet")
|
||||||
|
|
||||||
$: rowCount = $rows.length
|
|
||||||
$: selectedRowCount = Object.values($selectedRows).filter(x => !!x).length
|
|
||||||
|
|
||||||
const getIconForField = field => {
|
|
||||||
const type = field.schema.type
|
|
||||||
if (type === "options") {
|
|
||||||
return "ChevronDown"
|
|
||||||
} else if (type === "datetime") {
|
|
||||||
return "Date"
|
|
||||||
}
|
|
||||||
return "Text"
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectAll = () => {
|
|
||||||
const allSelected = selectedRowCount === rowCount
|
|
||||||
if (allSelected) {
|
|
||||||
$selectedRows = {}
|
|
||||||
} else {
|
|
||||||
let allRows = {}
|
|
||||||
$rows.forEach(row => {
|
|
||||||
allRows[row._id] = true
|
|
||||||
})
|
|
||||||
$selectedRows = allRows
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="row">
|
<div>
|
||||||
<!-- Field headers -->
|
<SheetScrollWrapper scrollVertically={false} wheelInteractive={false}>
|
||||||
<SheetCell header label on:click={selectAll} width="40" left="0">
|
<div class="row">
|
||||||
<Checkbox value={rowCount && selectedRowCount === rowCount} />
|
{#each $visibleColumns as column}
|
||||||
</SheetCell>
|
<SheetCell
|
||||||
{#each $visibleColumns as column}
|
header
|
||||||
<SheetCell
|
reorderSource={$reorder.columnIdx === column.idx}
|
||||||
header
|
reorderTarget={$reorder.swapColumnIdx === column.idx}
|
||||||
sticky={column.idx === 0}
|
on:mousedown={e => reorder.actions.startReordering(column.idx, e)}
|
||||||
reorderSource={$reorder.columnIdx === column.idx}
|
width={column.width}
|
||||||
reorderTarget={$reorder.swapColumnIdx === column.idx}
|
left={column.left}
|
||||||
on:mousedown={column.idx === 0
|
>
|
||||||
? null
|
<Icon
|
||||||
: e => reorder.actions.startReordering(column.idx, e)}
|
size="S"
|
||||||
width={column.width}
|
name={getIconForField(column)}
|
||||||
left={column.left}
|
color="var(--spectrum-global-color-gray-600)"
|
||||||
>
|
/>
|
||||||
<Icon
|
<span>
|
||||||
size="S"
|
{column.name}
|
||||||
name={getIconForField(column)}
|
</span>
|
||||||
color="var(--spectrum-global-color-gray-600)"
|
</SheetCell>
|
||||||
/>
|
{/each}
|
||||||
<span>
|
</div>
|
||||||
{column.name}
|
</SheetScrollWrapper>
|
||||||
</span>
|
|
||||||
</SheetCell>
|
|
||||||
{/each}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -70,6 +42,7 @@
|
||||||
top: 0;
|
top: 0;
|
||||||
width: inherit;
|
width: inherit;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
height: var(--cell-height);
|
||||||
}
|
}
|
||||||
.row :global(> :last-child) {
|
.row :global(> :last-child) {
|
||||||
border-right-width: 1px;
|
border-right-width: 1px;
|
||||||
|
|
|
@ -15,12 +15,8 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="row new">
|
<div class="row new">
|
||||||
<SheetCell label on:click={addRow} width="40" left="0">
|
|
||||||
<Icon hoverable name="Add" size="S" />
|
|
||||||
</SheetCell>
|
|
||||||
{#each $visibleColumns as column}
|
{#each $visibleColumns as column}
|
||||||
<SheetCell
|
<SheetCell
|
||||||
sticky={column.idx === 0}
|
|
||||||
on:click={() => addRow(column)}
|
on:click={() => addRow(column)}
|
||||||
width={column.width}
|
width={column.width}
|
||||||
left={column.left}
|
left={column.left}
|
||||||
|
@ -34,12 +30,10 @@
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: inherit;
|
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);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.row :global(> :last-child) {
|
|
||||||
border-right-width: 1px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const { visibleRows, columns, rand, scroll, visibleColumns, cellHeight } =
|
const {
|
||||||
getContext("spreadsheet")
|
visibleRows,
|
||||||
|
columns,
|
||||||
|
rand,
|
||||||
|
scroll,
|
||||||
|
visibleColumns,
|
||||||
|
cellHeight,
|
||||||
|
stickyColumn,
|
||||||
|
} = getContext("spreadsheet")
|
||||||
const MinColumnWidth = 100
|
const MinColumnWidth = 100
|
||||||
|
|
||||||
let initialMouseX = null
|
let initialMouseX = null
|
||||||
|
@ -14,14 +21,17 @@
|
||||||
|
|
||||||
$: scrollLeft = $scroll.left
|
$: scrollLeft = $scroll.left
|
||||||
$: cutoff = scrollLeft + 40 + $columns[0]?.width || 0
|
$: cutoff = scrollLeft + 40 + $columns[0]?.width || 0
|
||||||
|
$: offset = 40 + $stickyColumn?.width || 0
|
||||||
$: rowCount = $visibleRows.length
|
$: rowCount = $visibleRows.length
|
||||||
|
$: contentHeight = (rowCount + 2) * cellHeight
|
||||||
|
|
||||||
const startResizing = (idx, e) => {
|
const startResizing = (idx, e) => {
|
||||||
// Prevent propagation to stop reordering triggering
|
// Prevent propagation to stop reordering triggering
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
width = $columns[idx].width
|
const col = idx === "sticky" ? $stickyColumn : $columns[idx]
|
||||||
left = $columns[idx].left
|
width = col.width
|
||||||
|
left = col.left
|
||||||
initialWidth = width
|
initialWidth = width
|
||||||
initialMouseX = e.clientX
|
initialMouseX = e.clientX
|
||||||
columnIdx = idx
|
columnIdx = idx
|
||||||
|
@ -41,15 +51,22 @@
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
columns.update(state => {
|
if (columnIdx === "sticky") {
|
||||||
state[columnIdx].width = newWidth
|
stickyColumn.update(state => ({
|
||||||
let offset = state[columnIdx].left + newWidth
|
...state,
|
||||||
for (let i = columnIdx + 1; i < state.length; i++) {
|
width: newWidth,
|
||||||
state[i].left = offset
|
}))
|
||||||
offset += state[i].width
|
} else {
|
||||||
}
|
columns.update(state => {
|
||||||
return [...state]
|
state[columnIdx].width = newWidth
|
||||||
})
|
let offset = state[columnIdx].left + newWidth
|
||||||
|
for (let i = columnIdx + 1; i < state.length; i++) {
|
||||||
|
state[i].left = offset
|
||||||
|
offset += state[i].width
|
||||||
|
}
|
||||||
|
return [...state]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
width = newWidth
|
width = newWidth
|
||||||
}
|
}
|
||||||
|
@ -61,30 +78,38 @@
|
||||||
document.getElementById(`sheet-${rand}`).classList.remove("is-resizing")
|
document.getElementById(`sheet-${rand}`).classList.remove("is-resizing")
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStyle = (col, scrollLeft, rowCount) => {
|
const getStyle = (col, offset, scrollLeft, contentHeight) => {
|
||||||
const left = col.left + col.width - (col.idx === 0 ? 0 : scrollLeft)
|
const left = offset + col.left + col.width - scrollLeft
|
||||||
const contentHeight = (rowCount + 2) * cellHeight
|
|
||||||
return `--left:${left}px; --content-height:${contentHeight}px;`
|
return `--left:${left}px; --content-height:${contentHeight}px;`
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#if $stickyColumn}
|
||||||
|
<div
|
||||||
|
class="resize-slider sticky"
|
||||||
|
class:visible={columnIdx === "sticky"}
|
||||||
|
on:mousedown={e => startResizing("sticky", e)}
|
||||||
|
style="--left:{40 +
|
||||||
|
$stickyColumn.width}px; --content-height:{contentHeight}px;"
|
||||||
|
>
|
||||||
|
<div class="resize-indicator" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{#each $visibleColumns as col}
|
{#each $visibleColumns as col}
|
||||||
{#if col.idx === 0 || col.left + col.width > cutoff}
|
<div
|
||||||
<div
|
class="resize-slider"
|
||||||
class="resize-slider"
|
class:visible={columnIdx === col.idx}
|
||||||
class:visible={columnIdx === col.idx}
|
on:mousedown={e => startResizing(col.idx, e)}
|
||||||
on:mousedown={e => startResizing(col.idx, e)}
|
style={getStyle(col, offset, scrollLeft, contentHeight)}
|
||||||
style={getStyle(col, scrollLeft, rowCount)}
|
>
|
||||||
>
|
<div class="resize-indicator" />
|
||||||
<div class="resize-indicator" />
|
</div>
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.resize-slider {
|
.resize-slider {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: var(--controls-height);
|
top: 0;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
height: var(--cell-height);
|
height: var(--cell-height);
|
||||||
left: var(--left);
|
left: var(--left);
|
||||||
|
@ -97,7 +122,10 @@
|
||||||
.resize-slider.visible {
|
.resize-slider.visible {
|
||||||
cursor: col-resize;
|
cursor: col-resize;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
height: min(var(--content-height), calc(100% - var(--controls-height)));
|
height: min(var(--content-height), 100%);
|
||||||
|
}
|
||||||
|
.resize-slider.sticky {
|
||||||
|
z-index: 25;
|
||||||
}
|
}
|
||||||
.resize-indicator {
|
.resize-indicator {
|
||||||
margin-left: -1px;
|
margin-left: -1px;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { domDebounce, debounce, throttle } from "../../utils/utils"
|
import { domDebounce, debounce, throttle } from "../../utils/utils"
|
||||||
|
|
||||||
const { scroll, bounds, rows, cellHeight, columns } =
|
const { scroll, bounds, rows, cellHeight, columns, stickyColumn } =
|
||||||
getContext("spreadsheet")
|
getContext("spreadsheet")
|
||||||
|
|
||||||
// Bar config
|
// Bar config
|
||||||
|
@ -23,20 +23,28 @@
|
||||||
$: barHeight = Math.max(50, (height / contentHeight) * height)
|
$: barHeight = Math.max(50, (height / contentHeight) * height)
|
||||||
$: availHeight = height - barHeight - 2 * barOffset
|
$: availHeight = height - barHeight - 2 * barOffset
|
||||||
$: maxScrollTop = contentHeight - height
|
$: maxScrollTop = contentHeight - height
|
||||||
$: barTop = barOffset + availHeight * (scrollTop / maxScrollTop)
|
$: barTop = barOffset + cellHeight + availHeight * (scrollTop / maxScrollTop)
|
||||||
|
|
||||||
// Calculate H scrollbar size and offset
|
// Calculate H scrollbar size and offset
|
||||||
$: lastCol = $columns[$columns.length - 1]
|
$: contentWidth = calculateContentWidth($columns, $stickyColumn)
|
||||||
$: contentWidth = lastCol ? lastCol?.left + lastCol?.width : 0
|
$: totalWidth = width + 40 + $stickyColumn?.width || 0
|
||||||
$: barWidth = Math.max(50, (width / contentWidth) * width)
|
$: barWidth = Math.max(50, (totalWidth / contentWidth) * totalWidth)
|
||||||
$: availWidth = width - barWidth - 8
|
$: availWidth = totalWidth - barWidth - 2 * barOffset
|
||||||
$: maxScrollLeft = contentWidth - width
|
$: maxScrollLeft = contentWidth - totalWidth
|
||||||
$: barLeft = 4 + 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 > width
|
||||||
|
|
||||||
|
const calculateContentWidth = (columns, stickyColumn) => {
|
||||||
|
let width = 40 + stickyColumn?.width
|
||||||
|
columns.forEach(col => {
|
||||||
|
width += col.width
|
||||||
|
})
|
||||||
|
return width
|
||||||
|
}
|
||||||
|
|
||||||
// V scrollbar drag handlers
|
// V scrollbar drag handlers
|
||||||
const startVDragging = e => {
|
const startVDragging = e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
|
@ -6,13 +6,17 @@
|
||||||
import { createReorderStores } from "./stores/reorder"
|
import { createReorderStores } from "./stores/reorder"
|
||||||
import { createViewportStores } from "./stores/viewport"
|
import { createViewportStores } from "./stores/viewport"
|
||||||
import { createRowsStore } from "./stores/rows"
|
import { createRowsStore } from "./stores/rows"
|
||||||
import SheetHeader from "./SheetHeader.svelte"
|
import { createColumnsStores } from "./stores/columns"
|
||||||
|
import SheetControls from "./SheetControls.svelte"
|
||||||
import SheetBody from "./SheetBody.svelte"
|
import SheetBody from "./SheetBody.svelte"
|
||||||
import SheetRow from "./SheetRow.svelte"
|
import SheetRow from "./SheetRow.svelte"
|
||||||
import ResizeOverlay from "./ResizeOverlay.svelte"
|
import ResizeOverlay from "./ResizeOverlay.svelte"
|
||||||
import HeaderRow from "./HeaderRow.svelte"
|
import HeaderRow from "./HeaderRow.svelte"
|
||||||
import NewRow from "./NewRow.svelte"
|
import NewRow from "./NewRow.svelte"
|
||||||
import { createAPIClient } from "../../api"
|
import { createAPIClient } from "../../api"
|
||||||
|
import ScrollOverlay from "./ScrollOverlay.svelte"
|
||||||
|
import StickyColumn from "./StickyColumn.svelte"
|
||||||
|
import SheetScrollWrapper from "./SheetScrollWrapper.svelte"
|
||||||
|
|
||||||
export let tableId
|
export let tableId
|
||||||
export let filter
|
export let filter
|
||||||
|
@ -22,12 +26,10 @@
|
||||||
|
|
||||||
// Sheet constants
|
// Sheet constants
|
||||||
const cellHeight = 36
|
const cellHeight = 36
|
||||||
const defaultWidth = 200
|
|
||||||
const rand = Math.random()
|
const rand = Math.random()
|
||||||
|
|
||||||
// State stores
|
// State stores
|
||||||
const tableIdStore = writable()
|
const tableIdStore = writable()
|
||||||
const columns = writable([])
|
|
||||||
const selectedCellId = writable()
|
const selectedCellId = writable()
|
||||||
const selectedRows = writable({})
|
const selectedRows = writable({})
|
||||||
const scroll = writable({
|
const scroll = writable({
|
||||||
|
@ -45,70 +47,45 @@
|
||||||
let context = {
|
let context = {
|
||||||
API: API || createAPIClient(),
|
API: API || createAPIClient(),
|
||||||
rand,
|
rand,
|
||||||
columns,
|
|
||||||
selectedCellId,
|
selectedCellId,
|
||||||
selectedRows,
|
selectedRows,
|
||||||
|
|
||||||
cellHeight,
|
cellHeight,
|
||||||
bounds,
|
bounds,
|
||||||
scroll,
|
scroll,
|
||||||
tableId: tableIdStore,
|
tableId: tableIdStore,
|
||||||
}
|
}
|
||||||
const { rows, schema, primaryDisplay } = createRowsStore(context)
|
const { rows, schema } = createRowsStore(context)
|
||||||
context = { ...context, rows }
|
context = { ...context, rows, schema }
|
||||||
|
const { columns, stickyColumn } = createColumnsStores(context)
|
||||||
|
context = { ...context, columns, stickyColumn }
|
||||||
const { visibleRows, visibleColumns } = createViewportStores(context)
|
const { visibleRows, visibleColumns } = createViewportStores(context)
|
||||||
context = { ...context, visibleRows, visibleColumns }
|
context = { ...context, visibleRows, visibleColumns }
|
||||||
const { reorder } = createReorderStores(context)
|
const { reorder } = createReorderStores(context)
|
||||||
context = { ...context, reorder }
|
context = { ...context, reorder }
|
||||||
|
|
||||||
$: tableIdStore.set(tableId)
|
$: tableIdStore.set(tableId)
|
||||||
$: generateColumns($schema, $primaryDisplay)
|
|
||||||
|
|
||||||
// Generates the column array the first time the schema loads
|
|
||||||
const generateColumns = (schema, primaryDisplay) => {
|
|
||||||
if (!schema) {
|
|
||||||
$columns = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const currentColumns = $columns
|
|
||||||
|
|
||||||
// Get fields in new schema
|
|
||||||
let fields = Object.keys(schema || {})
|
|
||||||
if (primaryDisplay) {
|
|
||||||
fields = [primaryDisplay, ...fields.filter(x => x !== primaryDisplay)]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update columns, removing extraneous columns and adding missing ones
|
|
||||||
let offset = 40
|
|
||||||
$columns = fields.map((field, idx) => {
|
|
||||||
const existing = currentColumns.find(x => x.name === field)
|
|
||||||
const newCol = {
|
|
||||||
idx,
|
|
||||||
name: field,
|
|
||||||
width: existing?.width || defaultWidth,
|
|
||||||
left: offset,
|
|
||||||
schema: schema[field],
|
|
||||||
primaryDisplay: field === primaryDisplay,
|
|
||||||
}
|
|
||||||
offset += newCol.width
|
|
||||||
return newCol
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set context for children to consume
|
// Set context for children to consume
|
||||||
setContext("spreadsheet", context)
|
setContext("spreadsheet", context)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="sheet" style="--cell-height:{cellHeight}px;" id="sheet-{rand}">
|
<div class="sheet" style="--cell-height:{cellHeight}px;" id="sheet-{rand}">
|
||||||
<SheetHeader />
|
<SheetControls />
|
||||||
<HeaderRow />
|
<div class="sheet-data">
|
||||||
|
<StickyColumn />
|
||||||
<SheetBody>
|
<div class="sheet-main">
|
||||||
{#each $visibleRows as row}
|
<HeaderRow />
|
||||||
<SheetRow {row} />
|
<SheetBody>
|
||||||
{/each}
|
{#each $visibleRows as row}
|
||||||
<NewRow />
|
<SheetRow {row} />
|
||||||
</SheetBody>
|
{/each}
|
||||||
<ResizeOverlay />
|
<NewRow />
|
||||||
|
</SheetBody>
|
||||||
|
</div>
|
||||||
|
<ResizeOverlay />
|
||||||
|
<ScrollOverlay />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -133,4 +110,21 @@
|
||||||
.sheet :global(*) {
|
.sheet :global(*) {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sheet-data {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-items: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.sheet-main {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onMount } from "svelte"
|
import { getContext, onMount } from "svelte"
|
||||||
import { Utils } from "../../utils"
|
import { Utils } from "../../utils"
|
||||||
import ScrollOverlay from "./ScrollOverlay.svelte"
|
import SheetScrollWrapper from "./SheetScrollWrapper.svelte"
|
||||||
|
|
||||||
const { columns, selectedCellId, cellHeight, rows, bounds, scroll } =
|
const { columns, selectedCellId, cellHeight, rows, bounds, scroll } =
|
||||||
getContext("spreadsheet")
|
getContext("spreadsheet")
|
||||||
|
@ -10,47 +10,9 @@
|
||||||
|
|
||||||
let ref
|
let ref
|
||||||
|
|
||||||
$: contentHeight = ($rows.length + 2) * cellHeight
|
|
||||||
$: contentWidth = computeContentWidth($columns)
|
|
||||||
$: scrollLeft = $scroll.left
|
$: scrollLeft = $scroll.left
|
||||||
$: scrollTop = $scroll.top
|
$: scrollTop = $scroll.top
|
||||||
|
|
||||||
const computeContentWidth = columns => {
|
|
||||||
if (!columns.length) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
const last = columns[columns.length - 1]
|
|
||||||
return last.left + last.width
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateScrollStore = Utils.domDebounce((left, top) => {
|
|
||||||
scroll.set({ left, top })
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleScroll = e => {
|
|
||||||
updateScrollStore(e.target.scrollLeft, e.target.scrollTop)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleWheel = e => {
|
|
||||||
const step = cellHeight * 3
|
|
||||||
const deltaY = e.deltaY < 0 ? -1 : 1
|
|
||||||
const offset = deltaY * step
|
|
||||||
let newScrollTop = scrollTop
|
|
||||||
newScrollTop += offset
|
|
||||||
newScrollTop = Math.max(0, newScrollTop)
|
|
||||||
newScrollTop = Math.min(
|
|
||||||
newScrollTop,
|
|
||||||
$rows.length * cellHeight - $bounds.height
|
|
||||||
)
|
|
||||||
scroll.update(state => ({
|
|
||||||
...state,
|
|
||||||
top: newScrollTop,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
$: fakeOffsetY = -1 * (scrollTop % cellHeight)
|
|
||||||
$: fakeOffsetX = -1 * scrollLeft
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Observe and record the height of the body
|
// Observe and record the height of the body
|
||||||
const observer = new ResizeObserver(() => {
|
const observer = new ResizeObserver(() => {
|
||||||
|
@ -68,15 +30,10 @@
|
||||||
class="sheet-body"
|
class="sheet-body"
|
||||||
class:horizontally-scrolled={scrollLeft > 0}
|
class:horizontally-scrolled={scrollLeft > 0}
|
||||||
on:click|self={() => ($selectedCellId = null)}
|
on:click|self={() => ($selectedCellId = null)}
|
||||||
on:wheel|passive={handleWheel}
|
|
||||||
>
|
>
|
||||||
<div
|
<SheetScrollWrapper>
|
||||||
class="content"
|
|
||||||
style="width:{contentWidth}px; --offset-y:{fakeOffsetY}px; --offset-x:{fakeOffsetX}px;"
|
|
||||||
>
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</SheetScrollWrapper>
|
||||||
<ScrollOverlay />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -86,18 +43,10 @@
|
||||||
cursor: default;
|
cursor: default;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
height: 0;
|
|
||||||
}
|
}
|
||||||
.sheet-body::-webkit-scrollbar-track {
|
.sheet-body::-webkit-scrollbar-track {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
.content {
|
|
||||||
min-width: 100%;
|
|
||||||
min-height: 100%;
|
|
||||||
background: var(--background-alt);
|
|
||||||
transform: translate3d(var(--offset-x), var(--offset-y), 0);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add shadow to sticky cells when horizontally scrolled */
|
/* Add shadow to sticky cells when horizontally scrolled */
|
||||||
.horizontally-scrolled :global(.cell.sticky) {
|
.horizontally-scrolled :global(.cell.sticky) {
|
||||||
|
|
|
@ -10,11 +10,10 @@
|
||||||
export let reorderTarget = false
|
export let reorderTarget = false
|
||||||
export let left
|
export let left
|
||||||
export let width
|
export let width
|
||||||
export let column
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="cell col-{column}"
|
class="cell"
|
||||||
class:header
|
class:header
|
||||||
class:label
|
class:label
|
||||||
class:row-selected={rowSelected}
|
class:row-selected={rowSelected}
|
||||||
|
@ -26,7 +25,7 @@
|
||||||
on:mouseenter
|
on:mouseenter
|
||||||
on:click
|
on:click
|
||||||
on:mousedown
|
on:mousedown
|
||||||
style="--left: {left}px; --width:{width}px;"
|
style="--width:{width}px; --left:{left}px;"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,7 +38,7 @@
|
||||||
border-color: var(--spectrum-global-color-gray-200);
|
border-color: var(--spectrum-global-color-gray-200);
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
border-left-width: 1px;
|
border-right-width: 1px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
@ -48,10 +47,11 @@
|
||||||
font-size: var(--cell-font-size);
|
font-size: var(--cell-font-size);
|
||||||
gap: var(--cell-spacing);
|
gap: var(--cell-spacing);
|
||||||
background: var(--cell-background);
|
background: var(--cell-background);
|
||||||
position: absolute;
|
|
||||||
transition: border-color 130ms ease-out;
|
transition: border-color 130ms ease-out;
|
||||||
width: var(--width);
|
flex: 0 0 var(--width);
|
||||||
|
position: absolute;
|
||||||
left: var(--left);
|
left: var(--left);
|
||||||
|
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);
|
||||||
|
@ -91,18 +91,6 @@
|
||||||
z-index: 11;
|
z-index: 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sticky styles */
|
|
||||||
.cell.sticky {
|
|
||||||
position: sticky;
|
|
||||||
border-left-width: 0;
|
|
||||||
transform: none;
|
|
||||||
left: 40px;
|
|
||||||
z-index: 5;
|
|
||||||
}
|
|
||||||
.cell.selected.sticky {
|
|
||||||
z-index: 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reorder styles */
|
/* Reorder styles */
|
||||||
.cell.reorder-source {
|
.cell.reorder-source {
|
||||||
background: var(--spectrum-global-color-gray-100);
|
background: var(--spectrum-global-color-gray-100);
|
||||||
|
@ -122,8 +110,8 @@
|
||||||
/* Label cells */
|
/* Label cells */
|
||||||
.cell.label {
|
.cell.label {
|
||||||
padding: var(--cell-padding);
|
padding: var(--cell-padding);
|
||||||
width: 40px;
|
flex: 0 0 40px;
|
||||||
border-left-width: 0;
|
border-right-width: 0;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
|
|
|
@ -2,14 +2,8 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import SpreadsheetCell from "./SheetCell.svelte"
|
import SheetCell from "./SheetCell.svelte"
|
||||||
import OptionsCell from "./cells/OptionsCell.svelte"
|
import { getCellComponent } from "./utils"
|
||||||
import DateCell from "./cells/DateCell.svelte"
|
|
||||||
import MultiSelectCell from "./cells/MultiSelectCell.svelte"
|
|
||||||
import NumberCell from "./cells/NumberCell.svelte"
|
|
||||||
import RelationshipCell from "./cells/RelationshipCell.svelte"
|
|
||||||
import TextCell from "./cells/TextCell.svelte"
|
|
||||||
import { Checkbox } from "@budibase/bbui"
|
|
||||||
|
|
||||||
export let row
|
export let row
|
||||||
|
|
||||||
|
@ -21,57 +15,33 @@
|
||||||
visibleColumns,
|
visibleColumns,
|
||||||
cellHeight,
|
cellHeight,
|
||||||
} = getContext("spreadsheet")
|
} = getContext("spreadsheet")
|
||||||
const TypeComponentMap = {
|
|
||||||
options: OptionsCell,
|
|
||||||
datetime: DateCell,
|
|
||||||
array: MultiSelectCell,
|
|
||||||
number: NumberCell,
|
|
||||||
link: RelationshipCell,
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("mount")
|
console.log("mount")
|
||||||
|
|
||||||
$: rowSelected = !!$selectedRows[row._id]
|
$: rowSelected = !!$selectedRows[row._id]
|
||||||
|
|
||||||
const selectRow = id => {
|
|
||||||
selectedRows.update(state => ({
|
|
||||||
...state,
|
|
||||||
[id]: !state[id],
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<SpreadsheetCell label {rowSelected} on:click={() => selectRow(row._id)}>
|
|
||||||
<div class="checkbox" class:visible={rowSelected}>
|
|
||||||
<Checkbox value={rowSelected} />
|
|
||||||
</div>
|
|
||||||
<div class="number" class:visible={!rowSelected}>
|
|
||||||
{row.__idx + 1}
|
|
||||||
</div>
|
|
||||||
</SpreadsheetCell>
|
|
||||||
{#each $visibleColumns as column (column.name)}
|
{#each $visibleColumns as column (column.name)}
|
||||||
{@const cellIdx = `${row._id}-${column.name}`}
|
{@const cellIdx = `${row._id}-${column.name}`}
|
||||||
<SpreadsheetCell
|
<SheetCell
|
||||||
{rowSelected}
|
{rowSelected}
|
||||||
sticky={column.idx === 0}
|
|
||||||
selected={$selectedCellId === cellIdx}
|
selected={$selectedCellId === cellIdx}
|
||||||
reorderSource={$reorder.columnIdx === column.idx}
|
reorderSource={$reorder.columnIdx === column.idx}
|
||||||
reorderTarget={$reorder.swapColumnIdx === column.idx}
|
reorderTarget={$reorder.swapColumnIdx === column.idx}
|
||||||
on:click={() => ($selectedCellId = cellIdx)}
|
on:click={() => ($selectedCellId = cellIdx)}
|
||||||
width={column.width}
|
width={column.width}
|
||||||
left={column.left}
|
left={column.left}
|
||||||
column={column.idx}
|
|
||||||
>
|
>
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={TypeComponentMap[column.schema.type] || TextCell}
|
this={getCellComponent(column)}
|
||||||
value={row[column.name]}
|
value={row[column.name]}
|
||||||
schema={column.schema}
|
schema={column.schema}
|
||||||
selected={$selectedCellId === cellIdx}
|
selected={$selectedCellId === cellIdx}
|
||||||
onChange={val => rows.actions.updateRow(row._id, column, val)}
|
onChange={val => rows.actions.updateRow(row._id, column, val)}
|
||||||
readonly={column.schema.autocolumn}
|
readonly={column.schema.autocolumn}
|
||||||
/>
|
/>
|
||||||
</SpreadsheetCell>
|
</SheetCell>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -80,32 +50,9 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: inherit;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Styles for label cell */
|
|
||||||
.checkbox {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.number {
|
|
||||||
display: none;
|
|
||||||
color: var(--spectrum-global-color-gray-500);
|
|
||||||
}
|
|
||||||
.row:hover .checkbox,
|
|
||||||
.checkbox.visible {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.number.visible {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.row:hover .number {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add right border to last cell */
|
|
||||||
.row :global(> :last-child) {
|
|
||||||
border-right-width: 1px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
|
const { cellHeight, scroll, bounds, rows, columns, visibleRows } =
|
||||||
|
getContext("spreadsheet")
|
||||||
|
|
||||||
|
export let scrollVertically = true
|
||||||
|
export let scrollHorizontally = true
|
||||||
|
export let wheelInteractive = true
|
||||||
|
|
||||||
|
$: scrollTop = $scroll.top
|
||||||
|
$: scrollLeft = $scroll.left
|
||||||
|
$: offsetY = scrollVertically ? -1 * (scrollTop % cellHeight) : 0
|
||||||
|
$: offsetX = scrollHorizontally ? -1 * scrollLeft : 0
|
||||||
|
$: rowCount = $visibleRows.length
|
||||||
|
$: contentWidth = calculateContentWidth($columns, scrollHorizontally)
|
||||||
|
$: contentHeight = calculateContentHeight(rowCount, scrollVertically)
|
||||||
|
$: style = getStyle(offsetX, offsetY, contentWidth, contentHeight)
|
||||||
|
|
||||||
|
const getStyle = (offsetX, offsetY, contentWidth, contentHeight) => {
|
||||||
|
let style = `--offset-y:${offsetY}px; --offset-x:${offsetX}px;`
|
||||||
|
if (contentWidth) {
|
||||||
|
style += `--width:${contentWidth}px;`
|
||||||
|
}
|
||||||
|
if (contentHeight) {
|
||||||
|
style += `--height:${contentHeight}px;`
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculateContentWidth = (columns, scroll) => {
|
||||||
|
if (!scroll) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
let width = 0
|
||||||
|
columns.forEach(col => (width += col.width))
|
||||||
|
return width
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculateContentHeight = (rowCount, scroll) => {
|
||||||
|
if (!scroll) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return (rowCount + 1) * cellHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleWheel = e => {
|
||||||
|
const step = cellHeight * 3
|
||||||
|
const deltaY = e.deltaY < 0 ? -1 : 1
|
||||||
|
const offset = deltaY * step
|
||||||
|
let newScrollTop = scrollTop
|
||||||
|
newScrollTop += offset
|
||||||
|
newScrollTop = Math.max(0, newScrollTop)
|
||||||
|
newScrollTop = Math.min(
|
||||||
|
newScrollTop,
|
||||||
|
($rows.length + 1) * cellHeight - $bounds.height
|
||||||
|
)
|
||||||
|
scroll.update(state => ({
|
||||||
|
...state,
|
||||||
|
top: newScrollTop,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="scroll-wrapper"
|
||||||
|
{style}
|
||||||
|
on:wheel|passive={wheelInteractive ? handleWheel : null}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.scroll-wrapper {
|
||||||
|
background: var(--background-alt);
|
||||||
|
transform: translate3d(var(--offset-x), var(--offset-y), 0);
|
||||||
|
overflow: hidden;
|
||||||
|
width: var(--width);
|
||||||
|
height: var(--height);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,153 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import { Checkbox, Icon } from "@budibase/bbui"
|
||||||
|
import { getIconForField } from "./utils"
|
||||||
|
import SheetCell from "./SheetCell.svelte"
|
||||||
|
import { getCellComponent } from "./utils"
|
||||||
|
import SheetScrollWrapper from "./SheetScrollWrapper.svelte"
|
||||||
|
|
||||||
|
const { rows, selectedRows, stickyColumn, visibleRows, selectedCellId } =
|
||||||
|
getContext("spreadsheet")
|
||||||
|
|
||||||
|
$: rowCount = $rows.length
|
||||||
|
$: selectedRowCount = Object.values($selectedRows).filter(x => !!x).length
|
||||||
|
$: width = 40 + $stickyColumn?.width || 0
|
||||||
|
|
||||||
|
const selectAll = () => {
|
||||||
|
const allSelected = selectedRowCount === rowCount
|
||||||
|
if (allSelected) {
|
||||||
|
$selectedRows = {}
|
||||||
|
} else {
|
||||||
|
let allRows = {}
|
||||||
|
$rows.forEach(row => {
|
||||||
|
allRows[row._id] = true
|
||||||
|
})
|
||||||
|
$selectedRows = allRows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectRow = id => {
|
||||||
|
selectedRows.update(state => ({
|
||||||
|
...state,
|
||||||
|
[id]: !state[id],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const addRow = async field => {
|
||||||
|
const newRow = await rows.actions.addRow()
|
||||||
|
if (newRow) {
|
||||||
|
$selectedCellId = `${newRow._id}-${field.name}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="sticky-column" style="--width:{width}px;">
|
||||||
|
<div class="row">
|
||||||
|
<!-- Field headers -->
|
||||||
|
<SheetCell header label on:click={selectAll} width="40" left="0">
|
||||||
|
<Checkbox value={rowCount && selectedRowCount === rowCount} />
|
||||||
|
</SheetCell>
|
||||||
|
|
||||||
|
{#if $stickyColumn}
|
||||||
|
<SheetCell
|
||||||
|
header
|
||||||
|
sticky
|
||||||
|
width={$stickyColumn.width}
|
||||||
|
left={$stickyColumn.left}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
size="S"
|
||||||
|
name={getIconForField($stickyColumn)}
|
||||||
|
color="var(--spectrum-global-color-gray-600)"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{$stickyColumn.name}
|
||||||
|
</span>
|
||||||
|
</SheetCell>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SheetScrollWrapper scrollHorizontally={false}>
|
||||||
|
{#each $visibleRows as row}
|
||||||
|
{@const rowSelected = !!$selectedRows[row._id]}
|
||||||
|
<div class="row">
|
||||||
|
<SheetCell
|
||||||
|
label
|
||||||
|
{rowSelected}
|
||||||
|
on:click={() => selectRow(row._id)}
|
||||||
|
width="40"
|
||||||
|
>
|
||||||
|
<div class="checkbox" class:visible={rowSelected}>
|
||||||
|
<Checkbox value={rowSelected} />
|
||||||
|
</div>
|
||||||
|
<div class="number" class:visible={!rowSelected}>
|
||||||
|
{row.__idx + 1}
|
||||||
|
</div>
|
||||||
|
</SheetCell>
|
||||||
|
|
||||||
|
{#if $stickyColumn}
|
||||||
|
{@const cellIdx = `${row._id}-${$stickyColumn.name}`}
|
||||||
|
<SheetCell
|
||||||
|
{rowSelected}
|
||||||
|
sticky
|
||||||
|
selected={$selectedCellId === cellIdx}
|
||||||
|
on:click={() => ($selectedCellId = cellIdx)}
|
||||||
|
width={$stickyColumn.width}
|
||||||
|
left="40"
|
||||||
|
>
|
||||||
|
<svelte:component
|
||||||
|
this={getCellComponent($stickyColumn)}
|
||||||
|
value={row[$stickyColumn.name]}
|
||||||
|
schema={$stickyColumn.schema}
|
||||||
|
selected={$selectedCellId === cellIdx}
|
||||||
|
onChange={val =>
|
||||||
|
rows.actions.updateRow(row._id, $stickyColumn, val)}
|
||||||
|
readonly={$stickyColumn.schema.autocolumn}
|
||||||
|
/>
|
||||||
|
</SheetCell>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<SheetCell label on:click={addRow} width="40">
|
||||||
|
<Icon hoverable name="Add" size="S" />
|
||||||
|
</SheetCell>
|
||||||
|
{#if $stickyColumn}
|
||||||
|
<SheetCell on:click={addRow} width={$stickyColumn.width} left="40" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</SheetScrollWrapper>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.sticky-column {
|
||||||
|
flex: 0 0 var(--width);
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styles for label cell */
|
||||||
|
.checkbox {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.number {
|
||||||
|
display: none;
|
||||||
|
color: var(--spectrum-global-color-gray-500);
|
||||||
|
}
|
||||||
|
.row:hover .checkbox,
|
||||||
|
.checkbox.visible {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.number.visible {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.row:hover .number {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { get, writable } from "svelte/store"
|
||||||
|
|
||||||
|
export const createColumnsStores = context => {
|
||||||
|
const { schema } = context
|
||||||
|
const defaultWidth = 200
|
||||||
|
const columns = writable([])
|
||||||
|
const stickyColumn = writable(null)
|
||||||
|
|
||||||
|
schema.subscribe($schema => {
|
||||||
|
const currentColumns = get(columns)
|
||||||
|
if (!$schema) {
|
||||||
|
columns.set([])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get field list
|
||||||
|
let fields = []
|
||||||
|
Object.entries($schema || {}).forEach(([field, fieldSchema]) => {
|
||||||
|
if (!fieldSchema.primaryDisplay) {
|
||||||
|
fields.push(field)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update columns, removing extraneous columns and adding missing ones
|
||||||
|
let offset = 0
|
||||||
|
columns.set(
|
||||||
|
fields.map((field, idx) => {
|
||||||
|
const existing = currentColumns.find(x => x.name === field)
|
||||||
|
const newCol = {
|
||||||
|
idx,
|
||||||
|
name: field,
|
||||||
|
width: existing?.width || defaultWidth,
|
||||||
|
left: offset,
|
||||||
|
schema: $schema[field],
|
||||||
|
}
|
||||||
|
offset += newCol.width
|
||||||
|
return newCol
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
schema.subscribe($schema => {
|
||||||
|
const primaryDisplay = Object.entries($schema).find(entry => {
|
||||||
|
return entry[1].primaryDisplay
|
||||||
|
})
|
||||||
|
if (!primaryDisplay) {
|
||||||
|
stickyColumn.set(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const existingWidth = get(stickyColumn)?.width
|
||||||
|
const same = primaryDisplay[0] === get(stickyColumn)?.name
|
||||||
|
stickyColumn.set({
|
||||||
|
name: primaryDisplay[0],
|
||||||
|
width: same ? existingWidth : defaultWidth,
|
||||||
|
left: 40,
|
||||||
|
schema: primaryDisplay[1],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
columns,
|
||||||
|
stickyColumn,
|
||||||
|
}
|
||||||
|
}
|
|
@ -76,7 +76,7 @@ export const createReorderStores = context => {
|
||||||
swapColumnIdx++
|
swapColumnIdx++
|
||||||
}
|
}
|
||||||
state.splice(swapColumnIdx, 0, removed[0])
|
state.splice(swapColumnIdx, 0, removed[0])
|
||||||
let offset = 40
|
let offset = 0
|
||||||
return state.map((col, idx) => {
|
return state.map((col, idx) => {
|
||||||
const newCol = {
|
const newCol = {
|
||||||
...col,
|
...col,
|
||||||
|
|
|
@ -15,7 +15,6 @@ export const createRowsStore = context => {
|
||||||
// Exported stores
|
// Exported stores
|
||||||
const rows = writable([])
|
const rows = writable([])
|
||||||
const schema = writable({})
|
const schema = writable({})
|
||||||
const primaryDisplay = writable(null)
|
|
||||||
|
|
||||||
// Local stores for managing fetching data
|
// Local stores for managing fetching data
|
||||||
const query = derived(filter, $filter => buildLuceneQuery($filter))
|
const query = derived(filter, $filter => buildLuceneQuery($filter))
|
||||||
|
@ -62,8 +61,12 @@ export const createRowsStore = context => {
|
||||||
loaded = true
|
loaded = true
|
||||||
rowCacheMap = {}
|
rowCacheMap = {}
|
||||||
rows.set([])
|
rows.set([])
|
||||||
schema.set($$fetch.schema)
|
let newSchema = $$fetch.schema
|
||||||
primaryDisplay.set($$fetch.definition?.primaryDisplay)
|
const primaryDisplay = $$fetch.definition?.primaryDisplay
|
||||||
|
if (primaryDisplay && newSchema[primaryDisplay]) {
|
||||||
|
newSchema[primaryDisplay].primaryDisplay = true
|
||||||
|
}
|
||||||
|
schema.set(newSchema)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process new rows
|
// Process new rows
|
||||||
|
@ -220,6 +223,5 @@ export const createRowsStore = context => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
schema,
|
schema,
|
||||||
primaryDisplay,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,25 +39,26 @@ export const createViewportStores = context => {
|
||||||
if (!$columns.length) {
|
if (!$columns.length) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
let startColIdx = 1
|
let startColIdx = 0
|
||||||
let rightEdge = $columns[1].width
|
let rightEdge = $columns[0].width
|
||||||
while (rightEdge < $scrollLeft) {
|
while (rightEdge < $scrollLeft) {
|
||||||
startColIdx++
|
startColIdx++
|
||||||
rightEdge += $columns[startColIdx].width
|
rightEdge += $columns[startColIdx].width
|
||||||
}
|
}
|
||||||
let endColIdx = startColIdx + 1
|
let endColIdx = startColIdx + 1
|
||||||
let leftEdge = $columns[0].width + 40 + rightEdge
|
let leftEdge = rightEdge
|
||||||
while (leftEdge < $width + $scrollLeft) {
|
while (leftEdge < $width + $scrollLeft) {
|
||||||
leftEdge += $columns[endColIdx]?.width
|
leftEdge += $columns[endColIdx]?.width
|
||||||
endColIdx++
|
endColIdx++
|
||||||
}
|
}
|
||||||
return [
|
return $columns.slice(Math.max(0, startColIdx - 1), endColIdx + 1)
|
||||||
$columns[0],
|
|
||||||
...$columns.slice(Math.max(1, startColIdx - 2), endColIdx + 2),
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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]
|
||||||
|
|
|
@ -1,6 +1,34 @@
|
||||||
|
import OptionsCell from "./cells/OptionsCell.svelte"
|
||||||
|
import DateCell from "./cells/DateCell.svelte"
|
||||||
|
import MultiSelectCell from "./cells/MultiSelectCell.svelte"
|
||||||
|
import NumberCell from "./cells/NumberCell.svelte"
|
||||||
|
import RelationshipCell from "./cells/RelationshipCell.svelte"
|
||||||
|
import TextCell from "./cells/TextCell.svelte"
|
||||||
|
|
||||||
export const getColor = idx => {
|
export const getColor = idx => {
|
||||||
if (idx == null || idx === -1) {
|
if (idx == null || idx === -1) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return `hsla(${((idx + 1) * 222) % 360}, 90%, 75%, 0.3)`
|
return `hsla(${((idx + 1) * 222) % 360}, 90%, 75%, 0.3)`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getIconForField = field => {
|
||||||
|
const type = field.schema.type
|
||||||
|
if (type === "options") {
|
||||||
|
return "ChevronDown"
|
||||||
|
} else if (type === "datetime") {
|
||||||
|
return "Date"
|
||||||
|
}
|
||||||
|
return "Text"
|
||||||
|
}
|
||||||
|
|
||||||
|
const TypeComponentMap = {
|
||||||
|
options: OptionsCell,
|
||||||
|
datetime: DateCell,
|
||||||
|
array: MultiSelectCell,
|
||||||
|
number: NumberCell,
|
||||||
|
link: RelationshipCell,
|
||||||
|
}
|
||||||
|
export const getCellComponent = column => {
|
||||||
|
return TypeComponentMap[column?.schema?.type] || TextCell
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue