Improve sheet integration with data section and add horizontal cell inversion
This commit is contained in:
parent
724bff60f2
commit
69f6834886
|
@ -143,7 +143,7 @@
|
||||||
}
|
}
|
||||||
fields?.forEach(field => {
|
fields?.forEach(field => {
|
||||||
const fieldSchema = schema[field]
|
const fieldSchema = schema[field]
|
||||||
if (fieldSchema.width) {
|
if (fieldSchema.width && typeof fieldSchema.width === "string") {
|
||||||
style += ` ${fieldSchema.width}`
|
style += ` ${fieldSchema.width}`
|
||||||
} else {
|
} else {
|
||||||
style += " minmax(auto, 1fr)"
|
style += " minmax(auto, 1fr)"
|
||||||
|
|
|
@ -21,7 +21,13 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper">
|
<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">
|
<svelte:fragment slot="controls">
|
||||||
{#if isInternal}
|
{#if isInternal}
|
||||||
<SheetCreateViewButton />
|
<SheetCreateViewButton />
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { fade } from "svelte/transition"
|
import { fade } from "svelte/transition"
|
||||||
import { goto, params } from "@roxi/routify"
|
import { goto, params } from "@roxi/routify"
|
||||||
import { Table, Modal, Heading, notifications, Layout } from "@budibase/bbui"
|
import { Table, Heading, Layout } from "@budibase/bbui"
|
||||||
import { API } from "api"
|
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
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 CreateEditRow from "./modals/CreateEditRow.svelte"
|
||||||
import CreateEditUser from "./modals/CreateEditUser.svelte"
|
import CreateEditUser from "./modals/CreateEditUser.svelte"
|
||||||
import CreateEditColumn from "./modals/CreateEditColumn.svelte"
|
|
||||||
import { cloneDeep } from "lodash/fp"
|
|
||||||
import {
|
import {
|
||||||
TableNames,
|
TableNames,
|
||||||
UNEDITABLE_USER_FIELDS,
|
UNEDITABLE_USER_FIELDS,
|
||||||
|
@ -22,7 +17,6 @@
|
||||||
export let data = []
|
export let data = []
|
||||||
export let tableId
|
export let tableId
|
||||||
export let title
|
export let title
|
||||||
export let allowEditing = false
|
|
||||||
export let loading = false
|
export let loading = false
|
||||||
export let hideAutocolumns
|
export let hideAutocolumns
|
||||||
export let rowCount
|
export let rowCount
|
||||||
|
@ -32,12 +26,7 @@
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let selectedRows = []
|
let selectedRows = []
|
||||||
let editableColumn
|
|
||||||
let editableRow
|
|
||||||
let editRowModal
|
|
||||||
let editColumnModal
|
|
||||||
let customRenderers = []
|
let customRenderers = []
|
||||||
let confirmDelete
|
|
||||||
|
|
||||||
$: selectedRows, dispatch("selectionUpdated", selectedRows)
|
$: selectedRows, dispatch("selectionUpdated", selectedRows)
|
||||||
$: isUsersTable = tableId === TableNames.USERS
|
$: isUsersTable = tableId === TableNames.USERS
|
||||||
|
@ -92,36 +81,6 @@
|
||||||
`/builder/app/${$params.application}/data/table/${tableId}/relationship/${rowId}/${fieldName}`
|
`/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>
|
</script>
|
||||||
|
|
||||||
<Layout noPadding gap="S">
|
<Layout noPadding gap="S">
|
||||||
|
@ -138,16 +97,6 @@
|
||||||
{/if}
|
{/if}
|
||||||
<div class="popovers">
|
<div class="popovers">
|
||||||
<slot />
|
<slot />
|
||||||
{#if !isUsersTable && selectedRows.length > 0}
|
|
||||||
<DeleteRowsButton
|
|
||||||
on:updaterows
|
|
||||||
{selectedRows}
|
|
||||||
deleteRows={async rows => {
|
|
||||||
await deleteRows(rows)
|
|
||||||
resetSelectedRows()
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
{#key tableId}
|
{#key tableId}
|
||||||
|
@ -160,13 +109,7 @@
|
||||||
{rowCount}
|
{rowCount}
|
||||||
{disableSorting}
|
{disableSorting}
|
||||||
{customPlaceholder}
|
{customPlaceholder}
|
||||||
bind:selectedRows
|
|
||||||
allowSelectRows={allowEditing && !isUsersTable}
|
|
||||||
allowEditRows={allowEditing}
|
|
||||||
allowEditColumns={allowEditing}
|
|
||||||
showAutoColumns={!hideAutocolumns}
|
showAutoColumns={!hideAutocolumns}
|
||||||
on:editcolumn={e => editColumn(e.detail)}
|
|
||||||
on:editrow={e => editRow(e.detail)}
|
|
||||||
on:clickrelationship={e => selectRelationship(e.detail)}
|
on:clickrelationship={e => selectRelationship(e.detail)}
|
||||||
on:sort
|
on:sort
|
||||||
>
|
>
|
||||||
|
@ -176,42 +119,6 @@
|
||||||
{/key}
|
{/key}
|
||||||
</Layout>
|
</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>
|
<style>
|
||||||
.table-title {
|
.table-title {
|
||||||
height: 24px;
|
height: 24px;
|
||||||
|
|
|
@ -57,7 +57,6 @@
|
||||||
{data}
|
{data}
|
||||||
{loading}
|
{loading}
|
||||||
{type}
|
{type}
|
||||||
allowEditing={false}
|
|
||||||
rowCount={10}
|
rowCount={10}
|
||||||
bind:hideAutocolumns
|
bind:hideAutocolumns
|
||||||
>
|
>
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -1,10 +1,18 @@
|
||||||
<script>
|
<script>
|
||||||
import CreateViewButton from "../CreateViewButton.svelte"
|
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
import { Modal, ActionButton } from "@budibase/bbui"
|
||||||
|
import CreateViewModal from "../../modals/CreateViewModal.svelte"
|
||||||
|
|
||||||
const { rows, columns } = getContext("sheet")
|
const { rows, columns } = getContext("sheet")
|
||||||
|
|
||||||
|
let modal
|
||||||
|
|
||||||
$: disabled = !$columns.length || !$rows.length
|
$: disabled = !$columns.length || !$rows.length
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CreateViewButton {disabled} />
|
<ActionButton {disabled} icon="CollectionAdd" quiet on:click={modal.show}>
|
||||||
|
Create view
|
||||||
|
</ActionButton>
|
||||||
|
<Modal bind:this={modal}>
|
||||||
|
<CreateViewModal />
|
||||||
|
</Modal>
|
||||||
|
|
|
@ -11,7 +11,5 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
<CreateEditColumn
|
<CreateEditColumn on:updatecolumns={rows.actions.refreshTableDefinition} />
|
||||||
on:updatecolumns={() => rows.actions.refreshTableDefinition()}
|
|
||||||
/>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -2,16 +2,13 @@
|
||||||
import { getContext, onMount } from "svelte"
|
import { getContext, onMount } from "svelte"
|
||||||
import { Modal } from "@budibase/bbui"
|
import { Modal } from "@budibase/bbui"
|
||||||
import CreateEditColumn from "../CreateEditColumn.svelte"
|
import CreateEditColumn from "../CreateEditColumn.svelte"
|
||||||
|
import { tables } from "stores/backend"
|
||||||
|
|
||||||
const { rows, subscribe } = getContext("sheet")
|
const { rows, subscribe } = getContext("sheet")
|
||||||
|
|
||||||
let editableColumn
|
let editableColumn
|
||||||
let editColumnModal
|
let editColumnModal
|
||||||
|
|
||||||
const updateColumns = () => {
|
|
||||||
rows.actions.refreshData()
|
|
||||||
}
|
|
||||||
|
|
||||||
const editColumn = column => {
|
const editColumn = column => {
|
||||||
editableColumn = column
|
editableColumn = column
|
||||||
editColumnModal.show()
|
editColumnModal.show()
|
||||||
|
@ -23,7 +20,6 @@
|
||||||
<Modal bind:this={editColumnModal}>
|
<Modal bind:this={editColumnModal}>
|
||||||
<CreateEditColumn
|
<CreateEditColumn
|
||||||
field={editableColumn}
|
field={editableColumn}
|
||||||
on:updatecolumns={updateColumns}
|
on:updatecolumns={rows.actions.refreshData}
|
||||||
onClosed={editColumnModal.hide}
|
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -135,6 +135,17 @@ export function createTablesStore() {
|
||||||
await save(draft)
|
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 {
|
return {
|
||||||
subscribe: derivedStore.subscribe,
|
subscribe: derivedStore.subscribe,
|
||||||
fetch,
|
fetch,
|
||||||
|
@ -145,6 +156,7 @@ export function createTablesStore() {
|
||||||
delete: deleteTable,
|
delete: deleteTable,
|
||||||
saveField,
|
saveField,
|
||||||
deleteField,
|
deleteField,
|
||||||
|
updateTable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
export let onChange
|
export let onChange
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let api
|
export let api
|
||||||
export let invert = false
|
export let invertX = false
|
||||||
|
export let invertY = false
|
||||||
|
|
||||||
const { API } = getContext("sheet")
|
const { API } = getContext("sheet")
|
||||||
const imageExtensions = ["png", "tiff", "gif", "raw", "jpg", "jpeg"]
|
const imageExtensions = ["png", "tiff", "gif", "raw", "jpg", "jpeg"]
|
||||||
|
@ -89,7 +90,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if isOpen}
|
{#if isOpen}
|
||||||
<div class="dropzone" class:invert>
|
<div class="dropzone" class:invertX class:invertY>
|
||||||
<Dropzone
|
<Dropzone
|
||||||
{value}
|
{value}
|
||||||
compact
|
compact
|
||||||
|
@ -146,7 +147,11 @@
|
||||||
box-shadow: 0 0 8px 4px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 0 8px 4px rgba(0, 0, 0, 0.15);
|
||||||
padding: var(--cell-padding);
|
padding: var(--cell-padding);
|
||||||
}
|
}
|
||||||
.dropzone.invert {
|
.dropzone.invertX {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.dropzone.invertY {
|
||||||
transform: translateY(-100%);
|
transform: translateY(-100%);
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
export let row
|
export let row
|
||||||
export let cellId
|
export let cellId
|
||||||
export let updateRow = rows.actions.updateRow
|
export let updateRow = rows.actions.updateRow
|
||||||
export let invert = false
|
export let invertX = false
|
||||||
|
export let invertY = false
|
||||||
|
|
||||||
const emptyError = writable(null)
|
const emptyError = writable(null)
|
||||||
|
|
||||||
|
@ -74,6 +75,7 @@
|
||||||
onChange={cellAPI.updateValue}
|
onChange={cellAPI.updateValue}
|
||||||
{focused}
|
{focused}
|
||||||
{readonly}
|
{readonly}
|
||||||
{invert}
|
{invertY}
|
||||||
|
{invertX}
|
||||||
/>
|
/>
|
||||||
</SheetCell>
|
</SheetCell>
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
export let onChange
|
export let onChange
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let api
|
export let api
|
||||||
export let invert = false
|
export let invertX = false
|
||||||
|
export let invertY = false
|
||||||
|
|
||||||
let textarea
|
let textarea
|
||||||
let isOpen = false
|
let isOpen = false
|
||||||
|
@ -49,7 +50,8 @@
|
||||||
|
|
||||||
{#if isOpen}
|
{#if isOpen}
|
||||||
<textarea
|
<textarea
|
||||||
class:invert
|
class:invertX
|
||||||
|
class:invertY
|
||||||
bind:this={textarea}
|
bind:this={textarea}
|
||||||
value={value || ""}
|
value={value || ""}
|
||||||
on:change={handleChange}
|
on:change={handleChange}
|
||||||
|
@ -91,12 +93,17 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: calc(100% + 100px);
|
width: calc(100% + var(--max-cell-render-width-overflow));
|
||||||
height: calc(5 * var(--row-height) + 1px);
|
height: var(--max-cell-render-height);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
resize: none;
|
||||||
}
|
}
|
||||||
textarea.invert {
|
textarea.invertX {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
textarea.invertY {
|
||||||
transform: translateY(-100%);
|
transform: translateY(-100%);
|
||||||
top: calc(100% + 1px);
|
top: calc(100% + 1px);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
export let multi = false
|
export let multi = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let api
|
export let api
|
||||||
export let invert = false
|
export let invertX = false
|
||||||
|
export let invertY = false
|
||||||
|
|
||||||
let isOpen = false
|
let isOpen = false
|
||||||
let focusedOptionIdx = null
|
let focusedOptionIdx = null
|
||||||
|
@ -97,7 +98,12 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if isOpen}
|
{#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}
|
{#each options as option, idx}
|
||||||
{@const color = getOptionColor(option)}
|
{@const color = getOptionColor(option)}
|
||||||
<div
|
<div
|
||||||
|
@ -187,7 +193,11 @@
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border: var(--cell-border);
|
border: var(--cell-border);
|
||||||
}
|
}
|
||||||
.options.invert {
|
.options.invertX {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.options.invertY {
|
||||||
transform: translateY(-100%);
|
transform: translateY(-100%);
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
export let focused
|
export let focused
|
||||||
export let schema
|
export let schema
|
||||||
export let onChange
|
export let onChange
|
||||||
export let invert = false
|
export let invertX = false
|
||||||
|
export let invertY = false
|
||||||
|
|
||||||
const { API } = getContext("sheet")
|
const { API } = getContext("sheet")
|
||||||
|
|
||||||
|
@ -215,7 +216,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if isOpen}
|
{#if isOpen}
|
||||||
<div class="dropdown" class:invert on:wheel|stopPropagation>
|
<div class="dropdown" class:invertX class:invertY on:wheel|stopPropagation>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<Input autofocus quiet type="text" bind:value={searchString} />
|
<Input autofocus quiet type="text" bind:value={searchString} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -288,7 +289,7 @@
|
||||||
top: 100%;
|
top: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
min-width: 100%;
|
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);
|
max-height: var(--max-cell-render-height);
|
||||||
background: var(--cell-background);
|
background: var(--cell-background);
|
||||||
border: var(--cell-border);
|
border: var(--cell-border);
|
||||||
|
@ -298,10 +299,14 @@
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
background-color: var(--cell-background-hover);
|
background-color: var(--cell-background-hover);
|
||||||
}
|
}
|
||||||
.dropdown.invert {
|
.dropdown.invertY {
|
||||||
transform: translateY(-100%);
|
transform: translateY(-100%);
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
.dropdown.invertX {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.results {
|
.results {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
@ -323,7 +328,7 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.result .badge {
|
.result .badge {
|
||||||
max-width: 340px;
|
max-width: calc(100% - 30px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
import UserAvatars from "./UserAvatars.svelte"
|
import UserAvatars from "./UserAvatars.svelte"
|
||||||
import KeyboardManager from "../overlays/KeyboardManager.svelte"
|
import KeyboardManager from "../overlays/KeyboardManager.svelte"
|
||||||
import { clickOutside } from "@budibase/bbui"
|
import { clickOutside } from "@budibase/bbui"
|
||||||
import SheetControls from "./SheetControls.svelte"
|
import {
|
||||||
import { MaxCellRenderHeight } from "../lib/constants"
|
MaxCellRenderHeight,
|
||||||
|
MaxCellRenderWidthOverflow,
|
||||||
|
} from "../lib/constants"
|
||||||
import SortButton from "../controls/SortButton.svelte"
|
import SortButton from "../controls/SortButton.svelte"
|
||||||
import AddColumnButton from "../controls/AddColumnButton.svelte"
|
import AddColumnButton from "../controls/AddColumnButton.svelte"
|
||||||
import HideColumnsButton from "../controls/HideColumnsButton.svelte"
|
import HideColumnsButton from "../controls/HideColumnsButton.svelte"
|
||||||
|
@ -25,11 +27,11 @@
|
||||||
export let API
|
export let API
|
||||||
export let tableId
|
export let tableId
|
||||||
export let allowAddRows = true
|
export let allowAddRows = true
|
||||||
export let allowSelectRows = true
|
|
||||||
export let allowAddColumns = true
|
export let allowAddColumns = true
|
||||||
export let allowEditColumns = true
|
export let allowEditColumns = true
|
||||||
export let allowExpandRows = true
|
export let allowExpandRows = true
|
||||||
export let allowEditRows = true
|
export let allowEditRows = true
|
||||||
|
export let allowDeleteRows = true
|
||||||
|
|
||||||
// Sheet constants
|
// Sheet constants
|
||||||
const gutterWidth = 72
|
const gutterWidth = 72
|
||||||
|
@ -39,11 +41,11 @@
|
||||||
const tableIdStore = writable(tableId)
|
const tableIdStore = writable(tableId)
|
||||||
const config = writable({
|
const config = writable({
|
||||||
allowAddRows,
|
allowAddRows,
|
||||||
allowSelectRows,
|
|
||||||
allowAddColumns,
|
allowAddColumns,
|
||||||
allowEditColumns,
|
allowEditColumns,
|
||||||
allowExpandRows,
|
allowExpandRows,
|
||||||
allowEditRows,
|
allowEditRows,
|
||||||
|
allowDeleteRows,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Build up spreadsheet context
|
// Build up spreadsheet context
|
||||||
|
@ -64,11 +66,11 @@
|
||||||
$: tableIdStore.set(tableId)
|
$: tableIdStore.set(tableId)
|
||||||
$: config.set({
|
$: config.set({
|
||||||
allowAddRows,
|
allowAddRows,
|
||||||
allowSelectRows,
|
|
||||||
allowAddColumns,
|
allowAddColumns,
|
||||||
allowEditColumns,
|
allowEditColumns,
|
||||||
allowExpandRows,
|
allowExpandRows,
|
||||||
allowEditRows,
|
allowEditRows,
|
||||||
|
allowDeleteRows,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Set context for children to consume
|
// Set context for children to consume
|
||||||
|
@ -86,13 +88,12 @@
|
||||||
id="sheet-{rand}"
|
id="sheet-{rand}"
|
||||||
class:is-resizing={$isResizing}
|
class:is-resizing={$isResizing}
|
||||||
class:is-reordering={$isReordering}
|
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">
|
||||||
<div class="controls-left">
|
<div class="controls-left">
|
||||||
<AddRowButton />
|
<AddRowButton />
|
||||||
<AddColumnButton />
|
<AddColumnButton />
|
||||||
<SheetControls />
|
|
||||||
<slot name="controls" />
|
<slot name="controls" />
|
||||||
<HideColumnsButton />
|
<HideColumnsButton />
|
||||||
<SortButton />
|
<SortButton />
|
||||||
|
|
|
@ -2,16 +2,12 @@
|
||||||
import { getContext, onMount } from "svelte"
|
import { getContext, onMount } from "svelte"
|
||||||
import SheetScrollWrapper from "./SheetScrollWrapper.svelte"
|
import SheetScrollWrapper from "./SheetScrollWrapper.svelte"
|
||||||
import SheetRow from "./SheetRow.svelte"
|
import SheetRow from "./SheetRow.svelte"
|
||||||
import { MaxCellRenderHeight } from "../lib/constants"
|
|
||||||
|
|
||||||
const { bounds, renderedRows, visualRowCapacity, rowHeight } =
|
const { bounds, renderedRows, rowVerticalInversionIndex } =
|
||||||
getContext("sheet")
|
getContext("sheet")
|
||||||
|
|
||||||
let body
|
let body
|
||||||
|
|
||||||
$: inversionIdx =
|
|
||||||
$visualRowCapacity - Math.ceil(MaxCellRenderHeight / $rowHeight) - 2
|
|
||||||
|
|
||||||
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(() => {
|
||||||
|
@ -27,7 +23,7 @@
|
||||||
<div bind:this={body} class="sheet-body">
|
<div bind:this={body} class="sheet-body">
|
||||||
<SheetScrollWrapper scrollHorizontally scrollVertically wheelInteractive>
|
<SheetScrollWrapper scrollHorizontally scrollVertically wheelInteractive>
|
||||||
{#each $renderedRows as row, idx}
|
{#each $renderedRows as row, idx}
|
||||||
<SheetRow {row} {idx} invert={idx >= inversionIdx} />
|
<SheetRow {row} {idx} invertY={idx >= $rowVerticalInversionIndex} />
|
||||||
{/each}
|
{/each}
|
||||||
</SheetScrollWrapper>
|
</SheetScrollWrapper>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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>
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
export let row
|
export let row
|
||||||
export let idx
|
export let idx
|
||||||
export let invert = false
|
export let invertY = false
|
||||||
|
|
||||||
const {
|
const {
|
||||||
focusedCellId,
|
focusedCellId,
|
||||||
|
@ -16,6 +16,7 @@
|
||||||
selectedCellMap,
|
selectedCellMap,
|
||||||
focusedRow,
|
focusedRow,
|
||||||
hiddenColumnsWidth,
|
hiddenColumnsWidth,
|
||||||
|
columnHorizontalInversionIndex,
|
||||||
} = getContext("sheet")
|
} = getContext("sheet")
|
||||||
|
|
||||||
$: rowSelected = !!$selectedRows[row._id]
|
$: rowSelected = !!$selectedRows[row._id]
|
||||||
|
@ -28,18 +29,18 @@
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="row"
|
class="row"
|
||||||
style={rowFocused ? foo : null}
|
|
||||||
on:focus
|
on:focus
|
||||||
on:mouseenter={() => ($hoveredRowId = row._id)}
|
on:mouseenter={() => ($hoveredRowId = row._id)}
|
||||||
on:mouseleave={() => ($hoveredRowId = null)}
|
on:mouseleave={() => ($hoveredRowId = null)}
|
||||||
>
|
>
|
||||||
{#each cols as column (column.name)}
|
{#each $renderedColumns as column, idx (column.name)}
|
||||||
{@const cellId = `${row._id}-${column.name}`}
|
{@const cellId = `${row._id}-${column.name}`}
|
||||||
<DataCell
|
<DataCell
|
||||||
{cellId}
|
{cellId}
|
||||||
{column}
|
{column}
|
||||||
{row}
|
{row}
|
||||||
{invert}
|
{invertY}
|
||||||
|
invertX={idx >= $columnHorizontalInversionIndex}
|
||||||
{rowFocused}
|
{rowFocused}
|
||||||
highlighted={rowHovered || rowFocused || reorderSource === column.name}
|
highlighted={rowHovered || rowFocused || reorderSource === column.name}
|
||||||
selected={rowSelected}
|
selected={rowSelected}
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
on:wheel={wheelInteractive ? handleWheel : null}
|
on:wheel={wheelInteractive ? handleWheel : null}
|
||||||
on:click|self={() => ($focusedCellId = null)}
|
on:click|self={() => ($focusedCellId = null)}
|
||||||
>
|
>
|
||||||
<div {style}>
|
<div {style} class="inner">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -79,5 +79,13 @@
|
||||||
.outer {
|
.outer {
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.inner {
|
||||||
|
position: absolute;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -61,8 +61,8 @@
|
||||||
<SheetCell width={gutterWidth}>
|
<SheetCell width={gutterWidth}>
|
||||||
<div class="gutter">
|
<div class="gutter">
|
||||||
<div class="checkbox visible">
|
<div class="checkbox visible">
|
||||||
{#if $config.allowSelectRows}
|
{#if $config.allowDeleteRows}
|
||||||
<div on:click={$config.allowSelectRows && selectAll}>
|
<div on:click={selectAll}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
value={rowCount && selectedRowCount === rowCount}
|
value={rowCount && selectedRowCount === rowCount}
|
||||||
disabled={!$renderedRows.length}
|
disabled={!$renderedRows.length}
|
||||||
|
@ -104,14 +104,14 @@
|
||||||
<div
|
<div
|
||||||
on:click={() => selectRow(row._id)}
|
on:click={() => selectRow(row._id)}
|
||||||
class="checkbox"
|
class="checkbox"
|
||||||
class:visible={$config.allowSelectRows &&
|
class:visible={$config.allowDeleteRows &&
|
||||||
(rowSelected || rowHovered || rowFocused)}
|
(rowSelected || rowHovered || rowFocused)}
|
||||||
>
|
>
|
||||||
<Checkbox value={rowSelected} />
|
<Checkbox value={rowSelected} />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="number"
|
class="number"
|
||||||
class:visible={!$config.allowSelectRows ||
|
class:visible={!$config.allowDeleteRows ||
|
||||||
!(rowSelected || rowHovered || rowFocused)}
|
!(rowSelected || rowHovered || rowFocused)}
|
||||||
>
|
>
|
||||||
{row.__idx + 1}
|
{row.__idx + 1}
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
|
export const SheetPadding = 264
|
||||||
export const MaxCellRenderHeight = 216
|
export const MaxCellRenderHeight = 216
|
||||||
|
export const MaxCellRenderWidthOverflow = 200
|
||||||
|
export const ScrollBarSize = 8
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon="Delete"
|
icon="Delete"
|
||||||
disabled={!$config.allowEditRows}
|
disabled={!$config.allowDeleteRows}
|
||||||
on:click={deleteRow}>Delete row</MenuItem
|
on:click={deleteRow}>Delete row</MenuItem
|
||||||
>
|
>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { domDebounce } from "../../../utils/utils"
|
import { domDebounce } from "../../../utils/utils"
|
||||||
|
import { ScrollBarSize } from "../lib/constants"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
scroll,
|
scroll,
|
||||||
|
@ -17,9 +18,6 @@
|
||||||
height,
|
height,
|
||||||
} = getContext("sheet")
|
} = getContext("sheet")
|
||||||
|
|
||||||
// Bar config
|
|
||||||
const barOffset = 8
|
|
||||||
|
|
||||||
// State for dragging bars
|
// State for dragging bars
|
||||||
let initialMouse
|
let initialMouse
|
||||||
let initialScroll
|
let initialScroll
|
||||||
|
@ -28,17 +26,17 @@
|
||||||
// Terminology is the same for both axes:
|
// Terminology is the same for both axes:
|
||||||
// renderX - the space available to render the bar in, edge to edge
|
// renderX - the space available to render the bar in, edge to edge
|
||||||
// availX - the space available to render the bar in, until the 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)
|
$: barHeight = Math.max(50, ($height / $contentHeight) * renderHeight)
|
||||||
$: availHeight = renderHeight - barHeight
|
$: availHeight = renderHeight - barHeight
|
||||||
$: barTop =
|
$: barTop =
|
||||||
barOffset + $rowHeight + availHeight * ($scrollTop / $maxScrollTop)
|
ScrollBarSize + $rowHeight + availHeight * ($scrollTop / $maxScrollTop)
|
||||||
|
|
||||||
// Calculate H scrollbar size and offset
|
// Calculate H scrollbar size and offset
|
||||||
$: renderWidth = $screenWidth - 2 * barOffset
|
$: renderWidth = $screenWidth - 2 * ScrollBarSize
|
||||||
$: barWidth = Math.max(50, ($screenWidth / $contentWidth) * renderWidth)
|
$: barWidth = Math.max(50, ($screenWidth / $contentWidth) * renderWidth)
|
||||||
$: availWidth = renderWidth - barWidth
|
$: availWidth = renderWidth - barWidth
|
||||||
$: barLeft = barOffset + availWidth * ($scrollLeft / $maxScrollLeft)
|
$: barLeft = ScrollBarSize + availWidth * ($scrollLeft / $maxScrollLeft)
|
||||||
|
|
||||||
// V scrollbar drag handlers
|
// V scrollbar drag handlers
|
||||||
const startVDragging = e => {
|
const startVDragging = e => {
|
||||||
|
@ -88,14 +86,14 @@
|
||||||
{#if $showVScrollbar}
|
{#if $showVScrollbar}
|
||||||
<div
|
<div
|
||||||
class="v-scrollbar"
|
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}
|
on:mousedown={startVDragging}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $showHScrollbar}
|
{#if $showHScrollbar}
|
||||||
<div
|
<div
|
||||||
class="h-scrollbar"
|
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}
|
on:mousedown={startHDragging}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -112,9 +110,11 @@
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
.v-scrollbar {
|
.v-scrollbar {
|
||||||
width: 8px;
|
width: var(--size);
|
||||||
|
right: var(--size);
|
||||||
}
|
}
|
||||||
.h-scrollbar {
|
.h-scrollbar {
|
||||||
height: 8px;
|
height: var(--size);
|
||||||
|
bottom: var(--size);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { derived, get, writable } from "svelte/store"
|
import { derived, get, writable } from "svelte/store"
|
||||||
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
|
||||||
export const DefaultColumnWidth = 200
|
export const DefaultColumnWidth = 200
|
||||||
|
|
||||||
|
@ -16,7 +17,6 @@ export const createStores = () => {
|
||||||
const enriched = {
|
const enriched = {
|
||||||
...column,
|
...column,
|
||||||
left: offset,
|
left: offset,
|
||||||
order: idx,
|
|
||||||
}
|
}
|
||||||
if (column.visible) {
|
if (column.visible) {
|
||||||
offset += column.width
|
offset += column.width
|
||||||
|
@ -47,7 +47,7 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveStores = context => {
|
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
|
// Merge new schema fields with existing schema in order to preserve widths
|
||||||
table.subscribe($table => {
|
table.subscribe($table => {
|
||||||
|
@ -77,21 +77,13 @@ export const deriveStores = context => {
|
||||||
// Update columns, removing extraneous columns and adding missing ones
|
// Update columns, removing extraneous columns and adding missing ones
|
||||||
columns.set(
|
columns.set(
|
||||||
fields
|
fields
|
||||||
.map(field => {
|
.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,
|
name: field,
|
||||||
width: existing?.width || schema[field].width || DefaultColumnWidth,
|
width: schema[field].width || DefaultColumnWidth,
|
||||||
schema: schema[field],
|
schema: schema[field],
|
||||||
visible: existing?.visible ?? true,
|
visible: schema[field].visible ?? true,
|
||||||
order: schema[field].order,
|
order: schema[field].order,
|
||||||
}
|
}))
|
||||||
})
|
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
// Sort by order first
|
// Sort by order first
|
||||||
const orderA = a.order
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,19 +24,28 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveStores = context => {
|
export const deriveStores = context => {
|
||||||
const { reorder, columns, scroll, bounds, stickyColumn, ui, table, API } =
|
const {
|
||||||
context
|
reorder,
|
||||||
|
columns,
|
||||||
|
visibleColumns,
|
||||||
|
scroll,
|
||||||
|
bounds,
|
||||||
|
stickyColumn,
|
||||||
|
ui,
|
||||||
|
table,
|
||||||
|
API,
|
||||||
|
} = context
|
||||||
|
|
||||||
// Callback when dragging on a colum header and starting reordering
|
// Callback when dragging on a colum header and starting reordering
|
||||||
const startReordering = (column, e) => {
|
const startReordering = (column, e) => {
|
||||||
const $columns = get(columns)
|
const $visibleColumns = get(visibleColumns)
|
||||||
const $bounds = get(bounds)
|
const $bounds = get(bounds)
|
||||||
const $scroll = get(scroll)
|
const $scroll = get(scroll)
|
||||||
const $stickyColumn = get(stickyColumn)
|
const $stickyColumn = get(stickyColumn)
|
||||||
ui.actions.blur()
|
ui.actions.blur()
|
||||||
|
|
||||||
// Generate new breakpoints for the current columns
|
// Generate new breakpoints for the current columns
|
||||||
let breakpoints = $columns.map(col => ({
|
let breakpoints = $visibleColumns.map(col => ({
|
||||||
x: col.left + col.width,
|
x: col.left + col.width,
|
||||||
column: col.name,
|
column: col.name,
|
||||||
}))
|
}))
|
||||||
|
@ -93,6 +102,22 @@ export const deriveStores = context => {
|
||||||
const stopReordering = async () => {
|
const stopReordering = async () => {
|
||||||
// Swap position of columns
|
// Swap position of columns
|
||||||
let { sourceColumn, targetColumn } = get(reorder)
|
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 $columns = get(columns)
|
||||||
let sourceIdx = $columns.findIndex(x => x.name === sourceColumn)
|
let sourceIdx = $columns.findIndex(x => x.name === sourceColumn)
|
||||||
let targetIdx = $columns.findIndex(x => x.name === targetColumn)
|
let targetIdx = $columns.findIndex(x => x.name === targetColumn)
|
||||||
|
@ -105,61 +130,31 @@ export const deriveStores = context => {
|
||||||
state.splice(targetIdx, 0, removed[0])
|
state.splice(targetIdx, 0, removed[0])
|
||||||
return state.slice()
|
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 moveColumnLeft = async column => {
|
||||||
const $columns = get(columns)
|
const $visibleColumns = get(visibleColumns)
|
||||||
const sourceIdx = $columns.findIndex(x => x.name === column)
|
const sourceIdx = $visibleColumns.findIndex(x => x.name === column)
|
||||||
if (sourceIdx === 0) {
|
moveColumn(column, $visibleColumns[sourceIdx - 2]?.name)
|
||||||
return
|
|
||||||
}
|
|
||||||
columns.update(state => {
|
|
||||||
let tmp = state[sourceIdx]
|
|
||||||
state[sourceIdx] = state[sourceIdx - 1]
|
|
||||||
state[sourceIdx - 1] = tmp
|
|
||||||
return state.slice()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Persist changes
|
|
||||||
await saveOrderChanges()
|
await saveOrderChanges()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Moves a column one place right (as appears visually)
|
||||||
const moveColumnRight = async column => {
|
const moveColumnRight = async column => {
|
||||||
const $columns = get(columns)
|
const $visibleColumns = get(visibleColumns)
|
||||||
const sourceIdx = $columns.findIndex(x => x.name === column)
|
const sourceIdx = $visibleColumns.findIndex(x => x.name === column)
|
||||||
if (sourceIdx === $columns.length - 1) {
|
if (sourceIdx === $visibleColumns.length - 1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
columns.update(state => {
|
moveColumn(column, $visibleColumns[sourceIdx + 1]?.name)
|
||||||
let tmp = state[sourceIdx]
|
|
||||||
state[sourceIdx] = state[sourceIdx + 1]
|
|
||||||
state[sourceIdx + 1] = tmp
|
|
||||||
return state.slice()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Persist changes
|
|
||||||
await saveOrderChanges()
|
await saveOrderChanges()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saves order changes as part of table metadata
|
// Saves order changes as part of table metadata
|
||||||
const saveOrderChanges = async () => {
|
const saveOrderChanges = async () => {
|
||||||
const $table = cloneDeep(get(table))
|
const newOrder = get(columns).map(column => column.name)
|
||||||
const $columns = get(columns)
|
await columns.actions.updateColumnOrders(newOrder)
|
||||||
$columns.forEach(column => {
|
|
||||||
$table.schema[column.name].order = column.order
|
|
||||||
})
|
|
||||||
const newTable = await API.saveTable($table)
|
|
||||||
table.set(newTable)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -23,7 +23,7 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveStores = context => {
|
export const deriveStores = context => {
|
||||||
const { resize, columns, stickyColumn, ui, table, API, rows } = context
|
const { resize, columns, stickyColumn, ui } = context
|
||||||
|
|
||||||
// Starts resizing a certain column
|
// Starts resizing a certain column
|
||||||
const startResizing = (column, e) => {
|
const startResizing = (column, e) => {
|
||||||
|
@ -93,41 +93,25 @@ export const deriveStores = context => {
|
||||||
|
|
||||||
// Persist width if it changed
|
// Persist width if it changed
|
||||||
if ($resize.width !== $resize.initialWidth) {
|
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
|
// Resets a column size back to default
|
||||||
const resetSize = async column => {
|
const resetSize = async column => {
|
||||||
let columnIdx = get(columns).findIndex(col => col.name === column.name)
|
// let columnIdx = get(columns).findIndex(col => col.name === column.name)
|
||||||
if (columnIdx === -1) {
|
// if (columnIdx === -1) {
|
||||||
stickyColumn.update(state => ({
|
// stickyColumn.update(state => ({
|
||||||
...state,
|
// ...state,
|
||||||
width: DefaultColumnWidth,
|
// width: DefaultColumnWidth,
|
||||||
}))
|
// }))
|
||||||
} else {
|
// } else {
|
||||||
columns.update(state => {
|
// columns.update(state => {
|
||||||
state[columnIdx].width = DefaultColumnWidth
|
// state[columnIdx].width = DefaultColumnWidth
|
||||||
return [...state]
|
// return [...state]
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
await saveNewColumnWidth(column.name, DefaultColumnWidth)
|
await columns.actions.updateColumnWidth(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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -121,6 +121,8 @@ export const deriveStores = context => {
|
||||||
instanceLoaded.set(true)
|
instanceLoaded.set(true)
|
||||||
scroll.set({ top: 0, left: 0 })
|
scroll.set({ top: 0, left: 0 })
|
||||||
} else if (resetRows) {
|
} else if (resetRows) {
|
||||||
|
table.set($fetch.definition)
|
||||||
|
|
||||||
// Only reset top scroll position when resetting rows
|
// Only reset top scroll position when resetting rows
|
||||||
scroll.update(state => ({ ...state, top: 0 }))
|
scroll.update(state => ({ ...state, top: 0 }))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { writable, derived, get } from "svelte/store"
|
import { writable, derived, get } from "svelte/store"
|
||||||
import { tick } from "svelte"
|
import { tick } from "svelte"
|
||||||
import { DefaultColumnWidth } from "./columns"
|
import { SheetPadding } from "../lib/constants"
|
||||||
|
|
||||||
export const createStores = () => {
|
export const createStores = () => {
|
||||||
const scroll = writable({
|
const scroll = writable({
|
||||||
|
@ -35,7 +35,6 @@ export const deriveStores = context => {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
} = context
|
} = context
|
||||||
const padding = 264
|
|
||||||
|
|
||||||
// Memoize store primitives
|
// Memoize store primitives
|
||||||
const stickyColumnWidth = derived(stickyColumn, $col => $col?.width || 0, 0)
|
const stickyColumnWidth = derived(stickyColumn, $col => $col?.width || 0, 0)
|
||||||
|
@ -43,7 +42,7 @@ export const deriveStores = context => {
|
||||||
// Derive vertical limits
|
// Derive vertical limits
|
||||||
const contentHeight = derived(
|
const contentHeight = derived(
|
||||||
[rows, rowHeight],
|
[rows, rowHeight],
|
||||||
([$rows, $rowHeight]) => $rows.length * $rowHeight + padding,
|
([$rows, $rowHeight]) => $rows.length * $rowHeight + SheetPadding,
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
const maxScrollTop = derived(
|
const maxScrollTop = derived(
|
||||||
|
@ -56,7 +55,7 @@ export const deriveStores = context => {
|
||||||
const contentWidth = derived(
|
const contentWidth = derived(
|
||||||
[visibleColumns, stickyColumnWidth],
|
[visibleColumns, stickyColumnWidth],
|
||||||
([$visibleColumns, $stickyColumnWidth]) => {
|
([$visibleColumns, $stickyColumnWidth]) => {
|
||||||
let width = gutterWidth + padding + $stickyColumnWidth
|
let width = gutterWidth + SheetPadding + $stickyColumnWidth
|
||||||
$visibleColumns.forEach(col => {
|
$visibleColumns.forEach(col => {
|
||||||
width += col.width
|
width += col.width
|
||||||
})
|
})
|
||||||
|
@ -146,7 +145,7 @@ export const deriveStores = context => {
|
||||||
const $visibleColumns = get(visibleColumns)
|
const $visibleColumns = get(visibleColumns)
|
||||||
const columnName = $focusedCellId?.split("-")[1]
|
const columnName = $focusedCellId?.split("-")[1]
|
||||||
const column = $visibleColumns.find(col => col.name === columnName)
|
const column = $visibleColumns.find(col => col.name === columnName)
|
||||||
const horizontalOffset = DefaultColumnWidth
|
const horizontalOffset = 50
|
||||||
if (!column) {
|
if (!column) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
import { derived, get } from "svelte/store"
|
import { derived, get } from "svelte/store"
|
||||||
|
import {
|
||||||
|
MaxCellRenderHeight,
|
||||||
|
MaxCellRenderWidthOverflow,
|
||||||
|
ScrollBarSize,
|
||||||
|
} from "../lib/constants"
|
||||||
|
|
||||||
export const deriveStores = context => {
|
export const deriveStores = context => {
|
||||||
const {
|
const {
|
||||||
|
@ -96,11 +101,41 @@ export const deriveStores = context => {
|
||||||
0
|
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 {
|
return {
|
||||||
scrolledRowCount,
|
scrolledRowCount,
|
||||||
visualRowCapacity,
|
visualRowCapacity,
|
||||||
renderedRows,
|
renderedRows,
|
||||||
renderedColumns,
|
renderedColumns,
|
||||||
hiddenColumnsWidth,
|
hiddenColumnsWidth,
|
||||||
|
rowVerticalInversionIndex,
|
||||||
|
columnHorizontalInversionIndex,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,8 @@ export interface FieldSchema {
|
||||||
timeOnly?: boolean
|
timeOnly?: boolean
|
||||||
lastID?: number
|
lastID?: number
|
||||||
useRichText?: boolean | null
|
useRichText?: boolean | null
|
||||||
|
order?: number
|
||||||
|
width?: number
|
||||||
meta?: {
|
meta?: {
|
||||||
toTable: string
|
toTable: string
|
||||||
toKey: string
|
toKey: string
|
||||||
|
|
Loading…
Reference in New Issue