Update screen templates to support full form generation. Fix issues with screen templates
This commit is contained in:
parent
3dd6769777
commit
e3b0de6805
|
@ -9,5 +9,6 @@ const createScreen = () => {
|
|||
return new Screen()
|
||||
.mainType("div")
|
||||
.component("@budibase/standard-components/container")
|
||||
.instanceName("New Screen")
|
||||
.json()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -60,8 +60,7 @@
|
|||
"screenslot",
|
||||
"navigation",
|
||||
"login",
|
||||
"rowdetail",
|
||||
"newrow"
|
||||
"rowdetail"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
import {
|
||||
Label,
|
||||
DatePicker,
|
||||
Input,
|
||||
Select,
|
||||
Toggle,
|
||||
RichText,
|
||||
} from "@budibase/bbui"
|
||||
import Dropzone from "./attachments/Dropzone.svelte"
|
||||
import LinkedRowSelector from "./LinkedRowSelector.svelte"
|
||||
import { capitalise } from "./helpers"
|
||||
|
||||
const { styleable, API } = getContext("sdk")
|
||||
const component = getContext("component")
|
||||
const context = getContext("context")
|
||||
|
||||
export let wide = false
|
||||
|
||||
let row
|
||||
let schema
|
||||
let fields = []
|
||||
|
||||
// Fetch info about the closest data context
|
||||
$: getFormData($context[$context.closestComponentId])
|
||||
|
||||
const getFormData = async context => {
|
||||
if (context) {
|
||||
const tableDefinition = await API.fetchTableDefinition(context.tableId)
|
||||
schema = tableDefinition?.schema
|
||||
fields = Object.keys(schema ?? {})
|
||||
|
||||
// Use the draft version for editing
|
||||
row = $context[`${$context.closestComponentId}_draft`]
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="form-content" use:styleable={$component.styles}>
|
||||
<!-- <ErrorsBox errors={$store.saveRowErrors || {}} />-->
|
||||
{#each fields as field}
|
||||
<div class="form-field" class:wide>
|
||||
{#if !(schema[field].type === 'boolean' && !wide)}
|
||||
<Label extraSmall={!wide} grey>{capitalise(schema[field].name)}</Label>
|
||||
{/if}
|
||||
{#if schema[field].type === 'options'}
|
||||
<Select secondary bind:value={row[field]}>
|
||||
<option value="">Choose an option</option>
|
||||
{#each schema[field].constraints.inclusion as opt}
|
||||
<option>{opt}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
{:else if schema[field].type === 'datetime'}
|
||||
<DatePicker bind:value={row[field]} />
|
||||
{:else if schema[field].type === 'boolean'}
|
||||
<Toggle
|
||||
text={wide ? null : capitalise(schema[field].name)}
|
||||
bind:checked={row[field]} />
|
||||
{:else if schema[field].type === 'number'}
|
||||
<Input type="number" bind:value={row[field]} />
|
||||
{:else if schema[field].type === 'string'}
|
||||
<Input bind:value={row[field]} />
|
||||
{:else if schema[field].type === 'longform'}
|
||||
<RichText bind:value={row[field]} />
|
||||
{:else if schema[field].type === 'attachment'}
|
||||
<Dropzone bind:files={row[field]} />
|
||||
{:else if schema[field].type === 'link'}
|
||||
<LinkedRowSelector
|
||||
secondary
|
||||
showLabel={false}
|
||||
bind:linkedRows={row[field]}
|
||||
schema={schema[field]} />
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
display: grid;
|
||||
gap: var(--spacing-xl);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
display: grid;
|
||||
}
|
||||
.form-field.wide {
|
||||
align-items: center;
|
||||
grid-template-columns: 20% 1fr;
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
.form-field.wide :global(label) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
|
@ -1,14 +0,0 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
|
||||
const { Provider, styleable } = getContext("sdk")
|
||||
const component = getContext("component")
|
||||
|
||||
export let table
|
||||
</script>
|
||||
|
||||
<div use:styleable={$component.styles}>
|
||||
<Provider data={{ tableId: table }}>
|
||||
<slot />
|
||||
</Provider>
|
||||
</div>
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue