Merge branch 'master' into cli-improvements
This commit is contained in:
commit
d5a5744085
|
@ -8,6 +8,7 @@ import { derived, get } from "svelte/store"
|
||||||
import { findComponent, findComponentPath } from "./componentUtils"
|
import { findComponent, findComponentPath } from "./componentUtils"
|
||||||
import { RoleUtils } from "@budibase/frontend-core"
|
import { RoleUtils } from "@budibase/frontend-core"
|
||||||
import { createHistoryStore } from "builderStore/store/history"
|
import { createHistoryStore } from "builderStore/store/history"
|
||||||
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
|
||||||
export const store = getFrontendStore()
|
export const store = getFrontendStore()
|
||||||
export const automationStore = getAutomationStore()
|
export const automationStore = getAutomationStore()
|
||||||
|
@ -69,7 +70,14 @@ export const selectedComponent = derived(
|
||||||
if (!$selectedScreen || !$store.selectedComponentId) {
|
if (!$selectedScreen || !$store.selectedComponentId) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return findComponent($selectedScreen?.props, $store.selectedComponentId)
|
const selected = findComponent(
|
||||||
|
$selectedScreen?.props,
|
||||||
|
$store.selectedComponentId
|
||||||
|
)
|
||||||
|
|
||||||
|
const clone = selected ? cloneDeep(selected) : selected
|
||||||
|
store.actions.components.migrateSettings(clone)
|
||||||
|
return clone
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,7 @@ const INITIAL_FRONTEND_STATE = {
|
||||||
selectedScreenId: null,
|
selectedScreenId: null,
|
||||||
selectedComponentId: null,
|
selectedComponentId: null,
|
||||||
selectedLayoutId: null,
|
selectedLayoutId: null,
|
||||||
|
hoverComponentId: null,
|
||||||
|
|
||||||
// Client state
|
// Client state
|
||||||
selectedComponentInstance: null,
|
selectedComponentInstance: null,
|
||||||
|
@ -112,7 +113,7 @@ export const getFrontendStore = () => {
|
||||||
}
|
}
|
||||||
let clone = cloneDeep(screen)
|
let clone = cloneDeep(screen)
|
||||||
const result = patchFn(clone)
|
const result = patchFn(clone)
|
||||||
|
// An explicit false result means skip this change
|
||||||
if (result === false) {
|
if (result === false) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -601,6 +602,36 @@ export const getFrontendStore = () => {
|
||||||
// Finally try an external table
|
// Finally try an external table
|
||||||
return validTables.find(table => table.sourceType === DB_TYPE_EXTERNAL)
|
return validTables.find(table => table.sourceType === DB_TYPE_EXTERNAL)
|
||||||
},
|
},
|
||||||
|
migrateSettings: enrichedComponent => {
|
||||||
|
const componentPrefix = "@budibase/standard-components"
|
||||||
|
let migrated = false
|
||||||
|
|
||||||
|
if (enrichedComponent?._component == `${componentPrefix}/formblock`) {
|
||||||
|
// Use default config if the 'buttons' prop has never been initialised
|
||||||
|
if (!("buttons" in enrichedComponent)) {
|
||||||
|
enrichedComponent["buttons"] =
|
||||||
|
Utils.buildDynamicButtonConfig(enrichedComponent)
|
||||||
|
migrated = true
|
||||||
|
} else if (enrichedComponent["buttons"] == null) {
|
||||||
|
// Ignore legacy config if 'buttons' has been reset by 'resetOn'
|
||||||
|
const { _id, actionType, dataSource } = enrichedComponent
|
||||||
|
enrichedComponent["buttons"] = Utils.buildDynamicButtonConfig({
|
||||||
|
_id,
|
||||||
|
actionType,
|
||||||
|
dataSource,
|
||||||
|
})
|
||||||
|
migrated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure existing Formblocks position their buttons at the top.
|
||||||
|
if (!("buttonPosition" in enrichedComponent)) {
|
||||||
|
enrichedComponent["buttonPosition"] = "top"
|
||||||
|
migrated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return migrated
|
||||||
|
},
|
||||||
enrichEmptySettings: (component, opts) => {
|
enrichEmptySettings: (component, opts) => {
|
||||||
if (!component?._component) {
|
if (!component?._component) {
|
||||||
return
|
return
|
||||||
|
@ -672,7 +703,6 @@ export const getFrontendStore = () => {
|
||||||
component[setting.key] = setting.defaultValue
|
component[setting.key] = setting.defaultValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate non-empty settings
|
// Validate non-empty settings
|
||||||
else {
|
else {
|
||||||
if (setting.type === "dataProvider") {
|
if (setting.type === "dataProvider") {
|
||||||
|
@ -722,6 +752,9 @@ export const getFrontendStore = () => {
|
||||||
useDefaultValues: true,
|
useDefaultValues: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Migrate nested component settings
|
||||||
|
store.actions.components.migrateSettings(instance)
|
||||||
|
|
||||||
// Add any extra properties the component needs
|
// Add any extra properties the component needs
|
||||||
let extras = {}
|
let extras = {}
|
||||||
if (definition.hasChildren) {
|
if (definition.hasChildren) {
|
||||||
|
@ -845,7 +878,16 @@ export const getFrontendStore = () => {
|
||||||
if (!component) {
|
if (!component) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return patchFn(component, screen)
|
|
||||||
|
// Mutates the fetched component with updates
|
||||||
|
const patchResult = patchFn(component, screen)
|
||||||
|
|
||||||
|
// Mutates the component with any required settings updates
|
||||||
|
const migrated = store.actions.components.migrateSettings(component)
|
||||||
|
|
||||||
|
// Returning an explicit false signifies that we should skip this
|
||||||
|
// update. If we migrated something, ensure we never skip.
|
||||||
|
return migrated ? null : patchResult
|
||||||
}
|
}
|
||||||
await store.actions.screens.patch(patchScreen, screenId)
|
await store.actions.screens.patch(patchScreen, screenId)
|
||||||
},
|
},
|
||||||
|
@ -1247,9 +1289,13 @@ export const getFrontendStore = () => {
|
||||||
const settings = getComponentSettings(component._component)
|
const settings = getComponentSettings(component._component)
|
||||||
const updatedSetting = settings.find(setting => setting.key === name)
|
const updatedSetting = settings.find(setting => setting.key === name)
|
||||||
|
|
||||||
const resetFields = settings.filter(
|
// Can be a single string or array of strings
|
||||||
setting => name === setting.resetOn
|
const resetFields = settings.filter(setting => {
|
||||||
)
|
return (
|
||||||
|
name === setting.resetOn ||
|
||||||
|
(Array.isArray(setting.resetOn) && setting.resetOn.includes(name))
|
||||||
|
)
|
||||||
|
})
|
||||||
resetFields?.forEach(setting => {
|
resetFields?.forEach(setting => {
|
||||||
component[setting.key] = null
|
component[setting.key] = null
|
||||||
})
|
})
|
||||||
|
@ -1271,6 +1317,7 @@ export const getFrontendStore = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
component[name] = value
|
component[name] = value
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
requestEjectBlock: componentId => {
|
requestEjectBlock: componentId => {
|
||||||
|
@ -1278,7 +1325,6 @@ export const getFrontendStore = () => {
|
||||||
},
|
},
|
||||||
handleEjectBlock: async (componentId, ejectedDefinition) => {
|
handleEjectBlock: async (componentId, ejectedDefinition) => {
|
||||||
let nextSelectedComponentId
|
let nextSelectedComponentId
|
||||||
|
|
||||||
await store.actions.screens.patch(screen => {
|
await store.actions.screens.patch(screen => {
|
||||||
const block = findComponent(screen.props, componentId)
|
const block = findComponent(screen.props, componentId)
|
||||||
const parent = findComponentParent(screen.props, componentId)
|
const parent = findComponentParent(screen.props, componentId)
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
export let showTooltip = false
|
export let showTooltip = false
|
||||||
export let selectedBy = null
|
export let selectedBy = null
|
||||||
export let compact = false
|
export let compact = false
|
||||||
|
export let hovering = false
|
||||||
|
|
||||||
const scrollApi = getContext("scroll")
|
const scrollApi = getContext("scroll")
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -61,6 +62,7 @@
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="nav-item"
|
class="nav-item"
|
||||||
|
class:hovering
|
||||||
class:border
|
class:border
|
||||||
class:selected
|
class:selected
|
||||||
class:withActions
|
class:withActions
|
||||||
|
@ -71,6 +73,8 @@
|
||||||
on:dragstart
|
on:dragstart
|
||||||
on:dragover
|
on:dragover
|
||||||
on:drop
|
on:drop
|
||||||
|
on:mouseenter
|
||||||
|
on:mouseleave
|
||||||
on:click={onClick}
|
on:click={onClick}
|
||||||
ondragover="return false"
|
ondragover="return false"
|
||||||
ondragenter="return false"
|
ondragenter="return false"
|
||||||
|
@ -152,15 +156,17 @@
|
||||||
--avatars-background: var(--spectrum-global-color-gray-200);
|
--avatars-background: var(--spectrum-global-color-gray-200);
|
||||||
}
|
}
|
||||||
.nav-item.selected {
|
.nav-item.selected {
|
||||||
background-color: var(--spectrum-global-color-gray-300);
|
background-color: var(--spectrum-global-color-gray-300) !important;
|
||||||
--avatars-background: var(--spectrum-global-color-gray-300);
|
--avatars-background: var(--spectrum-global-color-gray-300);
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
.nav-item:hover {
|
.nav-item:hover,
|
||||||
background-color: var(--spectrum-global-color-gray-300);
|
.hovering {
|
||||||
|
background-color: var(--spectrum-global-color-gray-200);
|
||||||
--avatars-background: var(--spectrum-global-color-gray-300);
|
--avatars-background: var(--spectrum-global-color-gray-300);
|
||||||
}
|
}
|
||||||
.nav-item:hover .actions {
|
.nav-item:hover .actions,
|
||||||
|
.hovering .actions {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
.nav-item-content {
|
.nav-item-content {
|
||||||
|
|
|
@ -49,7 +49,15 @@
|
||||||
<div class="field-label">{item.label || item.field}</div>
|
<div class="field-label">{item.label || item.field}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-item-right">
|
<div class="list-item-right">
|
||||||
<Toggle on:change={onToggle(item)} text="" value={item.active} thin />
|
<Toggle
|
||||||
|
on:click={e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
on:change={onToggle(item)}
|
||||||
|
text=""
|
||||||
|
value={item.active}
|
||||||
|
thin
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -36,12 +36,14 @@
|
||||||
|
|
||||||
// Determine selected component ID
|
// Determine selected component ID
|
||||||
$: selectedComponentId = $store.selectedComponentId
|
$: selectedComponentId = $store.selectedComponentId
|
||||||
|
$: hoverComponentId = $store.hoverComponentId
|
||||||
|
|
||||||
$: previewData = {
|
$: previewData = {
|
||||||
appId: $store.appId,
|
appId: $store.appId,
|
||||||
layout,
|
layout,
|
||||||
screen,
|
screen,
|
||||||
selectedComponentId,
|
selectedComponentId,
|
||||||
|
hoverComponentId,
|
||||||
theme: $store.theme,
|
theme: $store.theme,
|
||||||
customTheme: $store.customTheme,
|
customTheme: $store.customTheme,
|
||||||
previewDevice: $store.previewDevice,
|
previewDevice: $store.previewDevice,
|
||||||
|
@ -117,6 +119,8 @@
|
||||||
error = event.error || "An unknown error occurred"
|
error = event.error || "An unknown error occurred"
|
||||||
} else if (type === "select-component" && data.id) {
|
} else if (type === "select-component" && data.id) {
|
||||||
$store.selectedComponentId = data.id
|
$store.selectedComponentId = data.id
|
||||||
|
} else if (type === "hover-component" && data.id) {
|
||||||
|
$store.hoverComponentId = data.id
|
||||||
} else if (type === "update-prop") {
|
} else if (type === "update-prop") {
|
||||||
await store.actions.components.updateSetting(data.prop, data.value)
|
await store.actions.components.updateSetting(data.prop, data.value)
|
||||||
} else if (type === "update-styles") {
|
} else if (type === "update-styles") {
|
||||||
|
|
|
@ -89,6 +89,17 @@
|
||||||
}
|
}
|
||||||
return findComponentPath($selectedComponent, component._id)?.length > 0
|
return findComponentPath($selectedComponent, component._id)?.length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleMouseover = componentId => {
|
||||||
|
if ($store.hoverComponentId !== componentId) {
|
||||||
|
$store.hoverComponentId = componentId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleMouseout = componentId => {
|
||||||
|
if ($store.hoverComponentId === componentId) {
|
||||||
|
$store.hoverComponentId = null
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -109,6 +120,9 @@
|
||||||
on:dragover={dragover(component, index)}
|
on:dragover={dragover(component, index)}
|
||||||
on:iconClick={() => toggleNodeOpen(component._id)}
|
on:iconClick={() => toggleNodeOpen(component._id)}
|
||||||
on:drop={onDrop}
|
on:drop={onDrop}
|
||||||
|
hovering={$store.hoverComponentId === component._id}
|
||||||
|
on:mouseenter={() => handleMouseover(component._id)}
|
||||||
|
on:mouseleave={() => handleMouseout(component._id)}
|
||||||
text={getComponentText(component)}
|
text={getComponentText(component)}
|
||||||
icon={getComponentIcon(component)}
|
icon={getComponentIcon(component)}
|
||||||
iconTooltip={getComponentName(component)}
|
iconTooltip={getComponentName(component)}
|
||||||
|
|
|
@ -32,6 +32,17 @@
|
||||||
const handleScroll = e => {
|
const handleScroll = e => {
|
||||||
scrolling = e.target.scrollTop !== 0
|
scrolling = e.target.scrollTop !== 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleMouseover = componentId => {
|
||||||
|
if ($store.hoverComponentId !== componentId) {
|
||||||
|
$store.hoverComponentId = componentId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleMouseout = componentId => {
|
||||||
|
if ($store.hoverComponentId === componentId) {
|
||||||
|
$store.hoverComponentId = null
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="components">
|
<div class="components">
|
||||||
|
@ -57,6 +68,12 @@
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
$store.selectedComponentId = `${$store.selectedScreenId}-screen`
|
$store.selectedComponentId = `${$store.selectedScreenId}-screen`
|
||||||
}}
|
}}
|
||||||
|
hovering={$store.hoverComponentId ===
|
||||||
|
`${$store.selectedScreenId}-screen`}
|
||||||
|
on:mouseenter={() =>
|
||||||
|
handleMouseover(`${$store.selectedScreenId}-screen`)}
|
||||||
|
on:mouseleave={() =>
|
||||||
|
handleMouseout(`${$store.selectedScreenId}-screen`)}
|
||||||
id={`component-screen`}
|
id={`component-screen`}
|
||||||
selectedBy={$userSelectedResourceMap[
|
selectedBy={$userSelectedResourceMap[
|
||||||
`${$store.selectedScreenId}-screen`
|
`${$store.selectedScreenId}-screen`
|
||||||
|
@ -78,6 +95,12 @@
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
$store.selectedComponentId = `${$store.selectedScreenId}-navigation`
|
$store.selectedComponentId = `${$store.selectedScreenId}-navigation`
|
||||||
}}
|
}}
|
||||||
|
hovering={$store.hoverComponentId ===
|
||||||
|
`${$store.selectedScreenId}-navigation`}
|
||||||
|
on:mouseenter={() =>
|
||||||
|
handleMouseover(`${$store.selectedScreenId}-navigation`)}
|
||||||
|
on:mouseleave={() =>
|
||||||
|
handleMouseout(`${$store.selectedScreenId}-navigation`)}
|
||||||
id={`component-nav`}
|
id={`component-nav`}
|
||||||
selectedBy={$userSelectedResourceMap[
|
selectedBy={$userSelectedResourceMap[
|
||||||
`${$store.selectedScreenId}-navigation`
|
`${$store.selectedScreenId}-navigation`
|
||||||
|
|
|
@ -6112,54 +6112,32 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"tag": "style",
|
||||||
|
"type": "select",
|
||||||
|
"label": "Button position",
|
||||||
|
"key": "buttonPosition",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Bottom",
|
||||||
|
"value": "bottom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Top",
|
||||||
|
"value": "top"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"defaultValue": "bottom"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"section": true,
|
"section": true,
|
||||||
"name": "Buttons",
|
"name": "Buttons",
|
||||||
"dependsOn": {
|
|
||||||
"setting": "actionType",
|
|
||||||
"value": "View",
|
|
||||||
"invert": true
|
|
||||||
},
|
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "buttonConfiguration",
|
||||||
"key": "saveButtonLabel",
|
"key": "buttons",
|
||||||
"label": "Save button",
|
|
||||||
"nested": true,
|
"nested": true,
|
||||||
"defaultValue": "Save"
|
"resetOn": ["actionType", "dataSource"]
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"key": "deleteButtonLabel",
|
|
||||||
"label": "Delete button",
|
|
||||||
"nested": true,
|
|
||||||
"defaultValue": "Delete",
|
|
||||||
"dependsOn": {
|
|
||||||
"setting": "actionType",
|
|
||||||
"value": "Update"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "url",
|
|
||||||
"label": "Navigate after button press",
|
|
||||||
"key": "actionUrl",
|
|
||||||
"placeholder": "Choose a screen",
|
|
||||||
"dependsOn": {
|
|
||||||
"setting": "actionType",
|
|
||||||
"value": "View",
|
|
||||||
"invert": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "boolean",
|
|
||||||
"label": "Hide notifications",
|
|
||||||
"key": "notificationOverride",
|
|
||||||
"defaultValue": false,
|
|
||||||
"dependsOn": {
|
|
||||||
"setting": "actionType",
|
|
||||||
"value": "View",
|
|
||||||
"invert": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import BlockComponent from "components/BlockComponent.svelte"
|
import BlockComponent from "components/BlockComponent.svelte"
|
||||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
import { enrichSearchColumns, enrichFilter } from "utils/blocks.js"
|
import { enrichSearchColumns, enrichFilter } from "utils/blocks.js"
|
||||||
|
import { Utils } from "@budibase/frontend-core"
|
||||||
|
|
||||||
export let title
|
export let title
|
||||||
export let dataSource
|
export let dataSource
|
||||||
|
@ -33,6 +34,7 @@
|
||||||
export let notificationOverride
|
export let notificationOverride
|
||||||
|
|
||||||
const { fetchDatasourceSchema, API } = getContext("sdk")
|
const { fetchDatasourceSchema, API } = getContext("sdk")
|
||||||
|
const component = getContext("component")
|
||||||
const stateKey = `ID_${generate()}`
|
const stateKey = `ID_${generate()}`
|
||||||
|
|
||||||
let formId
|
let formId
|
||||||
|
@ -259,16 +261,25 @@
|
||||||
name="Details form block"
|
name="Details form block"
|
||||||
type="formblock"
|
type="formblock"
|
||||||
bind:id={detailsFormBlockId}
|
bind:id={detailsFormBlockId}
|
||||||
|
context="form-edit"
|
||||||
props={{
|
props={{
|
||||||
dataSource,
|
dataSource,
|
||||||
saveButtonLabel: sidePanelSaveLabel || "Save", //always show
|
buttonPosition: "top",
|
||||||
deleteButtonLabel: deleteLabel,
|
buttons: Utils.buildDynamicButtonConfig({
|
||||||
|
_id: $component.id + "-form-edit",
|
||||||
|
showDeleteButton: deleteLabel !== "",
|
||||||
|
showSaveButton: true,
|
||||||
|
saveButtonLabel: sidePanelSaveLabel || "Save",
|
||||||
|
deleteButtonLabel: deleteLabel,
|
||||||
|
notificationOverride,
|
||||||
|
actionType: "Update",
|
||||||
|
dataSource,
|
||||||
|
}),
|
||||||
actionType: "Update",
|
actionType: "Update",
|
||||||
rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`,
|
rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`,
|
||||||
fields: sidePanelFields || normalFields,
|
fields: sidePanelFields || normalFields,
|
||||||
title: editTitle,
|
title: editTitle,
|
||||||
labelPosition: "left",
|
labelPosition: "left",
|
||||||
notificationOverride,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</BlockComponent>
|
</BlockComponent>
|
||||||
|
@ -284,16 +295,23 @@
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
name="New row form block"
|
name="New row form block"
|
||||||
type="formblock"
|
type="formblock"
|
||||||
|
context="form-new"
|
||||||
props={{
|
props={{
|
||||||
dataSource,
|
dataSource,
|
||||||
showSaveButton: true,
|
buttonPosition: "top",
|
||||||
showDeleteButton: false,
|
buttons: Utils.buildDynamicButtonConfig({
|
||||||
saveButtonLabel: sidePanelSaveLabel || "Save", //always show
|
_id: $component.id + "-form-new",
|
||||||
|
showDeleteButton: false,
|
||||||
|
showSaveButton: true,
|
||||||
|
saveButtonLabel: "Save",
|
||||||
|
notificationOverride,
|
||||||
|
actionType: "Create",
|
||||||
|
dataSource,
|
||||||
|
}),
|
||||||
actionType: "Create",
|
actionType: "Create",
|
||||||
fields: sidePanelFields || normalFields,
|
fields: sidePanelFields || normalFields,
|
||||||
title: "Create Row",
|
title: "Create Row",
|
||||||
labelPosition: "left",
|
labelPosition: "left",
|
||||||
notificationOverride,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</BlockComponent>
|
</BlockComponent>
|
||||||
|
|
|
@ -4,28 +4,31 @@
|
||||||
import Block from "components/Block.svelte"
|
import Block from "components/Block.svelte"
|
||||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
import InnerFormBlock from "./InnerFormBlock.svelte"
|
import InnerFormBlock from "./InnerFormBlock.svelte"
|
||||||
|
import { Utils } from "@budibase/frontend-core"
|
||||||
|
|
||||||
export let actionType
|
export let actionType
|
||||||
export let dataSource
|
export let dataSource
|
||||||
export let size
|
export let size
|
||||||
export let disabled
|
export let disabled
|
||||||
export let fields
|
export let fields
|
||||||
|
export let buttons
|
||||||
|
export let buttonPosition
|
||||||
|
|
||||||
export let title
|
export let title
|
||||||
export let description
|
export let description
|
||||||
export let showDeleteButton
|
|
||||||
export let showSaveButton
|
|
||||||
export let saveButtonLabel
|
|
||||||
export let deleteButtonLabel
|
|
||||||
export let rowId
|
export let rowId
|
||||||
export let actionUrl
|
export let actionUrl
|
||||||
export let noRowsMessage
|
export let noRowsMessage
|
||||||
export let notificationOverride
|
export let notificationOverride
|
||||||
|
|
||||||
// Accommodate old config to ensure delete button does not reappear
|
// Legacy
|
||||||
$: deleteLabel = showDeleteButton === false ? "" : deleteButtonLabel?.trim()
|
export let showDeleteButton
|
||||||
$: saveLabel = showSaveButton === false ? "" : saveButtonLabel?.trim()
|
export let showSaveButton
|
||||||
|
export let saveButtonLabel
|
||||||
|
export let deleteButtonLabel
|
||||||
|
|
||||||
const { fetchDatasourceSchema } = getContext("sdk")
|
const { fetchDatasourceSchema } = getContext("sdk")
|
||||||
|
const component = getContext("component")
|
||||||
|
|
||||||
const convertOldFieldFormat = fields => {
|
const convertOldFieldFormat = fields => {
|
||||||
if (!fields) {
|
if (!fields) {
|
||||||
|
@ -98,11 +101,23 @@
|
||||||
fields: fieldsOrDefault,
|
fields: fieldsOrDefault,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
saveButtonLabel: saveLabel,
|
|
||||||
deleteButtonLabel: deleteLabel,
|
|
||||||
schema,
|
schema,
|
||||||
repeaterId,
|
repeaterId,
|
||||||
notificationOverride,
|
notificationOverride,
|
||||||
|
buttons:
|
||||||
|
buttons ||
|
||||||
|
Utils.buildDynamicButtonConfig({
|
||||||
|
_id: $component.id,
|
||||||
|
showDeleteButton,
|
||||||
|
showSaveButton,
|
||||||
|
saveButtonLabel,
|
||||||
|
deleteButtonLabel,
|
||||||
|
notificationOverride,
|
||||||
|
actionType,
|
||||||
|
actionUrl,
|
||||||
|
dataSource,
|
||||||
|
}),
|
||||||
|
buttonPosition: buttons ? buttonPosition : "top",
|
||||||
}
|
}
|
||||||
const fetchSchema = async () => {
|
const fetchSchema = async () => {
|
||||||
schema = (await fetchDatasourceSchema(dataSource)) || {}
|
schema = (await fetchDatasourceSchema(dataSource)) || {}
|
||||||
|
|
|
@ -1,22 +1,18 @@
|
||||||
<script>
|
<script>
|
||||||
import BlockComponent from "components/BlockComponent.svelte"
|
import BlockComponent from "components/BlockComponent.svelte"
|
||||||
import Placeholder from "components/app/Placeholder.svelte"
|
import Placeholder from "components/app/Placeholder.svelte"
|
||||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
export let dataSource
|
export let dataSource
|
||||||
export let actionUrl
|
|
||||||
export let actionType
|
export let actionType
|
||||||
export let size
|
export let size
|
||||||
export let disabled
|
export let disabled
|
||||||
export let fields
|
export let fields
|
||||||
export let title
|
export let title
|
||||||
export let description
|
export let description
|
||||||
export let saveButtonLabel
|
export let buttons
|
||||||
export let deleteButtonLabel
|
export let buttonPosition = "bottom"
|
||||||
export let schema
|
export let schema
|
||||||
export let repeaterId
|
|
||||||
export let notificationOverride
|
|
||||||
|
|
||||||
const FieldTypeToComponentMap = {
|
const FieldTypeToComponentMap = {
|
||||||
string: "stringfield",
|
string: "stringfield",
|
||||||
|
@ -37,74 +33,7 @@
|
||||||
|
|
||||||
let formId
|
let formId
|
||||||
|
|
||||||
$: onSave = [
|
$: renderHeader = buttons || title
|
||||||
{
|
|
||||||
"##eventHandlerType": "Validate Form",
|
|
||||||
parameters: {
|
|
||||||
componentId: formId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"##eventHandlerType": "Save Row",
|
|
||||||
parameters: {
|
|
||||||
providerId: formId,
|
|
||||||
tableId: dataSource?.resourceId,
|
|
||||||
notificationOverride,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"##eventHandlerType": "Close Screen Modal",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"##eventHandlerType": "Close Side Panel",
|
|
||||||
},
|
|
||||||
// Clear a create form once submitted
|
|
||||||
...(actionType !== "Create"
|
|
||||||
? []
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
"##eventHandlerType": "Clear Form",
|
|
||||||
parameters: {
|
|
||||||
componentId: formId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
{
|
|
||||||
"##eventHandlerType": "Navigate To",
|
|
||||||
parameters: {
|
|
||||||
url: actionUrl,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
$: onDelete = [
|
|
||||||
{
|
|
||||||
"##eventHandlerType": "Delete Row",
|
|
||||||
parameters: {
|
|
||||||
confirm: true,
|
|
||||||
tableId: dataSource?.resourceId,
|
|
||||||
rowId: `{{ ${safe(repeaterId)}.${safe("_id")} }}`,
|
|
||||||
revId: `{{ ${safe(repeaterId)}.${safe("_rev")} }}`,
|
|
||||||
notificationOverride,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"##eventHandlerType": "Close Screen Modal",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"##eventHandlerType": "Close Side Panel",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"##eventHandlerType": "Navigate To",
|
|
||||||
parameters: {
|
|
||||||
url: actionUrl,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
$: renderDeleteButton = deleteButtonLabel && actionType === "Update"
|
|
||||||
$: renderSaveButton = saveButtonLabel && actionType !== "View"
|
|
||||||
$: renderButtons = renderDeleteButton || renderSaveButton
|
|
||||||
$: renderHeader = renderButtons || title
|
|
||||||
|
|
||||||
const getComponentForField = field => {
|
const getComponentForField = field => {
|
||||||
const fieldSchemaName = field.field || field.name
|
const fieldSchemaName = field.field || field.name
|
||||||
|
@ -184,42 +113,14 @@
|
||||||
props={{ text: title || "" }}
|
props={{ text: title || "" }}
|
||||||
order={0}
|
order={0}
|
||||||
/>
|
/>
|
||||||
{#if renderButtons}
|
{#if buttonPosition == "top"}
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
type="container"
|
type="buttongroup"
|
||||||
props={{
|
props={{
|
||||||
direction: "row",
|
buttons,
|
||||||
hAlign: "stretch",
|
|
||||||
vAlign: "center",
|
|
||||||
gap: "M",
|
|
||||||
wrap: true,
|
|
||||||
}}
|
}}
|
||||||
order={1}
|
order={0}
|
||||||
>
|
/>
|
||||||
{#if renderDeleteButton}
|
|
||||||
<BlockComponent
|
|
||||||
type="button"
|
|
||||||
props={{
|
|
||||||
text: deleteButtonLabel,
|
|
||||||
onClick: onDelete,
|
|
||||||
quiet: true,
|
|
||||||
type: "secondary",
|
|
||||||
}}
|
|
||||||
order={0}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{#if renderSaveButton}
|
|
||||||
<BlockComponent
|
|
||||||
type="button"
|
|
||||||
props={{
|
|
||||||
text: saveButtonLabel,
|
|
||||||
onClick: onSave,
|
|
||||||
type: "cta",
|
|
||||||
}}
|
|
||||||
order={1}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</BlockComponent>
|
|
||||||
{/if}
|
{/if}
|
||||||
</BlockComponent>
|
</BlockComponent>
|
||||||
</BlockComponent>
|
</BlockComponent>
|
||||||
|
@ -245,6 +146,20 @@
|
||||||
</BlockComponent>
|
</BlockComponent>
|
||||||
{/key}
|
{/key}
|
||||||
</BlockComponent>
|
</BlockComponent>
|
||||||
|
{#if buttonPosition === "bottom"}
|
||||||
|
<BlockComponent
|
||||||
|
type="buttongroup"
|
||||||
|
props={{
|
||||||
|
buttons,
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
normal: {
|
||||||
|
"margin-top": "16",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
order={1}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</BlockComponent>
|
</BlockComponent>
|
||||||
{:else}
|
{:else}
|
||||||
<Placeholder
|
<Placeholder
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
import IndicatorSet from "./IndicatorSet.svelte"
|
import IndicatorSet from "./IndicatorSet.svelte"
|
||||||
import { builderStore, dndIsDragging } from "stores"
|
import { builderStore, dndIsDragging } from "stores"
|
||||||
|
|
||||||
let componentId
|
$: componentId = $builderStore.hoverComponentId
|
||||||
|
|
||||||
$: zIndex = componentId === $builderStore.selectedComponentId ? 900 : 920
|
$: zIndex = componentId === $builderStore.selectedComponentId ? 900 : 920
|
||||||
|
|
||||||
const onMouseOver = e => {
|
const onMouseOver = e => {
|
||||||
|
@ -24,12 +23,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newId !== componentId) {
|
if (newId !== componentId) {
|
||||||
componentId = newId
|
builderStore.actions.hoverComponent(newId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMouseLeave = () => {
|
const onMouseLeave = () => {
|
||||||
componentId = null
|
builderStore.actions.hoverComponent(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
|
|
@ -32,6 +32,7 @@ const loadBudibase = async () => {
|
||||||
layout: window["##BUDIBASE_PREVIEW_LAYOUT##"],
|
layout: window["##BUDIBASE_PREVIEW_LAYOUT##"],
|
||||||
screen: window["##BUDIBASE_PREVIEW_SCREEN##"],
|
screen: window["##BUDIBASE_PREVIEW_SCREEN##"],
|
||||||
selectedComponentId: window["##BUDIBASE_SELECTED_COMPONENT_ID##"],
|
selectedComponentId: window["##BUDIBASE_SELECTED_COMPONENT_ID##"],
|
||||||
|
hoverComponentId: window["##BUDIBASE_HOVER_COMPONENT_ID##"],
|
||||||
previewId: window["##BUDIBASE_PREVIEW_ID##"],
|
previewId: window["##BUDIBASE_PREVIEW_ID##"],
|
||||||
theme: window["##BUDIBASE_PREVIEW_THEME##"],
|
theme: window["##BUDIBASE_PREVIEW_THEME##"],
|
||||||
customTheme: window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"],
|
customTheme: window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"],
|
||||||
|
|
|
@ -8,6 +8,7 @@ const createBuilderStore = () => {
|
||||||
inBuilder: false,
|
inBuilder: false,
|
||||||
screen: null,
|
screen: null,
|
||||||
selectedComponentId: null,
|
selectedComponentId: null,
|
||||||
|
hoverComponentId: null,
|
||||||
editMode: false,
|
editMode: false,
|
||||||
previewId: null,
|
previewId: null,
|
||||||
theme: null,
|
theme: null,
|
||||||
|
@ -23,6 +24,16 @@ const createBuilderStore = () => {
|
||||||
}
|
}
|
||||||
const store = writable(initialState)
|
const store = writable(initialState)
|
||||||
const actions = {
|
const actions = {
|
||||||
|
hoverComponent: id => {
|
||||||
|
if (id === get(store).hoverComponentId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
store.update(state => ({
|
||||||
|
...state,
|
||||||
|
hoverComponentId: id,
|
||||||
|
}))
|
||||||
|
eventStore.actions.dispatchEvent("hover-component", { id })
|
||||||
|
},
|
||||||
selectComponent: id => {
|
selectComponent: id => {
|
||||||
if (id === get(store).selectedComponentId) {
|
if (id === get(store).selectedComponentId) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
|
import { Helpers } from "@budibase/bbui"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility to wrap an async function and ensure all invocations happen
|
* Utility to wrap an async function and ensure all invocations happen
|
||||||
* sequentially.
|
* sequentially.
|
||||||
|
@ -106,3 +109,135 @@ export const domDebounce = callback => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the default FormBlock button configs per actionType
|
||||||
|
* Parse any legacy button config and mirror its the outcome
|
||||||
|
*
|
||||||
|
* @param {any} props
|
||||||
|
* */
|
||||||
|
export const buildDynamicButtonConfig = props => {
|
||||||
|
const {
|
||||||
|
_id,
|
||||||
|
actionType,
|
||||||
|
dataSource,
|
||||||
|
notificationOverride,
|
||||||
|
actionUrl,
|
||||||
|
showDeleteButton,
|
||||||
|
deleteButtonLabel,
|
||||||
|
showSaveButton,
|
||||||
|
saveButtonLabel,
|
||||||
|
} = props || {}
|
||||||
|
|
||||||
|
if (!_id) {
|
||||||
|
console.log("MISSING ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const formId = `${_id}-form`
|
||||||
|
const repeaterId = `${_id}-repeater`
|
||||||
|
const resourceId = dataSource?.resourceId
|
||||||
|
|
||||||
|
// Accommodate old config to ensure delete button does not reappear
|
||||||
|
const deleteText = showDeleteButton === false ? "" : deleteButtonLabel?.trim()
|
||||||
|
const saveText = showSaveButton === false ? "" : saveButtonLabel?.trim()
|
||||||
|
|
||||||
|
const onSave = [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Validate Form",
|
||||||
|
parameters: {
|
||||||
|
componentId: formId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Save Row",
|
||||||
|
parameters: {
|
||||||
|
providerId: formId,
|
||||||
|
tableId: resourceId,
|
||||||
|
notificationOverride,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Screen Modal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Side Panel",
|
||||||
|
},
|
||||||
|
// Clear a create form once submitted
|
||||||
|
...(actionType !== "Create"
|
||||||
|
? []
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Clear Form",
|
||||||
|
parameters: {
|
||||||
|
componentId: formId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
...(actionUrl
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Navigate To",
|
||||||
|
parameters: {
|
||||||
|
url: actionUrl,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
]
|
||||||
|
|
||||||
|
const onDelete = [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Delete Row",
|
||||||
|
parameters: {
|
||||||
|
confirm: true,
|
||||||
|
tableId: resourceId,
|
||||||
|
rowId: `{{ ${safe(repeaterId)}.${safe("_id")} }}`,
|
||||||
|
revId: `{{ ${safe(repeaterId)}.${safe("_rev")} }}`,
|
||||||
|
notificationOverride,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Screen Modal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Side Panel",
|
||||||
|
},
|
||||||
|
|
||||||
|
...(actionUrl
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Navigate To",
|
||||||
|
parameters: {
|
||||||
|
url: actionUrl,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
]
|
||||||
|
|
||||||
|
const defaultButtons = []
|
||||||
|
|
||||||
|
if (["Update", "Create"].includes(actionType) && showSaveButton !== false) {
|
||||||
|
defaultButtons.push({
|
||||||
|
text: saveText || "Save",
|
||||||
|
_id: Helpers.uuid(),
|
||||||
|
_component: "@budibase/standard-components/button",
|
||||||
|
onClick: onSave,
|
||||||
|
type: "cta",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionType == "Update" && showDeleteButton !== false) {
|
||||||
|
defaultButtons.push({
|
||||||
|
text: deleteText || "Delete",
|
||||||
|
_id: Helpers.uuid(),
|
||||||
|
_component: "@budibase/standard-components/button",
|
||||||
|
onClick: onDelete,
|
||||||
|
quiet: true,
|
||||||
|
type: "secondary",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultButtons
|
||||||
|
}
|
||||||
|
|
|
@ -63,6 +63,7 @@
|
||||||
// Extract data from message
|
// Extract data from message
|
||||||
const {
|
const {
|
||||||
selectedComponentId,
|
selectedComponentId,
|
||||||
|
hoverComponentId,
|
||||||
layout,
|
layout,
|
||||||
screen,
|
screen,
|
||||||
appId,
|
appId,
|
||||||
|
@ -81,6 +82,7 @@
|
||||||
window["##BUDIBASE_PREVIEW_LAYOUT##"] = layout
|
window["##BUDIBASE_PREVIEW_LAYOUT##"] = layout
|
||||||
window["##BUDIBASE_PREVIEW_SCREEN##"] = screen
|
window["##BUDIBASE_PREVIEW_SCREEN##"] = screen
|
||||||
window["##BUDIBASE_SELECTED_COMPONENT_ID##"] = selectedComponentId
|
window["##BUDIBASE_SELECTED_COMPONENT_ID##"] = selectedComponentId
|
||||||
|
window["##BUDIBASE_HOVER_COMPONENT_ID##"] = hoverComponentId
|
||||||
window["##BUDIBASE_PREVIEW_ID##"] = Math.random()
|
window["##BUDIBASE_PREVIEW_ID##"] = Math.random()
|
||||||
window["##BUDIBASE_PREVIEW_THEME##"] = theme
|
window["##BUDIBASE_PREVIEW_THEME##"] = theme
|
||||||
window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"] = customTheme
|
window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"] = customTheme
|
||||||
|
@ -108,4 +110,4 @@
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body></body>
|
<body></body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in New Issue