Merge branch 'master' into fix/massive-attachment-export
This commit is contained in:
commit
647fc563cc
|
@ -87,6 +87,7 @@ couchdb:
|
|||
storageClass: "nfs-client"
|
||||
adminPassword: admin
|
||||
|
||||
services:
|
||||
objectStore:
|
||||
storageClass: "nfs-client"
|
||||
redis:
|
||||
|
|
|
@ -86,6 +86,7 @@ couchdb:
|
|||
storageClass: "nfs-client"
|
||||
adminPassword: admin
|
||||
|
||||
services:
|
||||
objectStore:
|
||||
storageClass: "nfs-client"
|
||||
redis:
|
||||
|
|
|
@ -249,4 +249,30 @@ http {
|
|||
gzip_comp_level 6;
|
||||
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
|
||||
}
|
||||
|
||||
# From https://docs.datadoghq.com/integrations/nginx/?tab=kubernetes
|
||||
server {
|
||||
listen 81;
|
||||
server_name localhost;
|
||||
|
||||
access_log off;
|
||||
allow 127.0.0.1;
|
||||
deny all;
|
||||
|
||||
location /nginx_status {
|
||||
# Choose your status module
|
||||
|
||||
# freely available with open source NGINX
|
||||
stub_status;
|
||||
|
||||
# for open source NGINX < version 1.7.5
|
||||
# stub_status on;
|
||||
|
||||
# available only with NGINX Plus
|
||||
# status;
|
||||
|
||||
# ensures the version information can be retrieved
|
||||
server_tokens on;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import { derived, get } from "svelte/store"
|
|||
import { findComponent, findComponentPath } from "./componentUtils"
|
||||
import { RoleUtils } from "@budibase/frontend-core"
|
||||
import { createHistoryStore } from "builderStore/store/history"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
|
||||
export const store = getFrontendStore()
|
||||
export const automationStore = getAutomationStore()
|
||||
|
@ -69,7 +70,14 @@ export const selectedComponent = derived(
|
|||
if (!$selectedScreen || !$store.selectedComponentId) {
|
||||
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
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -601,6 +601,36 @@ export const getFrontendStore = () => {
|
|||
// Finally try an external table
|
||||
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) => {
|
||||
if (!component?._component) {
|
||||
return
|
||||
|
@ -672,7 +702,6 @@ export const getFrontendStore = () => {
|
|||
component[setting.key] = setting.defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
// Validate non-empty settings
|
||||
else {
|
||||
if (setting.type === "dataProvider") {
|
||||
|
@ -722,6 +751,9 @@ export const getFrontendStore = () => {
|
|||
useDefaultValues: true,
|
||||
})
|
||||
|
||||
// Migrate nested component settings
|
||||
store.actions.components.migrateSettings(instance)
|
||||
|
||||
// Add any extra properties the component needs
|
||||
let extras = {}
|
||||
if (definition.hasChildren) {
|
||||
|
@ -845,7 +877,13 @@ export const getFrontendStore = () => {
|
|||
if (!component) {
|
||||
return false
|
||||
}
|
||||
return patchFn(component, screen)
|
||||
|
||||
// Mutates the fetched component with updates
|
||||
const updated = patchFn(component, screen)
|
||||
// Mutates the component with any required settings updates
|
||||
const migrated = store.actions.components.migrateSettings(component)
|
||||
|
||||
return updated || migrated
|
||||
}
|
||||
await store.actions.screens.patch(patchScreen, screenId)
|
||||
},
|
||||
|
@ -1247,9 +1285,13 @@ export const getFrontendStore = () => {
|
|||
const settings = getComponentSettings(component._component)
|
||||
const updatedSetting = settings.find(setting => setting.key === name)
|
||||
|
||||
const resetFields = settings.filter(
|
||||
setting => name === setting.resetOn
|
||||
)
|
||||
// Can be a single string or array of strings
|
||||
const resetFields = settings.filter(setting => {
|
||||
return (
|
||||
name === setting.resetOn ||
|
||||
(Array.isArray(setting.resetOn) && setting.resetOn.includes(name))
|
||||
)
|
||||
})
|
||||
resetFields?.forEach(setting => {
|
||||
component[setting.key] = null
|
||||
})
|
||||
|
@ -1271,6 +1313,7 @@ export const getFrontendStore = () => {
|
|||
})
|
||||
}
|
||||
component[name] = value
|
||||
return true
|
||||
}
|
||||
},
|
||||
requestEjectBlock: componentId => {
|
||||
|
@ -1278,7 +1321,6 @@ export const getFrontendStore = () => {
|
|||
},
|
||||
handleEjectBlock: async (componentId, ejectedDefinition) => {
|
||||
let nextSelectedComponentId
|
||||
|
||||
await store.actions.screens.patch(screen => {
|
||||
const block = findComponent(screen.props, componentId)
|
||||
const parent = findComponentParent(screen.props, componentId)
|
||||
|
|
|
@ -57,16 +57,11 @@
|
|||
}}
|
||||
class="buttons"
|
||||
>
|
||||
<Icon hoverable size="M" name="Play" />
|
||||
<Icon size="M" name="Play" />
|
||||
<div>Run test</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<Icon
|
||||
disabled={!$automationStore.testResults}
|
||||
hoverable
|
||||
size="M"
|
||||
name="Multiple"
|
||||
/>
|
||||
<Icon disabled={!$automationStore.testResults} size="M" name="Multiple" />
|
||||
<div
|
||||
class:disabled={!$automationStore.testResults}
|
||||
on:click={() => {
|
||||
|
|
|
@ -97,6 +97,7 @@
|
|||
class:typing={typing && !automationNameError}
|
||||
class:typing-error={automationNameError}
|
||||
class="blockSection"
|
||||
on:click={() => dispatch("toggle")}
|
||||
>
|
||||
<div class="splitHeader">
|
||||
<div class="center-items">
|
||||
|
@ -138,7 +139,20 @@
|
|||
on:input={e => {
|
||||
automationName = e.target.value.trim()
|
||||
}}
|
||||
on:click={startTyping}
|
||||
on:click={e => {
|
||||
e.stopPropagation()
|
||||
startTyping()
|
||||
}}
|
||||
on:keydown={async e => {
|
||||
if (e.key === "Enter") {
|
||||
typing = false
|
||||
if (automationNameError) {
|
||||
automationName = stepNames[block.id] || block?.name
|
||||
} else {
|
||||
await saveName()
|
||||
}
|
||||
}
|
||||
}}
|
||||
on:blur={async () => {
|
||||
typing = false
|
||||
if (automationNameError) {
|
||||
|
@ -168,7 +182,11 @@
|
|||
</StatusLight>
|
||||
</div>
|
||||
<Icon
|
||||
on:click={() => dispatch("toggle")}
|
||||
e.stopPropagation()
|
||||
on:click={e => {
|
||||
e.stopPropagation()
|
||||
dispatch("toggle")
|
||||
}}
|
||||
hoverable
|
||||
name={open ? "ChevronUp" : "ChevronDown"}
|
||||
/>
|
||||
|
@ -195,7 +213,10 @@
|
|||
{/if}
|
||||
{#if !showTestStatus}
|
||||
<Icon
|
||||
on:click={() => dispatch("toggle")}
|
||||
on:click={e => {
|
||||
e.stopPropagation()
|
||||
dispatch("toggle")
|
||||
}}
|
||||
hoverable
|
||||
name={open ? "ChevronUp" : "ChevronDown"}
|
||||
/>
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
<script>
|
||||
import {
|
||||
ModalContent,
|
||||
Tabs,
|
||||
Tab,
|
||||
TextArea,
|
||||
Label,
|
||||
notifications,
|
||||
ActionButton,
|
||||
} from "@budibase/bbui"
|
||||
import { automationStore, selectedAutomation } from "builderStore"
|
||||
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
|
||||
|
@ -55,50 +53,69 @@
|
|||
notifications.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
const toggle = () => {
|
||||
selectedValues = !selectedValues
|
||||
selectedJSON = !selectedJSON
|
||||
}
|
||||
let selectedValues = true
|
||||
let selectedJSON = false
|
||||
</script>
|
||||
|
||||
<ModalContent
|
||||
title="Add test data"
|
||||
confirmText="Test"
|
||||
size="M"
|
||||
confirmText="Run test"
|
||||
size="L"
|
||||
showConfirmButton={true}
|
||||
disabled={isError}
|
||||
onConfirm={testAutomation}
|
||||
cancelText="Cancel"
|
||||
>
|
||||
<Tabs selected="Form" quiet>
|
||||
<Tab icon="Form" title="Form">
|
||||
<div class="tab-content-padding">
|
||||
<AutomationBlockSetup
|
||||
{testData}
|
||||
{schemaProperties}
|
||||
isTestModal
|
||||
block={trigger}
|
||||
/>
|
||||
</div></Tab
|
||||
>
|
||||
<Tab icon="FileJson" title="JSON">
|
||||
<div class="tab-content-padding">
|
||||
<Label>JSON</Label>
|
||||
<div class="text-area-container">
|
||||
<TextArea
|
||||
value={JSON.stringify($selectedAutomation.testData, null, 2)}
|
||||
error={failedParse}
|
||||
on:change={e => parseTestJSON(e)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<div class="size">
|
||||
<div class="options">
|
||||
<ActionButton quiet selected={selectedValues} on:click={toggle}
|
||||
>Use values</ActionButton
|
||||
>
|
||||
<ActionButton quiet selected={selectedJSON} on:click={toggle}
|
||||
>Use JSON</ActionButton
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if selectedValues}
|
||||
<div class="tab-content-padding">
|
||||
<AutomationBlockSetup
|
||||
{testData}
|
||||
{schemaProperties}
|
||||
isTestModal
|
||||
block={trigger}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if selectedJSON}
|
||||
<div class="text-area-container">
|
||||
<TextArea
|
||||
value={JSON.stringify($selectedAutomation.testData, null, 2)}
|
||||
error={failedParse}
|
||||
on:change={e => parseTestJSON(e)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</ModalContent>
|
||||
|
||||
<style>
|
||||
.text-area-container :global(textarea) {
|
||||
min-height: 200px;
|
||||
height: 200px;
|
||||
min-height: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.tab-content-padding {
|
||||
padding: 0 var(--spacing-xl);
|
||||
padding: 0 var(--spacing-s);
|
||||
}
|
||||
|
||||
.options {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<div class="title">
|
||||
<div class="title-text">
|
||||
<Icon name="MultipleCheck" />
|
||||
<div style="padding-left: var(--spacing-l)">Test Details</div>
|
||||
<div style="padding-left: var(--spacing-l); ">Test Details</div>
|
||||
</div>
|
||||
<div style="padding-right: var(--spacing-xl)">
|
||||
<Icon
|
||||
|
@ -40,6 +40,7 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding-top: var(--spacing-s);
|
||||
}
|
||||
|
||||
.title :global(h1) {
|
||||
|
|
|
@ -1,20 +1,44 @@
|
|||
<script>
|
||||
import AutomationList from "./AutomationList.svelte"
|
||||
import CreateAutomationModal from "./CreateAutomationModal.svelte"
|
||||
import { Modal, Button, Layout } from "@budibase/bbui"
|
||||
import { Modal, Icon } from "@budibase/bbui"
|
||||
import Panel from "components/design/Panel.svelte"
|
||||
|
||||
export let modal
|
||||
export let webhookModal
|
||||
</script>
|
||||
|
||||
<Panel title="Automations" borderRight>
|
||||
<Layout paddingX="L" paddingY="XL" gap="S">
|
||||
<Button cta on:click={modal.show}>Add automation</Button>
|
||||
</Layout>
|
||||
<Panel title="Automations" borderRight noHeaderBorder titleCSS={false}>
|
||||
<span class="panel-title-content" slot="panel-title-content">
|
||||
<div class="header">
|
||||
<div>Automations</div>
|
||||
<div on:click={modal.show} class="add-automation-button">
|
||||
<Icon name="Add" />
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
<AutomationList />
|
||||
</Panel>
|
||||
|
||||
<Modal bind:this={modal}>
|
||||
<CreateAutomationModal {webhookModal} />
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.add-automation-button {
|
||||
margin-left: 130px;
|
||||
color: var(--grey-7);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.add-automation-button:hover {
|
||||
color: var(--ink);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -149,7 +149,6 @@
|
|||
}
|
||||
const initialiseField = (field, savingColumn) => {
|
||||
isCreating = !field
|
||||
|
||||
if (field && !savingColumn) {
|
||||
editableColumn = cloneDeep(field)
|
||||
originalName = editableColumn.name ? editableColumn.name + "" : null
|
||||
|
@ -171,7 +170,8 @@
|
|||
relationshipPart2 = part2
|
||||
}
|
||||
}
|
||||
} else if (!savingColumn) {
|
||||
}
|
||||
if (!savingColumn) {
|
||||
let highestNumber = 0
|
||||
Object.keys(table.schema).forEach(columnName => {
|
||||
const columnNumber = extractColumnNumber(columnName)
|
||||
|
@ -307,12 +307,6 @@
|
|||
dispatch("updatecolumns")
|
||||
gridDispatch("close-edit-column")
|
||||
|
||||
if (saveColumn.type === LINK_TYPE) {
|
||||
// Fetching the new tables
|
||||
tables.fetch()
|
||||
// Fetching the new relationships
|
||||
datasources.fetch()
|
||||
}
|
||||
if (originalName) {
|
||||
notifications.success("Column updated successfully")
|
||||
} else {
|
||||
|
@ -339,11 +333,6 @@
|
|||
confirmDeleteDialog.hide()
|
||||
dispatch("updatecolumns")
|
||||
gridDispatch("close-edit-column")
|
||||
|
||||
if (editableColumn.type === LINK_TYPE) {
|
||||
// Updating the relationships
|
||||
datasources.fetch()
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error(`Error deleting column: ${error.message}`)
|
||||
|
@ -540,8 +529,16 @@
|
|||
<Layout noPadding gap="S">
|
||||
{#if mounted}
|
||||
<Input
|
||||
value={editableColumn.name}
|
||||
autofocus
|
||||
bind:value={editableColumn.name}
|
||||
on:input={e => {
|
||||
if (
|
||||
!uneditable &&
|
||||
!(linkEditDisabled && editableColumn.type === LINK_TYPE)
|
||||
) {
|
||||
editableColumn.name = e.target.value
|
||||
}
|
||||
}}
|
||||
disabled={uneditable ||
|
||||
(linkEditDisabled && editableColumn.type === LINK_TYPE)}
|
||||
error={errors?.name}
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
export let wide = false
|
||||
export let extraWide = false
|
||||
export let closeButtonIcon = "Close"
|
||||
|
||||
export let noHeaderBorder = false
|
||||
export let titleCSS = true
|
||||
$: customHeaderContent = $$slots["panel-header-content"]
|
||||
$: customTitleContent = $$slots["panel-title-content"]
|
||||
</script>
|
||||
|
@ -32,6 +33,7 @@
|
|||
class="header"
|
||||
class:custom={customHeaderContent}
|
||||
class:borderBottom={borderBottomHeader}
|
||||
class:noHeaderBorder
|
||||
>
|
||||
{#if showBackButton}
|
||||
<Icon name="ArrowLeft" hoverable on:click={onClickBackButton} />
|
||||
|
@ -41,7 +43,7 @@
|
|||
<Icon name={icon} />
|
||||
</AbsTooltip>
|
||||
{/if}
|
||||
<div class="title">
|
||||
<div class:title={titleCSS}>
|
||||
{#if customTitleContent}
|
||||
<slot name="panel-title-content" />
|
||||
{:else}
|
||||
|
@ -106,6 +108,10 @@
|
|||
padding: 0 var(--spacing-l);
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.noHeaderBorder {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
.header.borderBottom {
|
||||
border-bottom: var(--border-light);
|
||||
}
|
||||
|
|
|
@ -110,7 +110,7 @@
|
|||
}
|
||||
|
||||
.setup {
|
||||
padding-top: var(--spectrum-global-dimension-size-200);
|
||||
padding-top: 9px;
|
||||
border-left: var(--border-light);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -81,13 +81,21 @@ export function createTablesStore() {
|
|||
replaceTable(savedTable._id, savedTable)
|
||||
select(savedTable._id)
|
||||
// make sure tables up to date (related)
|
||||
let tableIdsToFetch = []
|
||||
let newTableIds = []
|
||||
for (let column of Object.values(updatedTable?.schema || {})) {
|
||||
if (column.type === FIELDS.LINK.type) {
|
||||
tableIdsToFetch.push(column.tableId)
|
||||
newTableIds.push(column.tableId)
|
||||
}
|
||||
}
|
||||
tableIdsToFetch = [...new Set(tableIdsToFetch)]
|
||||
|
||||
let oldTableIds = []
|
||||
for (let column of Object.values(oldTable?.schema || {})) {
|
||||
if (column.type === FIELDS.LINK.type) {
|
||||
oldTableIds.push(column.tableId)
|
||||
}
|
||||
}
|
||||
|
||||
const tableIdsToFetch = [...new Set([...newTableIds, ...oldTableIds])]
|
||||
// too many tables to fetch, just get all
|
||||
if (tableIdsToFetch.length > 3) {
|
||||
await fetch()
|
||||
|
|
|
@ -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,
|
||||
"name": "Buttons",
|
||||
"dependsOn": {
|
||||
"setting": "actionType",
|
||||
"value": "View",
|
||||
"invert": true
|
||||
},
|
||||
"settings": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "saveButtonLabel",
|
||||
"label": "Save button",
|
||||
"type": "buttonConfiguration",
|
||||
"key": "buttons",
|
||||
"nested": true,
|
||||
"defaultValue": "Save"
|
||||
},
|
||||
{
|
||||
"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
|
||||
}
|
||||
"resetOn": ["actionType", "dataSource"]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import BlockComponent from "components/BlockComponent.svelte"
|
||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||
import { enrichSearchColumns, enrichFilter } from "utils/blocks.js"
|
||||
import { Utils } from "@budibase/frontend-core"
|
||||
|
||||
export let title
|
||||
export let dataSource
|
||||
|
@ -33,6 +34,7 @@
|
|||
export let notificationOverride
|
||||
|
||||
const { fetchDatasourceSchema, API } = getContext("sdk")
|
||||
const component = getContext("component")
|
||||
const stateKey = `ID_${generate()}`
|
||||
|
||||
let formId
|
||||
|
@ -259,16 +261,25 @@
|
|||
name="Details form block"
|
||||
type="formblock"
|
||||
bind:id={detailsFormBlockId}
|
||||
context="form-edit"
|
||||
props={{
|
||||
dataSource,
|
||||
saveButtonLabel: sidePanelSaveLabel || "Save", //always show
|
||||
deleteButtonLabel: deleteLabel,
|
||||
buttonPosition: "top",
|
||||
buttons: Utils.buildDynamicButtonConfig({
|
||||
_id: $component.id + "-form-edit",
|
||||
showDeleteButton: deleteLabel !== "",
|
||||
showSaveButton: true,
|
||||
saveButtonLabel: sidePanelSaveLabel || "Save",
|
||||
deleteButtonLabel: deleteLabel,
|
||||
notificationOverride,
|
||||
actionType: "Update",
|
||||
dataSource,
|
||||
}),
|
||||
actionType: "Update",
|
||||
rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`,
|
||||
fields: sidePanelFields || normalFields,
|
||||
title: editTitle,
|
||||
labelPosition: "left",
|
||||
notificationOverride,
|
||||
}}
|
||||
/>
|
||||
</BlockComponent>
|
||||
|
@ -284,16 +295,23 @@
|
|||
<BlockComponent
|
||||
name="New row form block"
|
||||
type="formblock"
|
||||
context="form-new"
|
||||
props={{
|
||||
dataSource,
|
||||
showSaveButton: true,
|
||||
showDeleteButton: false,
|
||||
saveButtonLabel: sidePanelSaveLabel || "Save", //always show
|
||||
buttonPosition: "top",
|
||||
buttons: Utils.buildDynamicButtonConfig({
|
||||
_id: $component.id + "-form-new",
|
||||
showDeleteButton: false,
|
||||
showSaveButton: true,
|
||||
saveButtonLabel: "Save",
|
||||
notificationOverride,
|
||||
actionType: "Create",
|
||||
dataSource,
|
||||
}),
|
||||
actionType: "Create",
|
||||
fields: sidePanelFields || normalFields,
|
||||
title: "Create Row",
|
||||
labelPosition: "left",
|
||||
notificationOverride,
|
||||
}}
|
||||
/>
|
||||
</BlockComponent>
|
||||
|
|
|
@ -4,28 +4,31 @@
|
|||
import Block from "components/Block.svelte"
|
||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||
import InnerFormBlock from "./InnerFormBlock.svelte"
|
||||
import { Utils } from "@budibase/frontend-core"
|
||||
|
||||
export let actionType
|
||||
export let dataSource
|
||||
export let size
|
||||
export let disabled
|
||||
export let fields
|
||||
export let buttons
|
||||
export let buttonPosition
|
||||
|
||||
export let title
|
||||
export let description
|
||||
export let showDeleteButton
|
||||
export let showSaveButton
|
||||
export let saveButtonLabel
|
||||
export let deleteButtonLabel
|
||||
export let rowId
|
||||
export let actionUrl
|
||||
export let noRowsMessage
|
||||
export let notificationOverride
|
||||
|
||||
// Accommodate old config to ensure delete button does not reappear
|
||||
$: deleteLabel = showDeleteButton === false ? "" : deleteButtonLabel?.trim()
|
||||
$: saveLabel = showSaveButton === false ? "" : saveButtonLabel?.trim()
|
||||
// Legacy
|
||||
export let showDeleteButton
|
||||
export let showSaveButton
|
||||
export let saveButtonLabel
|
||||
export let deleteButtonLabel
|
||||
|
||||
const { fetchDatasourceSchema } = getContext("sdk")
|
||||
const component = getContext("component")
|
||||
|
||||
const convertOldFieldFormat = fields => {
|
||||
if (!fields) {
|
||||
|
@ -98,11 +101,23 @@
|
|||
fields: fieldsOrDefault,
|
||||
title,
|
||||
description,
|
||||
saveButtonLabel: saveLabel,
|
||||
deleteButtonLabel: deleteLabel,
|
||||
schema,
|
||||
repeaterId,
|
||||
notificationOverride,
|
||||
buttons:
|
||||
buttons ||
|
||||
Utils.buildDynamicButtonConfig({
|
||||
_id: $component.id,
|
||||
showDeleteButton,
|
||||
showSaveButton,
|
||||
saveButtonLabel,
|
||||
deleteButtonLabel,
|
||||
notificationOverride,
|
||||
actionType,
|
||||
actionUrl,
|
||||
dataSource,
|
||||
}),
|
||||
buttonPosition: buttons ? buttonPosition : "top",
|
||||
}
|
||||
const fetchSchema = async () => {
|
||||
schema = (await fetchDatasourceSchema(dataSource)) || {}
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
<script>
|
||||
import BlockComponent from "components/BlockComponent.svelte"
|
||||
import Placeholder from "components/app/Placeholder.svelte"
|
||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||
import { getContext } from "svelte"
|
||||
|
||||
export let dataSource
|
||||
export let actionUrl
|
||||
export let actionType
|
||||
export let size
|
||||
export let disabled
|
||||
export let fields
|
||||
export let title
|
||||
export let description
|
||||
export let saveButtonLabel
|
||||
export let deleteButtonLabel
|
||||
export let buttons
|
||||
export let buttonPosition = "bottom"
|
||||
export let schema
|
||||
export let repeaterId
|
||||
export let notificationOverride
|
||||
|
||||
const FieldTypeToComponentMap = {
|
||||
string: "stringfield",
|
||||
|
@ -37,74 +33,7 @@
|
|||
|
||||
let formId
|
||||
|
||||
$: onSave = [
|
||||
{
|
||||
"##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
|
||||
$: renderHeader = buttons || title
|
||||
|
||||
const getComponentForField = field => {
|
||||
const fieldSchemaName = field.field || field.name
|
||||
|
@ -184,42 +113,14 @@
|
|||
props={{ text: title || "" }}
|
||||
order={0}
|
||||
/>
|
||||
{#if renderButtons}
|
||||
{#if buttonPosition == "top"}
|
||||
<BlockComponent
|
||||
type="container"
|
||||
type="buttongroup"
|
||||
props={{
|
||||
direction: "row",
|
||||
hAlign: "stretch",
|
||||
vAlign: "center",
|
||||
gap: "M",
|
||||
wrap: true,
|
||||
buttons,
|
||||
}}
|
||||
order={1}
|
||||
>
|
||||
{#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>
|
||||
order={0}
|
||||
/>
|
||||
{/if}
|
||||
</BlockComponent>
|
||||
</BlockComponent>
|
||||
|
@ -245,6 +146,20 @@
|
|||
</BlockComponent>
|
||||
{/key}
|
||||
</BlockComponent>
|
||||
{#if buttonPosition === "bottom"}
|
||||
<BlockComponent
|
||||
type="buttongroup"
|
||||
props={{
|
||||
buttons,
|
||||
}}
|
||||
styles={{
|
||||
normal: {
|
||||
"margin-top": "16",
|
||||
},
|
||||
}}
|
||||
order={1}
|
||||
/>
|
||||
{/if}
|
||||
</BlockComponent>
|
||||
{:else}
|
||||
<Placeholder
|
||||
|
|
|
@ -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
|
||||
* 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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue