Merge remote-tracking branch 'origin/master' into feature/signature-field-and-component

This commit is contained in:
Dean 2024-05-13 16:31:46 +01:00
commit 421f70635e
8 changed files with 147 additions and 85 deletions

View File

@ -49,17 +49,20 @@
}, },
] ]
$: tables = findAllMatchingComponents($selectedScreen?.props, component => $: components = findAllMatchingComponents(
component._component.endsWith("table")
)
$: tableBlocks = findAllMatchingComponents(
$selectedScreen?.props, $selectedScreen?.props,
component => component._component.endsWith("tableblock") component => {
const type = component._component
return (
type.endsWith("/table") ||
type.endsWith("/tableblock") ||
type.endsWith("/gridblock")
)
}
) )
$: components = tables.concat(tableBlocks)
$: componentOptions = components.map(table => ({ $: componentOptions = components.map(table => ({
label: table._instanceName, label: table._instanceName,
value: table._component.includes("tableblock") value: table._component.endsWith("/tableblock")
? `${table._id}-table` ? `${table._id}-table`
: table._id, : table._id,
})) }))
@ -69,6 +72,7 @@
$: selectedTable = components.find( $: selectedTable = components.find(
component => component._id === selectedTableId component => component._id === selectedTableId
) )
$: parameters.rows = `{{ literal [${parameters.tableComponentId}].[selectedRows] }}`
onMount(() => { onMount(() => {
if (!parameters.type) { if (!parameters.type) {

View File

@ -7066,10 +7066,22 @@
] ]
} }
], ],
"context": { "context": [
{
"type": "schema", "type": "schema",
"scope": "local" "scope": "local"
}, },
{
"type": "static",
"values": [
{
"label": "Selected rows",
"key": "selectedRows",
"type": "array"
}
]
}
],
"actions": ["RefreshDatasource"] "actions": ["RefreshDatasource"]
}, },
"bbreferencefield": { "bbreferencefield": {

View File

@ -1,8 +1,8 @@
<script> <script>
// NOTE: this is not a block - it's just named as such to avoid confusing users, // NOTE: this is not a block - it's just named as such to avoid confusing users,
// because it functions similarly to one // because it functions similarly to one
import { getContext } from "svelte" import { getContext, onMount } from "svelte"
import { get } from "svelte/store" import { get, derived, readable } from "svelte/store"
import { Grid } from "@budibase/frontend-core" import { Grid } from "@budibase/frontend-core"
// table is actually any datasource, but called table for legacy compatibility // table is actually any datasource, but called table for legacy compatibility
@ -19,7 +19,6 @@
export let columns = null export let columns = null
export let onRowClick = null export let onRowClick = null
export let buttons = null export let buttons = null
export let repeat = null
const context = getContext("context") const context = getContext("context")
const component = getContext("component") const component = getContext("component")
@ -36,19 +35,20 @@
} = getContext("sdk") } = getContext("sdk")
let grid let grid
let gridContext
$: currentTheme = $context?.device?.theme $: currentTheme = $context?.device?.theme
$: darkMode = !currentTheme?.includes("light") $: darkMode = !currentTheme?.includes("light")
$: columnWhitelist = parsedColumns $: parsedColumns = getParsedColumns(columns)
?.filter(col => col.active) $: columnWhitelist = parsedColumns.filter(x => x.active).map(x => x.field)
?.map(col => col.field)
$: schemaOverrides = getSchemaOverrides(parsedColumns) $: schemaOverrides = getSchemaOverrides(parsedColumns)
$: enrichedButtons = enrichButtons(buttons) $: enrichedButtons = enrichButtons(buttons)
$: parsedColumns = getParsedColumns(columns) $: selectedRows = deriveSelectedRows(gridContext)
$: data = { selectedRows: $selectedRows }
$: actions = [ $: actions = [
{ {
type: ActionTypes.RefreshDatasource, type: ActionTypes.RefreshDatasource,
callback: () => grid?.getContext()?.rows.actions.refreshData(), callback: () => gridContext?.rows.actions.refreshData(),
metadata: { dataSource: table }, metadata: { dataSource: table },
}, },
] ]
@ -70,12 +70,14 @@
// Parses columns to fix older formats // Parses columns to fix older formats
const getParsedColumns = columns => { const getParsedColumns = columns => {
if (!columns?.length) {
return []
}
// If the first element has an active key all elements should be in the new format // If the first element has an active key all elements should be in the new format
if (columns?.length && columns[0]?.active !== undefined) { if (columns[0].active !== undefined) {
return columns return columns
} }
return columns.map(column => ({
return columns?.map(column => ({
label: column.displayName || column.name, label: column.displayName || column.name,
field: column.name, field: column.name,
active: true, active: true,
@ -84,7 +86,7 @@
const getSchemaOverrides = columns => { const getSchemaOverrides = columns => {
let overrides = {} let overrides = {}
columns?.forEach(column => { columns.forEach(column => {
overrides[column.field] = { overrides[column.field] = {
displayName: column.label, displayName: column.label,
} }
@ -111,6 +113,23 @@
})) }))
} }
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 getSanitisedStyles = styles => { const getSanitisedStyles = styles => {
return { return {
...styles, ...styles,
@ -120,11 +139,14 @@
}, },
} }
} }
onMount(() => {
gridContext = grid.getContext()
})
</script> </script>
<div use:styleable={styles} class:in-builder={$builderStore.inBuilder}> <div use:styleable={styles} class:in-builder={$builderStore.inBuilder}>
<span style="--height:{height};"> <span style="--height:{height};">
<Provider {actions}>
<Grid <Grid
bind:this={grid} bind:this={grid}
datasource={table} datasource={table}
@ -138,23 +160,24 @@
{fixedRowHeight} {fixedRowHeight}
{columnWhitelist} {columnWhitelist}
{schemaOverrides} {schemaOverrides}
{repeat}
canAddRows={allowAddRows} canAddRows={allowAddRows}
canEditRows={allowEditRows} canEditRows={allowEditRows}
canDeleteRows={allowDeleteRows} canDeleteRows={allowDeleteRows}
canEditColumns={false} canEditColumns={false}
canExpandRows={false} canExpandRows={false}
canSaveSchema={false} canSaveSchema={false}
canSelectRows={true}
showControls={false} showControls={false}
notifySuccess={notificationStore.actions.success} notifySuccess={notificationStore.actions.success}
notifyError={notificationStore.actions.error} notifyError={notificationStore.actions.error}
buttons={enrichedButtons} buttons={enrichedButtons}
on:rowclick={e => onRowClick?.({ row: e.detail })} on:rowclick={e => onRowClick?.({ row: e.detail })}
/> />
</Provider>
</span> </span>
</div> </div>
<Provider {data} {actions} />
<style> <style>
div { div {
display: flex; display: flex;

View File

@ -15,7 +15,7 @@ const createRowSelectionStore = () => {
const componentId = Object.keys(selection).find( const componentId = Object.keys(selection).find(
componentId => componentId === tableComponentId componentId => componentId === tableComponentId
) )
return selection[componentId] || {} return selection[componentId]
} }
return { return {

View File

@ -333,31 +333,59 @@ const s3UploadHandler = async action => {
} }
} }
/**
* For new configs, "rows" is defined and enriched to be the array of rows to
* export. For old configs it will be undefined and we need to use the legacy
* row selection store in combination with the tableComponentId parameter.
*/
const exportDataHandler = async action => { const exportDataHandler = async action => {
let selection = rowSelectionStore.actions.getSelection( let { tableComponentId, rows, type, columns, delimiter, customHeaders } =
action.parameters.tableComponentId action.parameters
let tableId
// Handle legacy configs using the row selection store
if (!rows?.length) {
const selection = rowSelectionStore.actions.getSelection(tableComponentId)
if (selection?.selectedRows?.length) {
rows = selection.selectedRows
tableId = selection.tableId
}
}
// Get table ID from first row if needed
if (!tableId) {
tableId = rows?.[0]?.tableId
}
// Handle no rows selected
if (!rows?.length) {
notificationStore.actions.error("Please select at least one row")
}
// Handle case where we're not using a DS+
else if (!tableId) {
notificationStore.actions.error(
"You can only export data from table datasources"
) )
if (selection.selectedRows && selection.selectedRows.length > 0) { }
// Happy path when we have both rows and table ID
else {
try { try {
// Flatten rows if required
if (typeof rows[0] !== "string") {
rows = rows.map(row => row._id)
}
const data = await API.exportRows({ const data = await API.exportRows({
tableId: selection.tableId, tableId,
rows: selection.selectedRows, rows,
format: action.parameters.type, format: type,
columns: action.parameters.columns?.map( columns: columns?.map(column => column.name || column),
column => column.name || column delimiter,
), customHeaders,
delimiter: action.parameters.delimiter,
customHeaders: action.parameters.customHeaders,
}) })
download( download(new Blob([data], { type: "text/plain" }), `${tableId}.${type}`)
new Blob([data], { type: "text/plain" }),
`${selection.tableId}.${action.parameters.type}`
)
} catch (error) { } catch (error) {
notificationStore.actions.error("There was an error exporting the data") notificationStore.actions.error("There was an error exporting the data")
} }
} else {
notificationStore.actions.error("Please select at least one row")
} }
} }

View File

@ -16,6 +16,8 @@
const { config, dispatch, selectedRows } = getContext("grid") const { config, dispatch, selectedRows } = getContext("grid")
const svelteDispatch = createEventDispatcher() const svelteDispatch = createEventDispatcher()
$: selectionEnabled = $config.canSelectRows || $config.canDeleteRows
const select = e => { const select = e => {
e.stopPropagation() e.stopPropagation()
svelteDispatch("select") svelteDispatch("select")
@ -52,7 +54,7 @@
<div <div
on:click={select} on:click={select}
class="checkbox" class="checkbox"
class:visible={$config.canDeleteRows && class:visible={selectionEnabled &&
(disableNumber || rowSelected || rowHovered || rowFocused)} (disableNumber || rowSelected || rowHovered || rowFocused)}
> >
<Checkbox value={rowSelected} {disabled} /> <Checkbox value={rowSelected} {disabled} />
@ -60,7 +62,7 @@
{#if !disableNumber} {#if !disableNumber}
<div <div
class="number" class="number"
class:visible={!$config.canDeleteRows || class:visible={!selectionEnabled ||
!(rowSelected || rowHovered || rowFocused)} !(rowSelected || rowHovered || rowFocused)}
> >
{row.__idx + 1} {row.__idx + 1}
@ -117,19 +119,11 @@
.expand { .expand {
margin-right: 4px; margin-right: 4px;
} }
.expand { .expand:not(.visible),
.expand:not(.visible) :global(*) {
opacity: 0; opacity: 0;
pointer-events: none !important;
} }
.expand :global(.spectrum-Icon) {
pointer-events: none;
}
.expand.visible {
opacity: 1;
}
.expand.visible :global(.spectrum-Icon) {
pointer-events: all;
}
.delete:hover { .delete:hover {
cursor: pointer; cursor: pointer;
} }

View File

@ -41,6 +41,7 @@
export let canDeleteRows = true export let canDeleteRows = true
export let canEditColumns = true export let canEditColumns = true
export let canSaveSchema = true export let canSaveSchema = true
export let canSelectRows = false
export let stripeRows = false export let stripeRows = false
export let quiet = false export let quiet = false
export let collaboration = true export let collaboration = true
@ -95,6 +96,7 @@
canDeleteRows, canDeleteRows,
canEditColumns, canEditColumns,
canSaveSchema, canSaveSchema,
canSelectRows,
stripeRows, stripeRows,
quiet, quiet,
collaboration, collaboration,

View File

@ -110,12 +110,11 @@ export const deriveStores = context => {
} }
export const createActions = context => { export const createActions = context => {
const { focusedCellId, selectedRows, hoveredRowId } = context const { focusedCellId, hoveredRowId } = context
// Callback when leaving the grid, deselecting all focussed or selected items // Callback when leaving the grid, deselecting all focussed or selected items
const blur = () => { const blur = () => {
focusedCellId.set(null) focusedCellId.set(null)
selectedRows.set({})
hoveredRowId.set(null) hoveredRowId.set(null)
} }