Merge branch 'master' into fix-section-crash

This commit is contained in:
Martin McKeaveney 2023-11-20 09:41:11 +00:00 committed by GitHub
commit 536f7dfede
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 307 additions and 27 deletions

View File

@ -21,6 +21,7 @@
export let allowHelpers = true
export let updateOnChange = true
export let drawerLeft
export let disableBindings = false
const dispatch = createEventDispatcher()
let bindingDrawer
@ -62,7 +63,7 @@
{placeholder}
{updateOnChange}
/>
{#if !disabled}
{#if !disabled && !disableBindings}
<div
class="icon"
on:click={() => {

View File

@ -21,7 +21,8 @@
$: schemaComponents = getContextProviderComponents(
$currentAsset,
$store.selectedComponentId,
"schema"
"schema",
{ includeSelf: nested }
)
$: providerOptions = getProviderOptions(formComponents, schemaComponents)
$: schemaFields = getSchemaFields(parameters?.tableId)

View File

@ -4,10 +4,15 @@
import { createEventDispatcher } from "svelte"
import { store } from "builderStore"
import { Helpers } from "@budibase/bbui"
import { getEventContextBindings } from "builderStore/dataBinding"
export let componentInstance
export let componentBindings
export let bindings
export let value
export let key
export let nested
export let max
const dispatch = createEventDispatcher()
@ -15,12 +20,18 @@
$: buttonList = sanitizeValue(value) || []
$: buttonCount = buttonList.length
$: eventContextBindings = getEventContextBindings({
componentInstance,
settingKey: key,
})
$: allBindings = [...bindings, ...eventContextBindings]
$: itemProps = {
componentBindings: componentBindings || [],
bindings,
bindings: allBindings,
removeButton,
canRemove: buttonCount > 1,
nested,
}
$: canAddButtons = max == null || buttonList.length < max
const sanitizeValue = val => {
return val?.map(button => {
@ -86,11 +97,16 @@
focus={focusItem}
draggable={buttonCount > 1}
/>
<div class="list-footer" on:click={addButton}>
<div class="add-button">Add button</div>
</div>
{/if}
<div
class="list-footer"
class:disabled={!canAddButtons}
on:click={addButton}
class:empty={!buttonCount}
>
<div class="add-button">Add button</div>
</div>
</div>
<style>
@ -120,15 +136,21 @@
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid));
cursor: pointer;
}
.add-button {
margin: var(--spacing-s);
.list-footer.empty {
border-radius: 4px;
}
.list-footer.disabled {
color: var(--spectrum-global-color-gray-500);
pointer-events: none;
}
.list-footer:hover {
background-color: var(
--spectrum-table-row-background-color-hover,
var(--spectrum-alias-highlight-hover)
);
}
.add-button {
margin: var(--spacing-s);
}
</style>

View File

@ -9,11 +9,33 @@
export let bindings
export let anchor
export let removeButton
export let canRemove
export let nested
$: readableText = isJSBinding(item.text)
? "(JavaScript function)"
: runtimeToReadableBinding([...bindings, componentBindings], item.text)
// If this is a nested setting (for example inside a grid or form block) then
// we need to mark all the settings of the actual buttons as nested too, to
// allow us to reference context provided by the block.
// We will need to update this in future if the normal button component
// gets broken into multiple settings sections, as we assume a flat array.
const updatedNestedFlags = settings => {
if (!nested || !settings?.length) {
return settings
}
let newSettings = settings.map(setting => ({
...setting,
nested: true,
}))
// We need to prevent bindings for the button names because of how grid
// blocks work. This is an edge case but unavoidable.
let name = newSettings.find(x => x.key === "text")
if (name) {
name.disableBindings = true
}
return newSettings
}
</script>
<div class="list-item-body">
@ -24,12 +46,12 @@
{componentBindings}
{bindings}
on:change
parseSettings={updatedNestedFlags}
/>
<div class="field-label">{readableText || "Button"}</div>
</div>
<div class="list-item-right">
<Icon
disabled={!canRemove}
size="S"
name="Close"
hoverable

View File

@ -18,6 +18,7 @@
export let value
const dispatch = createEventDispatcher()
let sanitisedFields
let fieldList
let schema

View File

@ -23,6 +23,7 @@
export let highlighted = false
export let propertyFocus = false
export let info = null
export let disableBindings = false
$: nullishValue = value == null || value === ""
$: allBindings = getAllBindings(bindings, componentBindings, nested)
@ -99,6 +100,7 @@
{nested}
{key}
{type}
{disableBindings}
{...props}
on:drawerHide
on:drawerShow

View File

@ -179,6 +179,7 @@
highlighted={$store.highlightedSettingKey === setting.key}
propertyFocus={$store.propertyFocus === setting.key}
info={setting.info}
disableBindings={setting.disableBindings}
props={{
// Generic settings
placeholder: setting.placeholder || null,

View File

@ -270,7 +270,6 @@
{
"type": "buttonConfiguration",
"key": "buttons",
"nested": true,
"defaultValue": [
{
"type": "cta",
@ -6339,8 +6338,29 @@
"label": "High contrast",
"key": "stripeRows",
"defaultValue": false
},
{
"section": true,
"name": "Buttons",
"settings": [
{
"type": "buttonConfiguration",
"key": "buttons",
"nested": true,
"max": 3,
"context": [
{
"label": "Clicked row",
"key": "row"
}
]
}
]
}
]
],
"context": {
"type": "schema"
}
},
"bbreferencefield": {
"devComment": "As bb reference is only used for user subtype for now, we are using user for icon and labels",

View File

@ -2,6 +2,7 @@
// NOTE: this is not a block - it's just named as such to avoid confusing users,
// because it functions similarly to one
import { getContext } from "svelte"
import { get } from "svelte/store"
import { Grid } from "@budibase/frontend-core"
// table is actually any datasource, but called table for legacy compatibility
@ -16,12 +17,21 @@
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 { styleable, API, builderStore, notificationStore } = getContext("sdk")
const {
styleable,
API,
builderStore,
notificationStore,
enrichButtonActions,
} = getContext("sdk")
$: columnWhitelist = columns?.map(col => col.name)
$: schemaOverrides = getSchemaOverrides(columns)
$: enrichedButtons = enrichButtons(buttons)
const getSchemaOverrides = columns => {
let overrides = {}
@ -33,6 +43,25 @@
})
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 => {
// We add a fake context binding in here, which allows us to pretend
// that the grid provides a "schema" binding - that lets us use the
// clicked row in things like save row actions
const enrichedContext = { ...get(context), [get(component).id]: row }
const fn = enrichButtonActions(settings.onClick, enrichedContext)
return await fn?.({ row })
},
}))
}
</script>
<div
@ -58,6 +87,7 @@
showControls={false}
notifySuccess={notificationStore.actions.success}
notifyError={notificationStore.actions.error}
buttons={enrichedButtons}
on:rowclick={e => onRowClick?.({ row: e.detail })}
/>
</div>

View File

@ -14,6 +14,7 @@ import {
dndIsDragging,
confirmationStore,
roleStore,
stateStore,
} from "stores"
import { styleable } from "utils/styleable"
import { linkable } from "utils/linkable"
@ -24,9 +25,13 @@ import BlockComponent from "components/BlockComponent.svelte"
import { ActionTypes } from "./constants"
import { fetchDatasourceSchema } from "./utils/schema.js"
import { getAPIKey } from "./utils/api.js"
import { enrichButtonActions } from "./utils/buttonActions.js"
import { processStringSync, makePropSafe } from "@budibase/string-templates"
export default {
API,
// Stores
authStore,
notificationStore,
routeStore,
@ -41,13 +46,23 @@ export default {
currentRole,
confirmationStore,
roleStore,
stateStore,
// Utils
styleable,
linkable,
getAction,
fetchDatasourceSchema,
Provider,
ActionTypes,
getAPIKey,
enrichButtonActions,
processStringSync,
makePropSafe,
// Components
Provider,
Block,
BlockComponent,
// Constants
ActionTypes,
}

View File

@ -15,7 +15,7 @@
$: style = getStyle(width, selectedUser)
const getStyle = (width, selectedUser) => {
let style = `flex: 0 0 ${width}px;`
let style = width === "auto" ? "width: auto;" : `flex: 0 0 ${width}px;`
if (selectedUser) {
style += `--user-color:${selectedUser.color};`
}

View File

@ -0,0 +1,144 @@
<script>
import { getContext, onMount } from "svelte"
import { Button } from "@budibase/bbui"
import GridCell from "../cells/GridCell.svelte"
import GridScrollWrapper from "./GridScrollWrapper.svelte"
const {
renderedRows,
hoveredRowId,
props,
width,
rows,
focusedRow,
selectedRows,
visibleColumns,
scroll,
isDragging,
buttonColumnWidth,
} = getContext("grid")
let measureContainer
$: buttons = $props.buttons?.slice(0, 3) || []
$: columnsWidth = $visibleColumns.reduce(
(total, col) => (total += col.width),
0
)
$: end = columnsWidth - 1 - $scroll.left
$: left = Math.min($width - $buttonColumnWidth, end)
const handleClick = async (button, row) => {
await button.onClick?.(rows.actions.cleanRow(row))
// Refresh the row in case it changed
await rows.actions.refreshRow(row._id)
}
onMount(() => {
const observer = new ResizeObserver(entries => {
const width = entries?.[0]?.contentRect?.width ?? 0
buttonColumnWidth.set(width)
})
observer.observe(measureContainer)
})
</script>
<!-- Hidden copy of buttons to measure -->
<div class="measure" bind:this={measureContainer}>
<GridCell width="auto">
<div class="buttons">
{#each buttons as button}
<Button size="S">
{button.text || "Button"}
</Button>
{/each}
</div>
</GridCell>
</div>
<div
class="button-column"
style="left:{left}px"
class:hidden={$buttonColumnWidth === 0}
>
<div class="content" on:mouseleave={() => ($hoveredRowId = null)}>
<GridScrollWrapper scrollVertically attachHandlers>
{#each $renderedRows as row}
{@const rowSelected = !!$selectedRows[row._id]}
{@const rowHovered = $hoveredRowId === row._id}
{@const rowFocused = $focusedRow?._id === row._id}
<div
class="row"
on:mouseenter={$isDragging ? null : () => ($hoveredRowId = row._id)}
on:mouseleave={$isDragging ? null : () => ($hoveredRowId = null)}
>
<GridCell
width="auto"
rowIdx={row.__idx}
selected={rowSelected}
highlighted={rowHovered || rowFocused}
>
<div class="buttons">
{#each buttons as button}
<Button
newStyles
size="S"
cta={button.type === "cta"}
primary={button.type === "primary"}
secondary={button.type === "secondary"}
warning={button.type === "warning"}
overBackground={button.type === "overBackground"}
on:click={() => handleClick(button, row)}
>
{button.text || "Button"}
</Button>
{/each}
</div>
</GridCell>
</div>
{/each}
</GridScrollWrapper>
</div>
</div>
<style>
.button-column {
display: flex;
flex-direction: column;
background: var(--cell-background);
position: absolute;
top: 0;
}
.button-column.hidden {
opacity: 0;
}
.content {
position: relative;
flex: 1 1 auto;
}
.row {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: stretch;
}
.buttons {
display: flex;
align-items: center;
padding: 0 var(--cell-padding);
gap: var(--cell-padding);
height: inherit;
}
/* Add left cell border */
.button-column :global(.cell) {
border-left: var(--cell-border);
}
/* Hidden copy of buttons to measure width against */
.measure {
position: absolute;
opacity: 0;
pointer-events: none;
}
</style>

View File

@ -48,6 +48,7 @@
export let fixedRowHeight = null
export let notifySuccess = null
export let notifyError = null
export let buttons = null
// Unique identifier for DOM nodes inside this instance
const rand = Math.random()
@ -99,6 +100,7 @@
fixedRowHeight,
notifySuccess,
notifyError,
buttons,
})
// Set context for children to consume

View File

@ -3,6 +3,7 @@
import GridScrollWrapper from "./GridScrollWrapper.svelte"
import GridRow from "./GridRow.svelte"
import { BlankRowID } from "../lib/constants"
import ButtonColumn from "./ButtonColumn.svelte"
const {
bounds,
@ -13,6 +14,7 @@
dispatch,
isDragging,
config,
props,
} = getContext("grid")
let body
@ -54,6 +56,9 @@
/>
{/if}
</GridScrollWrapper>
{#if $props.buttons?.length}
<ButtonColumn />
{/if}
</div>
<style>

View File

@ -314,8 +314,12 @@ export const createActions = context => {
// Refreshes a specific row
const refreshRow = async id => {
const row = await datasource.actions.getRow(id)
replaceRow(id, row)
try {
const row = await datasource.actions.getRow(id)
replaceRow(id, row)
} catch {
// Do nothing - we probably just don't support refreshing individual rows
}
}
// Refreshes all data

View File

@ -20,8 +20,15 @@ export const createStores = () => {
}
export const deriveStores = context => {
const { rows, visibleColumns, stickyColumn, rowHeight, width, height } =
context
const {
rows,
visibleColumns,
stickyColumn,
rowHeight,
width,
height,
buttonColumnWidth,
} = context
// Memoize store primitives
const stickyColumnWidth = derived(stickyColumn, $col => $col?.width || 0, 0)
@ -40,9 +47,10 @@ export const deriveStores = context => {
// Derive horizontal limits
const contentWidth = derived(
[visibleColumns, stickyColumnWidth],
([$visibleColumns, $stickyColumnWidth]) => {
let width = GutterWidth + Padding + $stickyColumnWidth
[visibleColumns, stickyColumnWidth, buttonColumnWidth],
([$visibleColumns, $stickyColumnWidth, $buttonColumnWidth]) => {
const space = Math.max(Padding, $buttonColumnWidth - 1)
let width = GutterWidth + space + $stickyColumnWidth
$visibleColumns.forEach(col => {
width += col.width
})

View File

@ -18,6 +18,7 @@ export const createStores = context => {
const previousFocusedRowId = writable(null)
const gridFocused = writable(false)
const isDragging = writable(false)
const buttonColumnWidth = writable(0)
// Derive the current focused row ID
const focusedRowId = derived(
@ -51,6 +52,7 @@ export const createStores = context => {
rowHeight,
gridFocused,
isDragging,
buttonColumnWidth,
selectedRows: {
...selectedRows,
actions: {