Improve sheet integration with data section and add horizontal cell inversion

This commit is contained in:
Andrew Kingston 2023-04-13 12:01:16 +01:00
parent 724bff60f2
commit 69f6834886
32 changed files with 296 additions and 332 deletions

View File

@ -143,7 +143,7 @@
}
fields?.forEach(field => {
const fieldSchema = schema[field]
if (fieldSchema.width) {
if (fieldSchema.width && typeof fieldSchema.width === "string") {
style += ` ${fieldSchema.width}`
} else {
style += " minmax(auto, 1fr)"

View File

@ -21,7 +21,13 @@
</script>
<div class="wrapper">
<Sheet {API} tableId={id} allowAddRows={!isUsersTable}>
<Sheet
{API}
tableId={id}
allowAddRows={!isUsersTable}
allowDeleteRows={!isUsersTable}
on:updatetable={e => tables.updateTable(e.detail)}
>
<svelte:fragment slot="controls">
{#if isInternal}
<SheetCreateViewButton />

View File

@ -1,15 +1,10 @@
<script>
import { fade } from "svelte/transition"
import { goto, params } from "@roxi/routify"
import { Table, Modal, Heading, notifications, Layout } from "@budibase/bbui"
import { API } from "api"
import { Table, Heading, Layout } from "@budibase/bbui"
import Spinner from "components/common/Spinner.svelte"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import DeleteRowsButton from "./buttons/DeleteRowsButton.svelte"
import CreateEditRow from "./modals/CreateEditRow.svelte"
import CreateEditUser from "./modals/CreateEditUser.svelte"
import CreateEditColumn from "./modals/CreateEditColumn.svelte"
import { cloneDeep } from "lodash/fp"
import {
TableNames,
UNEDITABLE_USER_FIELDS,
@ -22,7 +17,6 @@
export let data = []
export let tableId
export let title
export let allowEditing = false
export let loading = false
export let hideAutocolumns
export let rowCount
@ -32,12 +26,7 @@
const dispatch = createEventDispatcher()
let selectedRows = []
let editableColumn
let editableRow
let editRowModal
let editColumnModal
let customRenderers = []
let confirmDelete
$: selectedRows, dispatch("selectionUpdated", selectedRows)
$: isUsersTable = tableId === TableNames.USERS
@ -92,36 +81,6 @@
`/builder/app/${$params.application}/data/table/${tableId}/relationship/${rowId}/${fieldName}`
)
}
const deleteRows = async targetRows => {
try {
await API.deleteRows({
tableId,
rows: targetRows,
})
const deletedRowIds = targetRows.map(row => row._id)
data = data.filter(row => deletedRowIds.indexOf(row._id))
notifications.success(`Successfully deleted ${targetRows.length} rows`)
} catch (error) {
notifications.error("Error deleting rows")
}
}
const editRow = row => {
editableRow = row
if (row) {
editRowModal.show()
}
}
const editColumn = field => {
editableColumn = cloneDeep(schema?.[field])
if (editableColumn) {
editColumnModal.show()
}
}
</script>
<Layout noPadding gap="S">
@ -138,16 +97,6 @@
{/if}
<div class="popovers">
<slot />
{#if !isUsersTable && selectedRows.length > 0}
<DeleteRowsButton
on:updaterows
{selectedRows}
deleteRows={async rows => {
await deleteRows(rows)
resetSelectedRows()
}}
/>
{/if}
</div>
</Layout>
{#key tableId}
@ -160,13 +109,7 @@
{rowCount}
{disableSorting}
{customPlaceholder}
bind:selectedRows
allowSelectRows={allowEditing && !isUsersTable}
allowEditRows={allowEditing}
allowEditColumns={allowEditing}
showAutoColumns={!hideAutocolumns}
on:editcolumn={e => editColumn(e.detail)}
on:editrow={e => editRow(e.detail)}
on:clickrelationship={e => selectRelationship(e.detail)}
on:sort
>
@ -176,42 +119,6 @@
{/key}
</Layout>
<Modal bind:this={editRowModal}>
<svelte:component
this={editRowComponent}
on:updaterows
on:deleteRows={() => {
confirmDelete.show()
}}
row={editableRow}
/>
</Modal>
<ConfirmDialog
bind:this={confirmDelete}
okText="Delete"
onOk={async () => {
if (editableRow) {
await deleteRows([editableRow])
}
editableRow = undefined
}}
onCancel={async () => {
editRow(editableRow)
}}
title="Confirm Deletion"
>
Are you sure you want to delete this row?
</ConfirmDialog>
<Modal bind:this={editColumnModal}>
<CreateEditColumn
field={editableColumn}
on:updatecolumns
onClosed={editColumnModal.hide}
/>
</Modal>
<style>
.table-title {
height: 24px;

View File

@ -57,7 +57,6 @@
{data}
{loading}
{type}
allowEditing={false}
rowCount={10}
bind:hideAutocolumns
>

View File

@ -1,25 +0,0 @@
<script>
import { ActionButton, Modal } from "@budibase/bbui"
import CreateEditColumn from "../modals/CreateEditColumn.svelte"
export let highlighted = false
export let disabled = false
let modal
export const show = () => modal?.show()
</script>
<ActionButton
{disabled}
selected={highlighted}
emphasized={highlighted}
icon="TableColumnAddRight"
quiet
on:click={modal.show}
>
Create column
</ActionButton>
<Modal bind:this={modal}>
<CreateEditColumn on:updatecolumns />
</Modal>

View File

@ -1,27 +0,0 @@
<script>
import { ActionButton, Modal } from "@budibase/bbui"
import CreateEditRow from "../modals/CreateEditRow.svelte"
export let modalContentComponent = CreateEditRow
export let title = "Create row"
export let disabled = false
export let highlighted = false
let modal
export const show = () => modal?.show()
</script>
<ActionButton
{disabled}
emphasized={highlighted}
selected={highlighted}
icon="TableRowAddBottom"
quiet
on:click={modal.show}
>
{title}
</ActionButton>
<Modal bind:this={modal}>
<svelte:component this={modalContentComponent} on:updaterows />
</Modal>

View File

@ -1,15 +0,0 @@
<script>
import { Modal, ActionButton } from "@budibase/bbui"
import CreateViewModal from "../modals/CreateViewModal.svelte"
export let disabled = false
let modal
</script>
<ActionButton {disabled} icon="CollectionAdd" quiet on:click={modal.show}>
Create view
</ActionButton>
<Modal bind:this={modal}>
<CreateViewModal />
</Modal>

View File

@ -1,10 +1,18 @@
<script>
import CreateViewButton from "../CreateViewButton.svelte"
import { getContext } from "svelte"
import { Modal, ActionButton } from "@budibase/bbui"
import CreateViewModal from "../../modals/CreateViewModal.svelte"
const { rows, columns } = getContext("sheet")
let modal
$: disabled = !$columns.length || !$rows.length
</script>
<CreateViewButton {disabled} />
<ActionButton {disabled} icon="CollectionAdd" quiet on:click={modal.show}>
Create view
</ActionButton>
<Modal bind:this={modal}>
<CreateViewModal />
</Modal>

View File

@ -11,7 +11,5 @@
</script>
<Modal bind:this={modal}>
<CreateEditColumn
on:updatecolumns={() => rows.actions.refreshTableDefinition()}
/>
<CreateEditColumn on:updatecolumns={rows.actions.refreshTableDefinition} />
</Modal>

View File

@ -2,16 +2,13 @@
import { getContext, onMount } from "svelte"
import { Modal } from "@budibase/bbui"
import CreateEditColumn from "../CreateEditColumn.svelte"
import { tables } from "stores/backend"
const { rows, subscribe } = getContext("sheet")
let editableColumn
let editColumnModal
const updateColumns = () => {
rows.actions.refreshData()
}
const editColumn = column => {
editableColumn = column
editColumnModal.show()
@ -23,7 +20,6 @@
<Modal bind:this={editColumnModal}>
<CreateEditColumn
field={editableColumn}
on:updatecolumns={updateColumns}
onClosed={editColumnModal.hide}
on:updatecolumns={rows.actions.refreshData}
/>
</Modal>

View File

@ -135,6 +135,17 @@ export function createTablesStore() {
await save(draft)
}
const updateTable = table => {
const index = get(store).list.findIndex(x => x._id === table._id)
if (index === -1) {
return
}
store.update(state => {
state.list[index] = table
return state
})
}
return {
subscribe: derivedStore.subscribe,
fetch,
@ -145,6 +156,7 @@ export function createTablesStore() {
delete: deleteTable,
saveField,
deleteField,
updateTable,
}
}

View File

@ -8,7 +8,8 @@
export let onChange
export let readonly = false
export let api
export let invert = false
export let invertX = false
export let invertY = false
const { API } = getContext("sheet")
const imageExtensions = ["png", "tiff", "gif", "raw", "jpg", "jpeg"]
@ -89,7 +90,7 @@
</div>
{#if isOpen}
<div class="dropzone" class:invert>
<div class="dropzone" class:invertX class:invertY>
<Dropzone
{value}
compact
@ -146,7 +147,11 @@
box-shadow: 0 0 8px 4px rgba(0, 0, 0, 0.15);
padding: var(--cell-padding);
}
.dropzone.invert {
.dropzone.invertX {
left: auto;
right: 0;
}
.dropzone.invertY {
transform: translateY(-100%);
top: 0;
}

View File

@ -17,7 +17,8 @@
export let row
export let cellId
export let updateRow = rows.actions.updateRow
export let invert = false
export let invertX = false
export let invertY = false
const emptyError = writable(null)
@ -74,6 +75,7 @@
onChange={cellAPI.updateValue}
{focused}
{readonly}
{invert}
{invertY}
{invertX}
/>
</SheetCell>

View File

@ -6,7 +6,8 @@
export let onChange
export let readonly = false
export let api
export let invert = false
export let invertX = false
export let invertY = false
let textarea
let isOpen = false
@ -49,7 +50,8 @@
{#if isOpen}
<textarea
class:invert
class:invertX
class:invertY
bind:this={textarea}
value={value || ""}
on:change={handleChange}
@ -91,12 +93,17 @@
position: absolute;
top: 0;
left: 0;
width: calc(100% + 100px);
height: calc(5 * var(--row-height) + 1px);
width: calc(100% + var(--max-cell-render-width-overflow));
height: var(--max-cell-render-height);
z-index: 1;
border-radius: 2px;
resize: none;
}
textarea.invert {
textarea.invertX {
left: auto;
right: 0;
}
textarea.invertY {
transform: translateY(-100%);
top: calc(100% + 1px);
}

View File

@ -10,7 +10,8 @@
export let multi = false
export let readonly = false
export let api
export let invert = false
export let invertX = false
export let invertY = false
let isOpen = false
let focusedOptionIdx = null
@ -97,7 +98,12 @@
</div>
{/if}
{#if isOpen}
<div class="options" class:invert on:wheel={e => e.stopPropagation()}>
<div
class="options"
class:invertX
class:invertY
on:wheel={e => e.stopPropagation()}
>
{#each options as option, idx}
{@const color = getOptionColor(option)}
<div
@ -187,7 +193,11 @@
overflow-y: auto;
border: var(--cell-border);
}
.options.invert {
.options.invertX {
left: auto;
right: 0;
}
.options.invertY {
transform: translateY(-100%);
top: 0;
}

View File

@ -10,7 +10,8 @@
export let focused
export let schema
export let onChange
export let invert = false
export let invertX = false
export let invertY = false
const { API } = getContext("sheet")
@ -215,7 +216,7 @@
</div>
{#if isOpen}
<div class="dropdown" class:invert on:wheel|stopPropagation>
<div class="dropdown" class:invertX class:invertY on:wheel|stopPropagation>
<div class="search">
<Input autofocus quiet type="text" bind:value={searchString} />
</div>
@ -288,7 +289,7 @@
top: 100%;
left: 0;
min-width: 100%;
max-width: calc(100% + 240px);
max-width: calc(100% + var(--max-cell-render-width-overflow));
max-height: var(--max-cell-render-height);
background: var(--cell-background);
border: var(--cell-border);
@ -298,10 +299,14 @@
align-items: stretch;
background-color: var(--cell-background-hover);
}
.dropdown.invert {
.dropdown.invertY {
transform: translateY(-100%);
top: 0;
}
.dropdown.invertX {
left: auto;
right: 0;
}
.results {
overflow-y: auto;
@ -323,7 +328,7 @@
cursor: pointer;
}
.result .badge {
max-width: 340px;
max-width: calc(100% - 30px);
}
.search {

View File

@ -15,8 +15,10 @@
import UserAvatars from "./UserAvatars.svelte"
import KeyboardManager from "../overlays/KeyboardManager.svelte"
import { clickOutside } from "@budibase/bbui"
import SheetControls from "./SheetControls.svelte"
import { MaxCellRenderHeight } from "../lib/constants"
import {
MaxCellRenderHeight,
MaxCellRenderWidthOverflow,
} from "../lib/constants"
import SortButton from "../controls/SortButton.svelte"
import AddColumnButton from "../controls/AddColumnButton.svelte"
import HideColumnsButton from "../controls/HideColumnsButton.svelte"
@ -25,11 +27,11 @@
export let API
export let tableId
export let allowAddRows = true
export let allowSelectRows = true
export let allowAddColumns = true
export let allowEditColumns = true
export let allowExpandRows = true
export let allowEditRows = true
export let allowDeleteRows = true
// Sheet constants
const gutterWidth = 72
@ -39,11 +41,11 @@
const tableIdStore = writable(tableId)
const config = writable({
allowAddRows,
allowSelectRows,
allowAddColumns,
allowEditColumns,
allowExpandRows,
allowEditRows,
allowDeleteRows,
})
// Build up spreadsheet context
@ -64,11 +66,11 @@
$: tableIdStore.set(tableId)
$: config.set({
allowAddRows,
allowSelectRows,
allowAddColumns,
allowEditColumns,
allowExpandRows,
allowEditRows,
allowDeleteRows,
})
// Set context for children to consume
@ -86,13 +88,12 @@
id="sheet-{rand}"
class:is-resizing={$isResizing}
class:is-reordering={$isReordering}
style="--row-height:{$rowHeight}px; --gutter-width:{gutterWidth}px; --max-cell-render-height:{MaxCellRenderHeight}px;"
style="--row-height:{$rowHeight}px; --gutter-width:{gutterWidth}px; --max-cell-render-height:{MaxCellRenderHeight}px; --max-cell-render-width-overflow:{MaxCellRenderWidthOverflow}px;"
>
<div class="controls">
<div class="controls-left">
<AddRowButton />
<AddColumnButton />
<SheetControls />
<slot name="controls" />
<HideColumnsButton />
<SortButton />

View File

@ -2,16 +2,12 @@
import { getContext, onMount } from "svelte"
import SheetScrollWrapper from "./SheetScrollWrapper.svelte"
import SheetRow from "./SheetRow.svelte"
import { MaxCellRenderHeight } from "../lib/constants"
const { bounds, renderedRows, visualRowCapacity, rowHeight } =
const { bounds, renderedRows, rowVerticalInversionIndex } =
getContext("sheet")
let body
$: inversionIdx =
$visualRowCapacity - Math.ceil(MaxCellRenderHeight / $rowHeight) - 2
onMount(() => {
// Observe and record the height of the body
const observer = new ResizeObserver(() => {
@ -27,7 +23,7 @@
<div bind:this={body} class="sheet-body">
<SheetScrollWrapper scrollHorizontally scrollVertically wheelInteractive>
{#each $renderedRows as row, idx}
<SheetRow {row} {idx} invert={idx >= inversionIdx} />
<SheetRow {row} {idx} invertY={idx >= $rowVerticalInversionIndex} />
{/each}
</SheetScrollWrapper>
</div>

View File

@ -1,6 +0,0 @@
<script>
import SortButton from "../controls/SortButton.svelte"
import HideColumnsButton from "../controls/HideColumnsButton.svelte"
import AddRowButton from "../controls/AddRowButton.svelte"
import AddColumnButton from "../controls/AddColumnButton.svelte"
</script>

View File

@ -4,7 +4,7 @@
export let row
export let idx
export let invert = false
export let invertY = false
const {
focusedCellId,
@ -16,6 +16,7 @@
selectedCellMap,
focusedRow,
hiddenColumnsWidth,
columnHorizontalInversionIndex,
} = getContext("sheet")
$: rowSelected = !!$selectedRows[row._id]
@ -28,18 +29,18 @@
<div
class="row"
style={rowFocused ? foo : null}
on:focus
on:mouseenter={() => ($hoveredRowId = row._id)}
on:mouseleave={() => ($hoveredRowId = null)}
>
{#each cols as column (column.name)}
{#each $renderedColumns as column, idx (column.name)}
{@const cellId = `${row._id}-${column.name}`}
<DataCell
{cellId}
{column}
{row}
{invert}
{invertY}
invertX={idx >= $columnHorizontalInversionIndex}
{rowFocused}
highlighted={rowHovered || rowFocused || reorderSource === column.name}
selected={rowSelected}

View File

@ -70,7 +70,7 @@
on:wheel={wheelInteractive ? handleWheel : null}
on:click|self={() => ($focusedCellId = null)}
>
<div {style}>
<div {style} class="inner">
<slot />
</div>
</div>
@ -79,5 +79,13 @@
.outer {
min-width: 100%;
min-height: 100%;
height: 100%;
width: 100%;
max-height: 100%;
max-width: 100%;
overflow: hidden;
}
.inner {
position: absolute;
}
</style>

View File

@ -61,8 +61,8 @@
<SheetCell width={gutterWidth}>
<div class="gutter">
<div class="checkbox visible">
{#if $config.allowSelectRows}
<div on:click={$config.allowSelectRows && selectAll}>
{#if $config.allowDeleteRows}
<div on:click={selectAll}>
<Checkbox
value={rowCount && selectedRowCount === rowCount}
disabled={!$renderedRows.length}
@ -104,14 +104,14 @@
<div
on:click={() => selectRow(row._id)}
class="checkbox"
class:visible={$config.allowSelectRows &&
class:visible={$config.allowDeleteRows &&
(rowSelected || rowHovered || rowFocused)}
>
<Checkbox value={rowSelected} />
</div>
<div
class="number"
class:visible={!$config.allowSelectRows ||
class:visible={!$config.allowDeleteRows ||
!(rowSelected || rowHovered || rowFocused)}
>
{row.__idx + 1}

View File

@ -1 +1,4 @@
export const SheetPadding = 264
export const MaxCellRenderHeight = 216
export const MaxCellRenderWidthOverflow = 200
export const ScrollBarSize = 8

View File

@ -39,7 +39,7 @@
<Menu>
<MenuItem
icon="Delete"
disabled={!$config.allowEditRows}
disabled={!$config.allowDeleteRows}
on:click={deleteRow}>Delete row</MenuItem
>
<MenuItem

View File

@ -1,6 +1,7 @@
<script>
import { getContext } from "svelte"
import { domDebounce } from "../../../utils/utils"
import { ScrollBarSize } from "../lib/constants"
const {
scroll,
@ -17,9 +18,6 @@
height,
} = getContext("sheet")
// Bar config
const barOffset = 8
// State for dragging bars
let initialMouse
let initialScroll
@ -28,17 +26,17 @@
// 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
$: renderHeight = $height - 2 * ScrollBarSize
$: barHeight = Math.max(50, ($height / $contentHeight) * renderHeight)
$: availHeight = renderHeight - barHeight
$: barTop =
barOffset + $rowHeight + availHeight * ($scrollTop / $maxScrollTop)
ScrollBarSize + $rowHeight + availHeight * ($scrollTop / $maxScrollTop)
// Calculate H scrollbar size and offset
$: renderWidth = $screenWidth - 2 * barOffset
$: renderWidth = $screenWidth - 2 * ScrollBarSize
$: barWidth = Math.max(50, ($screenWidth / $contentWidth) * renderWidth)
$: availWidth = renderWidth - barWidth
$: barLeft = barOffset + availWidth * ($scrollLeft / $maxScrollLeft)
$: barLeft = ScrollBarSize + availWidth * ($scrollLeft / $maxScrollLeft)
// V scrollbar drag handlers
const startVDragging = e => {
@ -88,14 +86,14 @@
{#if $showVScrollbar}
<div
class="v-scrollbar"
style="top:{barTop}px; height:{barHeight}px;right:{barOffset}px;"
style="--size:{ScrollBarSize}px; top:{barTop}px; height:{barHeight}px;"
on:mousedown={startVDragging}
/>
{/if}
{#if $showHScrollbar}
<div
class="h-scrollbar"
style="left:{barLeft}px; width:{barWidth}px;bottom:{barOffset}px;"
style="--size:{ScrollBarSize}px; left:{barLeft}px; width:{barWidth}px;"
on:mousedown={startHDragging}
/>
{/if}
@ -112,9 +110,11 @@
opacity: 1;
}
.v-scrollbar {
width: 8px;
width: var(--size);
right: var(--size);
}
.h-scrollbar {
height: 8px;
height: var(--size);
bottom: var(--size);
}
</style>

View File

@ -1,4 +1,5 @@
import { derived, get, writable } from "svelte/store"
import { cloneDeep } from "lodash/fp"
export const DefaultColumnWidth = 200
@ -16,7 +17,6 @@ export const createStores = () => {
const enriched = {
...column,
left: offset,
order: idx,
}
if (column.visible) {
offset += column.width
@ -47,7 +47,7 @@ export const createStores = () => {
}
export const deriveStores = context => {
const { table, gutterWidth, columns, stickyColumn } = context
const { table, gutterWidth, columns, stickyColumn, API, dispatch } = context
// Merge new schema fields with existing schema in order to preserve widths
table.subscribe($table => {
@ -77,21 +77,13 @@ export const deriveStores = context => {
// Update columns, removing extraneous columns and adding missing ones
columns.set(
fields
.map(field => {
// Check if there is an existing column with this name so we can keep
// the width setting
let existing = currentColumns.find(x => x.name === field)
if (!existing && currentStickyColumn?.name === field) {
existing = currentStickyColumn
}
return {
name: field,
width: existing?.width || schema[field].width || DefaultColumnWidth,
schema: schema[field],
visible: existing?.visible ?? true,
order: schema[field].order,
}
})
.map(field => ({
name: field,
width: schema[field].width || DefaultColumnWidth,
schema: schema[field],
visible: schema[field].visible ?? true,
order: schema[field].order,
}))
.sort((a, b) => {
// Sort by order first
const orderA = a.order
@ -136,5 +128,69 @@ export const deriveStores = context => {
})
})
return null
// Updates a columns width
const updateColumnWidth = async (columnName, width) => {
const $table = get(table)
await updateTable({
...$table,
schema: {
...$table.schema,
[columnName]: {
...$table.schema[columnName],
width,
},
},
})
}
// Updates a columns visibility
const updateColumnVisibility = async (columnName, visible) => {
const $table = get(table)
await updateTable({
...$table,
schema: {
...$table.schema,
[columnName]: {
...$table.schema[columnName],
visible,
},
},
})
}
// Updates the orders of columns
const updateColumnOrders = async newColumns => {
const $table = get(table)
const newSchema = cloneDeep($table.schema)
Object.keys(newSchema).forEach(column => {
const index = newColumns.indexOf(column)
if (index !== -1) {
newSchema[column].order = index
} else {
delete newSchema[column].order
}
})
await updateTable({
...$table,
schema: newSchema,
})
}
// Updates the table definition
const updateTable = async newTable => {
table.set(newTable)
dispatch("updatetable", newTable)
await API.saveTable(newTable)
}
return {
columns: {
...columns,
actions: {
updateColumnWidth,
updateColumnVisibility,
updateColumnOrders,
},
},
}
}

View File

@ -24,19 +24,28 @@ export const createStores = () => {
}
export const deriveStores = context => {
const { reorder, columns, scroll, bounds, stickyColumn, ui, table, API } =
context
const {
reorder,
columns,
visibleColumns,
scroll,
bounds,
stickyColumn,
ui,
table,
API,
} = context
// Callback when dragging on a colum header and starting reordering
const startReordering = (column, e) => {
const $columns = get(columns)
const $visibleColumns = get(visibleColumns)
const $bounds = get(bounds)
const $scroll = get(scroll)
const $stickyColumn = get(stickyColumn)
ui.actions.blur()
// Generate new breakpoints for the current columns
let breakpoints = $columns.map(col => ({
let breakpoints = $visibleColumns.map(col => ({
x: col.left + col.width,
column: col.name,
}))
@ -93,6 +102,22 @@ export const deriveStores = context => {
const stopReordering = async () => {
// Swap position of columns
let { sourceColumn, targetColumn } = get(reorder)
moveColumn(sourceColumn, targetColumn)
// Reset state
reorder.set(reorderInitialState)
// Remove event handlers
document.removeEventListener("mousemove", onReorderMouseMove)
document.removeEventListener("mouseup", stopReordering)
// Save column changes
await saveOrderChanges()
}
// Moves a column after another columns.
// An undefined target column will move the source to index 0.
const moveColumn = (sourceColumn, targetColumn) => {
let $columns = get(columns)
let sourceIdx = $columns.findIndex(x => x.name === sourceColumn)
let targetIdx = $columns.findIndex(x => x.name === targetColumn)
@ -105,61 +130,31 @@ export const deriveStores = context => {
state.splice(targetIdx, 0, removed[0])
return state.slice()
})
// Reset state
reorder.set(reorderInitialState)
// Remove event handlers
document.removeEventListener("mousemove", onReorderMouseMove)
document.removeEventListener("mouseup", stopReordering)
// Persist changes
await saveOrderChanges()
}
// Moves a column one place left (as appears visually)
const moveColumnLeft = async column => {
const $columns = get(columns)
const sourceIdx = $columns.findIndex(x => x.name === column)
if (sourceIdx === 0) {
return
}
columns.update(state => {
let tmp = state[sourceIdx]
state[sourceIdx] = state[sourceIdx - 1]
state[sourceIdx - 1] = tmp
return state.slice()
})
// Persist changes
const $visibleColumns = get(visibleColumns)
const sourceIdx = $visibleColumns.findIndex(x => x.name === column)
moveColumn(column, $visibleColumns[sourceIdx - 2]?.name)
await saveOrderChanges()
}
// Moves a column one place right (as appears visually)
const moveColumnRight = async column => {
const $columns = get(columns)
const sourceIdx = $columns.findIndex(x => x.name === column)
if (sourceIdx === $columns.length - 1) {
const $visibleColumns = get(visibleColumns)
const sourceIdx = $visibleColumns.findIndex(x => x.name === column)
if (sourceIdx === $visibleColumns.length - 1) {
return
}
columns.update(state => {
let tmp = state[sourceIdx]
state[sourceIdx] = state[sourceIdx + 1]
state[sourceIdx + 1] = tmp
return state.slice()
})
// Persist changes
moveColumn(column, $visibleColumns[sourceIdx + 1]?.name)
await saveOrderChanges()
}
// Saves order changes as part of table metadata
const saveOrderChanges = async () => {
const $table = cloneDeep(get(table))
const $columns = get(columns)
$columns.forEach(column => {
$table.schema[column.name].order = column.order
})
const newTable = await API.saveTable($table)
table.set(newTable)
const newOrder = get(columns).map(column => column.name)
await columns.actions.updateColumnOrders(newOrder)
}
return {

View File

@ -23,7 +23,7 @@ export const createStores = () => {
}
export const deriveStores = context => {
const { resize, columns, stickyColumn, ui, table, API, rows } = context
const { resize, columns, stickyColumn, ui } = context
// Starts resizing a certain column
const startResizing = (column, e) => {
@ -93,41 +93,25 @@ export const deriveStores = context => {
// Persist width if it changed
if ($resize.width !== $resize.initialWidth) {
await saveNewColumnWidth($resize.column, $resize.width)
await columns.actions.updateColumnWidth($resize.column, $resize.width)
}
}
// Resets a column size back to default
const resetSize = async column => {
let columnIdx = get(columns).findIndex(col => col.name === column.name)
if (columnIdx === -1) {
stickyColumn.update(state => ({
...state,
width: DefaultColumnWidth,
}))
} else {
columns.update(state => {
state[columnIdx].width = DefaultColumnWidth
return [...state]
})
}
await saveNewColumnWidth(column.name, DefaultColumnWidth)
}
// Saves a new column width as part of table metadata
const saveNewColumnWidth = async (columnName, width) => {
const $table = get(table)
const newDefinition = await API.saveTable({
...$table,
schema: {
...$table.schema,
[columnName]: {
...$table.schema[columnName],
width,
},
},
})
table.set(newDefinition)
// let columnIdx = get(columns).findIndex(col => col.name === column.name)
// if (columnIdx === -1) {
// stickyColumn.update(state => ({
// ...state,
// width: DefaultColumnWidth,
// }))
// } else {
// columns.update(state => {
// state[columnIdx].width = DefaultColumnWidth
// return [...state]
// })
// }
await columns.actions.updateColumnWidth(column.name, DefaultColumnWidth)
}
return {

View File

@ -121,6 +121,8 @@ export const deriveStores = context => {
instanceLoaded.set(true)
scroll.set({ top: 0, left: 0 })
} else if (resetRows) {
table.set($fetch.definition)
// Only reset top scroll position when resetting rows
scroll.update(state => ({ ...state, top: 0 }))
}

View File

@ -1,6 +1,6 @@
import { writable, derived, get } from "svelte/store"
import { tick } from "svelte"
import { DefaultColumnWidth } from "./columns"
import { SheetPadding } from "../lib/constants"
export const createStores = () => {
const scroll = writable({
@ -35,7 +35,6 @@ export const deriveStores = context => {
width,
height,
} = context
const padding = 264
// Memoize store primitives
const stickyColumnWidth = derived(stickyColumn, $col => $col?.width || 0, 0)
@ -43,7 +42,7 @@ export const deriveStores = context => {
// Derive vertical limits
const contentHeight = derived(
[rows, rowHeight],
([$rows, $rowHeight]) => $rows.length * $rowHeight + padding,
([$rows, $rowHeight]) => $rows.length * $rowHeight + SheetPadding,
0
)
const maxScrollTop = derived(
@ -56,7 +55,7 @@ export const deriveStores = context => {
const contentWidth = derived(
[visibleColumns, stickyColumnWidth],
([$visibleColumns, $stickyColumnWidth]) => {
let width = gutterWidth + padding + $stickyColumnWidth
let width = gutterWidth + SheetPadding + $stickyColumnWidth
$visibleColumns.forEach(col => {
width += col.width
})
@ -146,7 +145,7 @@ export const deriveStores = context => {
const $visibleColumns = get(visibleColumns)
const columnName = $focusedCellId?.split("-")[1]
const column = $visibleColumns.find(col => col.name === columnName)
const horizontalOffset = DefaultColumnWidth
const horizontalOffset = 50
if (!column) {
return
}

View File

@ -1,4 +1,9 @@
import { derived, get } from "svelte/store"
import {
MaxCellRenderHeight,
MaxCellRenderWidthOverflow,
ScrollBarSize,
} from "../lib/constants"
export const deriveStores = context => {
const {
@ -96,11 +101,41 @@ export const deriveStores = context => {
0
)
// Determine the row index at which we should start vertically inverting cell
// dropdowns
const rowVerticalInversionIndex = derived(
[visualRowCapacity, rowHeight],
([$visualRowCapacity, $rowHeight]) => {
return (
$visualRowCapacity - Math.ceil(MaxCellRenderHeight / $rowHeight) - 2
)
}
)
// Determine the column index at which we should start horizontally inverting
// cell dropdowns
const columnHorizontalInversionIndex = derived(
[renderedColumns, scrollLeft, width],
([$renderedColumns, $scrollLeft, $width]) => {
const cutoff = $width + $scrollLeft - ScrollBarSize * 3
let inversionIdx = $renderedColumns.length
for (let i = $renderedColumns.length - 1; i >= 0; i--, inversionIdx--) {
const rightEdge = $renderedColumns[i].left + $renderedColumns[i].width
if (rightEdge + MaxCellRenderWidthOverflow < cutoff) {
break
}
}
return inversionIdx
}
)
return {
scrolledRowCount,
visualRowCapacity,
renderedRows,
renderedColumns,
hiddenColumnsWidth,
rowVerticalInversionIndex,
columnHorizontalInversionIndex,
}
}

View File

@ -31,6 +31,8 @@ export interface FieldSchema {
timeOnly?: boolean
lastID?: number
useRichText?: boolean | null
order?: number
width?: number
meta?: {
toTable: string
toKey: string