From e3b0de680567fae82e4e971b9fbb8b2de37d01ea Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 2 Feb 2021 14:32:58 +0000 Subject: [PATCH] Update screen templates to support full form generation. Fix issues with screen templates --- .../createFromScratchScreen.js | 1 + .../screenTemplates/emptyNewRowScreen.js | 13 --- .../screenTemplates/emptyRowDetailScreen.js | 13 --- .../store/screenTemplates/index.js | 21 ++-- .../store/screenTemplates/newRowScreen.js | 53 ++++++--- .../store/screenTemplates/rowDetailScreen.js | 59 ++++++---- .../store/screenTemplates/utils/Component.js | 24 ++-- .../screenTemplates/utils/commonComponents.js | 86 ++++++++++++++- .../design/AppPreview/componentStructure.json | 3 +- packages/client/src/store/context.js | 5 +- packages/client/src/utils/buttonActions.js | 2 +- packages/client/src/utils/componentProps.js | 4 +- packages/standard-components/src/Form.svelte | 103 ------------------ .../standard-components/src/NewRow.svelte | 14 --- packages/standard-components/src/index.js | 1 - 15 files changed, 190 insertions(+), 212 deletions(-) delete mode 100644 packages/builder/src/builderStore/store/screenTemplates/emptyNewRowScreen.js delete mode 100644 packages/builder/src/builderStore/store/screenTemplates/emptyRowDetailScreen.js delete mode 100644 packages/standard-components/src/Form.svelte delete mode 100644 packages/standard-components/src/NewRow.svelte diff --git a/packages/builder/src/builderStore/store/screenTemplates/createFromScratchScreen.js b/packages/builder/src/builderStore/store/screenTemplates/createFromScratchScreen.js index b25562758e..1f30d974ef 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/createFromScratchScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/createFromScratchScreen.js @@ -9,5 +9,6 @@ const createScreen = () => { return new Screen() .mainType("div") .component("@budibase/standard-components/container") + .instanceName("New Screen") .json() } diff --git a/packages/builder/src/builderStore/store/screenTemplates/emptyNewRowScreen.js b/packages/builder/src/builderStore/store/screenTemplates/emptyNewRowScreen.js deleted file mode 100644 index a2f2f6df67..0000000000 --- a/packages/builder/src/builderStore/store/screenTemplates/emptyNewRowScreen.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Screen } from "./utils/Screen" - -export default { - name: `New Row (Empty)`, - create: () => createScreen(), -} - -const createScreen = () => { - return new Screen() - .component("@budibase/standard-components/newrow") - .table("") - .json() -} diff --git a/packages/builder/src/builderStore/store/screenTemplates/emptyRowDetailScreen.js b/packages/builder/src/builderStore/store/screenTemplates/emptyRowDetailScreen.js deleted file mode 100644 index 5dbdcf4e69..0000000000 --- a/packages/builder/src/builderStore/store/screenTemplates/emptyRowDetailScreen.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Screen } from "./utils/Screen" - -export default { - name: `Row Detail (Empty)`, - create: () => createScreen(), -} - -const createScreen = () => { - return new Screen() - .component("@budibase/standard-components/rowdetail") - .table("") - .json() -} diff --git a/packages/builder/src/builderStore/store/screenTemplates/index.js b/packages/builder/src/builderStore/store/screenTemplates/index.js index 7272f3514c..38ae434753 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/index.js +++ b/packages/builder/src/builderStore/store/screenTemplates/index.js @@ -1,17 +1,12 @@ import newRowScreen from "./newRowScreen" import rowDetailScreen from "./rowDetailScreen" import rowListScreen from "./rowListScreen" -import emptyNewRowScreen from "./emptyNewRowScreen" import createFromScratchScreen from "./createFromScratchScreen" -import emptyRowDetailScreen from "./emptyRowDetailScreen" const allTemplates = tables => [ - createFromScratchScreen, ...newRowScreen(tables), ...rowDetailScreen(tables), ...rowListScreen(tables), - emptyNewRowScreen, - emptyRowDetailScreen, ] // Allows us to apply common behaviour to all create() functions @@ -22,8 +17,18 @@ const createTemplateOverride = (frontendState, create) => () => { return screen } -export default (frontendState, tables) => - allTemplates(tables).map(template => ({ +export default (frontendState, tables) => { + const enrichTemplate = template => ({ ...template, create: createTemplateOverride(frontendState, template.create), - })) + }) + + const fromScratch = enrichTemplate(createFromScratchScreen) + const tableTemplates = allTemplates(tables).map(enrichTemplate) + return [ + fromScratch, + ...tableTemplates.sort((templateA, templateB) => { + return templateA.name > templateB.name ? 1 : -1 + }), + ] +} diff --git a/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js b/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js index 2790a68677..21754256cd 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js @@ -1,11 +1,12 @@ import sanitizeUrl from "./utils/sanitizeUrl" -import { Component } from "./utils/Component" import { Screen } from "./utils/Screen" +import { Component } from "./utils/Component" import { makeBreadcrumbContainer, - makeMainContainer, + makeMainForm, makeTitleContainer, makeSaveButton, + makeSchemaFormComponents, } from "./utils/commonComponents" export default function(tables) { @@ -21,29 +22,45 @@ export default function(tables) { export const newRowUrl = table => sanitizeUrl(`/${table.name}/new/row`) export const NEW_ROW_TEMPLATE = "NEW_ROW_TEMPLATE" -function generateTitleContainer(table, providerId) { - return makeTitleContainer("New Row").addChild( - makeSaveButton(table, providerId) - ) +function generateTitleContainer(table, formId) { + return makeTitleContainer("New Row").addChild(makeSaveButton(table, formId)) } const createScreen = table => { const screen = new Screen() - .component("@budibase/standard-components/newrow") - .table(table._id) - .route(newRowUrl(table)) + .component("@budibase/standard-components/container") .instanceName(`${table.name} - New`) - .name("") + .route(newRowUrl(table)) - const dataform = new Component( - "@budibase/standard-components/dataformwide" - ).instanceName("Form") + const form = makeMainForm() + .instanceName("Form") + .customProps({ + theme: "spectrum--light", + size: "spectrum--medium", + datasource: { + label: table.name, + tableId: table._id, + type: "table", + }, + }) - const providerId = screen._json.props._id - const container = makeMainContainer() + const fieldGroup = new Component("@budibase/standard-components/fieldgroup") + .instanceName("Field Group") + .customProps({ + labelPosition: "left", + }) + + // Add all form fields from this schema to the field group + makeSchemaFormComponents(table._id).forEach(component => { + fieldGroup.addChild(component) + }) + + // Add all children to the form + const formId = form._json._id + form .addChild(makeBreadcrumbContainer(table.name, "New")) - .addChild(generateTitleContainer(table, providerId)) - .addChild(dataform) + .addChild(generateTitleContainer(table, formId)) + .addChild(fieldGroup) - return screen.addChild(container).json() + return screen.addChild(form).json() } diff --git a/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js b/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js index 0a9148eaf8..02502dbb44 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js @@ -3,20 +3,18 @@ import { rowListUrl } from "./rowListScreen" import { Screen } from "./utils/Screen" import { Component } from "./utils/Component" import { - makeMainContainer, makeBreadcrumbContainer, makeTitleContainer, makeSaveButton, + makeSchemaFormComponents, + makeMainForm, } from "./utils/commonComponents" export default function(tables) { return tables.map(table => { - const heading = table.primaryDisplay - ? `{{ data.${table.primaryDisplay} }}` - : null return { name: `${table.name} - Detail`, - create: () => createScreen(table, heading), + create: () => createScreen(table), id: ROW_DETAIL_TEMPLATE, } }) @@ -25,9 +23,9 @@ export default function(tables) { export const ROW_DETAIL_TEMPLATE = "ROW_DETAIL_TEMPLATE" export const rowDetailUrl = table => sanitizeUrl(`/${table.name}/:id`) -function generateTitleContainer(table, title, providerId) { +function generateTitleContainer(table, title, formId) { // have to override style for this, its missing margin - const saveButton = makeSaveButton(table, providerId).normalStyle({ + const saveButton = makeSaveButton(table, formId).normalStyle({ background: "#000000", "border-width": "0", "border-style": "None", @@ -60,8 +58,8 @@ function generateTitleContainer(table, title, providerId) { onClick: [ { parameters: { - rowId: `{{ ${providerId}._id }}`, - revId: `{{ ${providerId}._rev }}`, + rowId: `{{ ${formId}._id }}`, + revId: `{{ ${formId}._rev }}`, tableId: table._id, }, "##eventHandlerType": "Delete Row", @@ -81,23 +79,46 @@ function generateTitleContainer(table, title, providerId) { .addChild(saveButton) } -const createScreen = (table, heading) => { +const createScreen = table => { const screen = new Screen() .component("@budibase/standard-components/rowdetail") .table(table._id) .instanceName(`${table.name} - Detail`) .route(rowDetailUrl(table)) - .name("") - const dataform = new Component( - "@budibase/standard-components/dataformwide" - ).instanceName("Form") + const form = makeMainForm() + .instanceName("Form") + .customProps({ + theme: "spectrum--light", + size: "spectrum--medium", + datasource: { + label: table.name, + tableId: table._id, + type: "table", + }, + }) - const providerId = screen._json.props._id - const container = makeMainContainer() + const fieldGroup = new Component("@budibase/standard-components/fieldgroup") + .instanceName("Field Group") + .customProps({ + labelPosition: "left", + }) + + // Add all form fields from this schema to the field group + makeSchemaFormComponents(table._id).forEach(component => { + fieldGroup.addChild(component) + }) + + // Add all children to the form + const formId = form._json._id + const rowDetailId = screen._json.props._id + const heading = table.primaryDisplay + ? `{{ ${rowDetailId}.${table.primaryDisplay} }}` + : null + form .addChild(makeBreadcrumbContainer(table.name, heading || "Edit")) - .addChild(generateTitleContainer(table, heading || "Edit Row", providerId)) - .addChild(dataform) + .addChild(generateTitleContainer(table, heading || "Edit Row", formId)) + .addChild(fieldGroup) - return screen.addChild(container).json() + return screen.addChild(form).json() } diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/Component.js b/packages/builder/src/builderStore/store/screenTemplates/utils/Component.js index a74ea526f7..1e588895af 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/utils/Component.js +++ b/packages/builder/src/builderStore/store/screenTemplates/utils/Component.js @@ -14,17 +14,11 @@ export class Component extends BaseStructure { active: {}, selected: {}, }, - type: "", _instanceName: "", _children: [], } } - type(type) { - this._json.type = type - return this - } - normalStyle(styling) { this._json._styles.normal = styling return this @@ -35,14 +29,20 @@ export class Component extends BaseStructure { return this } - text(text) { - this._json.text = text - return this - } - - // TODO: do we need this instanceName(name) { this._json._instanceName = name return this } + + // Shorthand for custom props "type" + type(type) { + this._json.type = type + return this + } + + // Shorthand for custom props "text" + text(text) { + this._json.text = text + return this + } } diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js index a00f66f828..f0bd52d53c 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js +++ b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js @@ -1,5 +1,15 @@ +import { get } from "svelte/store" import { Component } from "./Component" import { rowListUrl } from "../rowListScreen" +import { backendUiStore } from "builderStore" +import StringFieldSelect from "../../../../components/design/PropertiesPanel/PropertyControls/StringFieldSelect.svelte" +import NumberFieldSelect from "../../../../components/design/PropertiesPanel/PropertyControls/NumberFieldSelect.svelte" +import OptionsFieldSelect from "../../../../components/design/PropertiesPanel/PropertyControls/OptionsFieldSelect.svelte" +import BooleanFieldSelect from "../../../../components/design/PropertiesPanel/PropertyControls/BooleanFieldSelect.svelte" +import LongFormFieldSelect from "../../../../components/design/PropertiesPanel/PropertyControls/LongFormFieldSelect.svelte" +import DateTimeFieldSelect from "../../../../components/design/PropertiesPanel/PropertyControls/DateTimeFieldSelect.svelte" +import AttachmentFieldSelect from "../../../../components/design/PropertiesPanel/PropertyControls/AttachmentFieldSelect.svelte" +import RelationshipFieldSelect from "../../../../components/design/PropertiesPanel/PropertyControls/RelationshipFieldSelect.svelte" export function makeLinkComponent(tableName) { return new Component("@budibase/standard-components/link") @@ -22,13 +32,12 @@ export function makeLinkComponent(tableName) { }) } -export function makeMainContainer() { - return new Component("@budibase/standard-components/container") +export function makeMainForm() { + return new Component("@budibase/standard-components/form") .type("div") .normalStyle({ width: "700px", padding: "0px", - background: "white", "border-radius": "0.5rem", "box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)", margin: "auto", @@ -39,7 +48,26 @@ export function makeMainContainer() { "padding-left": "48px", "margin-bottom": "20px", }) - .instanceName("Container") + .instanceName("Form") +} + +export function makeMainContainer() { + return new Component("@budibase/standard-components/container") + .type("div") + .normalStyle({ + width: "700px", + padding: "0px", + "border-radius": "0.5rem", + "box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)", + margin: "auto", + "margin-top": "20px", + "padding-top": "48px", + "padding-bottom": "48px", + "padding-right": "48px", + "padding-left": "48px", + "margin-bottom": "20px", + }) + .instanceName("Form") } export function makeBreadcrumbContainer(tableName, text, capitalise = false) { @@ -78,7 +106,7 @@ export function makeBreadcrumbContainer(tableName, text, capitalise = false) { .addChild(identifierText) } -export function makeSaveButton(table, providerId) { +export function makeSaveButton(table, formId) { return new Component("@budibase/standard-components/button") .normalStyle({ background: "#000000", @@ -99,8 +127,14 @@ export function makeSaveButton(table, providerId) { disabled: false, onClick: [ { + "##eventHandlerType": "Validate Form", parameters: { - providerId, + componentId: formId, + }, + }, + { + parameters: { + providerId: formId, }, "##eventHandlerType": "Save Row", }, @@ -142,3 +176,43 @@ export function makeTitleContainer(title) { .instanceName("Title Container") .addChild(heading) } + +const fieldTypeToComponentMap = { + string: "stringfield", + number: "numberfield", + options: "optionsfield", + boolean: "booleanfield", + longform: "longformfield", + datetime: "datetimefield", + attachment: "attachmentfield", + link: "relationshipfield", +} + +export function makeSchemaFormComponents(tableId) { + const tables = get(backendUiStore).tables + const schema = tables.find(table => table._id === tableId)?.schema ?? {} + let components = [] + let fields = Object.keys(schema) + fields.forEach(field => { + const fieldSchema = schema[field] + const componentType = fieldTypeToComponentMap[fieldSchema.type] + const fullComponentType = `@budibase/standard-components/${componentType}` + if (componentType) { + const component = new Component(fullComponentType) + .instanceName(field) + .customProps({ + field, + label: field, + placeholder: field, + }) + if (fieldSchema.type === "options") { + component.customProps({ placeholder: "Choose an option " }) + } + if (fieldSchema.type === "boolean") { + component.customProps({ text: field, label: "" }) + } + components.push(component) + } + }) + return components +} diff --git a/packages/builder/src/components/design/AppPreview/componentStructure.json b/packages/builder/src/components/design/AppPreview/componentStructure.json index 485b429b23..61dc241d2f 100644 --- a/packages/builder/src/components/design/AppPreview/componentStructure.json +++ b/packages/builder/src/components/design/AppPreview/componentStructure.json @@ -60,8 +60,7 @@ "screenslot", "navigation", "login", - "rowdetail", - "newrow" + "rowdetail" ] } ] diff --git a/packages/client/src/store/context.js b/packages/client/src/store/context.js index a040c5bd20..f6c07bf41d 100644 --- a/packages/client/src/store/context.js +++ b/packages/client/src/store/context.js @@ -9,7 +9,10 @@ export const createContextStore = existingContext => { store.update(state => { if (componentId) { state[componentId] = data - state[`${componentId}_draft`] = cloneDeep(data) + + // Keep track of the closest component ID so we can later hydrate a "data" prop. + // This is only required for legacy bindings that used "data" rather than a + // component ID. state.closestComponentId = componentId } return state diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js index e293223c5d..fdeb65ffb0 100644 --- a/packages/client/src/utils/buttonActions.js +++ b/packages/client/src/utils/buttonActions.js @@ -7,7 +7,7 @@ import { ActionTypes } from "../constants" const saveRowHandler = async (action, context) => { const { fields, providerId } = action.parameters if (providerId) { - let draft = context[`${providerId}_draft`] + let draft = context[providerId] if (fields) { for (let [key, entry] of Object.entries(fields)) { draft[key] = await enrichDataBinding(entry.value, context) diff --git a/packages/client/src/utils/componentProps.js b/packages/client/src/utils/componentProps.js index c4d2d668b1..42bb6e624a 100644 --- a/packages/client/src/utils/componentProps.js +++ b/packages/client/src/utils/componentProps.js @@ -35,8 +35,10 @@ export const enrichProps = async (props, context, user) => { const totalContext = { ...context, user, + + // This is only required for legacy bindings that used "data" rather than a + // component ID. data: context[context.closestComponentId], - data_draft: context[`${context.closestComponentId}_draft`], } // Enrich all data bindings in top level props diff --git a/packages/standard-components/src/Form.svelte b/packages/standard-components/src/Form.svelte deleted file mode 100644 index bec99de553..0000000000 --- a/packages/standard-components/src/Form.svelte +++ /dev/null @@ -1,103 +0,0 @@ - - -
- - {#each fields as field} -
- {#if !(schema[field].type === 'boolean' && !wide)} - - {/if} - {#if schema[field].type === 'options'} - - {:else if schema[field].type === 'datetime'} - - {:else if schema[field].type === 'boolean'} - - {:else if schema[field].type === 'number'} - - {:else if schema[field].type === 'string'} - - {:else if schema[field].type === 'longform'} - - {:else if schema[field].type === 'attachment'} - - {:else if schema[field].type === 'link'} - - {/if} -
- {/each} -
- - diff --git a/packages/standard-components/src/NewRow.svelte b/packages/standard-components/src/NewRow.svelte deleted file mode 100644 index 9830c87015..0000000000 --- a/packages/standard-components/src/NewRow.svelte +++ /dev/null @@ -1,14 +0,0 @@ - - -
- - - -
diff --git a/packages/standard-components/src/index.js b/packages/standard-components/src/index.js index e100a0d6bd..a30e747363 100644 --- a/packages/standard-components/src/index.js +++ b/packages/standard-components/src/index.js @@ -28,7 +28,6 @@ export { default as image } from "./Image.svelte" export { default as embed } from "./Embed.svelte" export { default as cardhorizontal } from "./CardHorizontal.svelte" export { default as cardstat } from "./CardStat.svelte" -export { default as newrow } from "./NewRow.svelte" export { default as icon } from "./Icon.svelte" export * from "./charts" export * from "./forms"