budibase/packages/client/src/components/app/GridBlock.svelte

195 lines
5.1 KiB
Svelte

<script>
// NOTE: this is not a block - it's just named as such to avoid confusing users,
// because it functions similarly to one
import { getContext, onMount } from "svelte"
import { get, derived, readable } from "svelte/store"
import { Grid } from "@budibase/frontend-core"
// table is actually any datasource, but called table for legacy compatibility
export let table
export let allowAddRows = true
export let allowEditRows = true
export let allowDeleteRows = true
export let stripeRows = false
export let quiet = false
export let initialFilter = null
export let initialSortColumn = null
export let initialSortOrder = null
export let fixedRowHeight = null
export let columns = null
export let onRowClick = null
export let buttons = null
const context = getContext("context")
const component = getContext("component")
const { environmentStore } = getContext("sdk")
const {
styleable,
API,
builderStore,
notificationStore,
enrichButtonActions,
ActionTypes,
createContextStore,
Provider,
generateGoldenSample,
} = getContext("sdk")
let grid
let gridContext
let minHeight
$: parsedColumns = getParsedColumns(columns)
$: columnWhitelist = parsedColumns.filter(x => x.active).map(x => x.field)
$: schemaOverrides = getSchemaOverrides(parsedColumns)
$: enrichedButtons = enrichButtons(buttons)
$: selectedRows = deriveSelectedRows(gridContext)
$: styles = patchStyles($component.styles, minHeight)
$: data = { selectedRows: $selectedRows }
$: actions = [
{
type: ActionTypes.RefreshDatasource,
callback: () => gridContext?.rows.actions.refreshData(),
metadata: { dataSource: table },
},
]
// Provide additional data context for live binding eval
export const getAdditionalDataContext = () => {
const rows = get(grid?.getContext()?.rows)
const goldenRow = generateGoldenSample(rows)
const id = get(component).id
return {
[id]: goldenRow,
eventContext: {
row: goldenRow,
},
}
}
// Parses columns to fix older formats
const getParsedColumns = columns => {
if (!columns?.length) {
return []
}
// If the first element has an active key all elements should be in the new format
if (columns[0].active !== undefined) {
return columns
}
return columns.map(column => ({
label: column.displayName || column.name,
field: column.name,
active: true,
}))
}
const getSchemaOverrides = columns => {
let overrides = {}
columns.forEach((column, idx) => {
overrides[column.field] = {
displayName: column.label,
width: column.width,
order: idx,
}
})
return overrides
}
const enrichButtons = buttons => {
if (!buttons?.length) {
return null
}
return buttons.map(settings => ({
size: "M",
text: settings.text,
type: settings.type,
onClick: async row => {
// Create a fake, ephemeral context to run the buttons actions with
const id = get(component).id
const gridContext = createContextStore(context)
gridContext.actions.provideData(id, row)
const fn = enrichButtonActions(settings.onClick, get(gridContext))
return await fn?.({ row })
},
}))
}
const deriveSelectedRows = gridContext => {
if (!gridContext) {
return readable([])
}
return derived(
[gridContext.selectedRows, gridContext.rowLookupMap, gridContext.rows],
([$selectedRows, $rowLookupMap, $rows]) => {
return Object.entries($selectedRows || {})
.filter(([_, selected]) => selected)
.map(([rowId]) => {
const idx = $rowLookupMap[rowId]
return gridContext.rows.actions.cleanRow($rows[idx])
})
}
)
}
const patchStyles = (styles, minHeight) => {
return {
...styles,
normal: {
...styles?.normal,
"min-height": `${minHeight}px`,
},
}
}
onMount(() => {
gridContext = grid.getContext()
gridContext.minHeight.subscribe($height => (minHeight = $height))
})
</script>
<div use:styleable={styles} class:in-builder={$builderStore.inBuilder}>
<Grid
bind:this={grid}
datasource={table}
{API}
{stripeRows}
{quiet}
{initialFilter}
{initialSortColumn}
{initialSortOrder}
{fixedRowHeight}
{columnWhitelist}
{schemaOverrides}
canAddRows={allowAddRows}
canEditRows={allowEditRows}
canDeleteRows={allowDeleteRows}
canEditColumns={false}
canExpandRows={false}
canSaveSchema={false}
canSelectRows={true}
showControls={false}
notifySuccess={notificationStore.actions.success}
notifyError={notificationStore.actions.error}
buttons={enrichedButtons}
isCloud={$environmentStore.cloud}
on:rowclick={e => onRowClick?.({ row: e.detail })}
/>
</div>
<Provider {data} {actions} />
<style>
div {
display: flex;
flex-direction: column;
align-items: stretch;
border: 1px solid var(--spectrum-global-color-gray-300);
border-radius: 4px;
overflow: hidden;
height: 410px;
}
div.in-builder :global(*) {
pointer-events: none;
}
</style>