Merge pull request #13527 from Budibase/prefill-cards
Prefill card blocks
This commit is contained in:
commit
868989d4c6
|
@ -20,7 +20,7 @@ import {
|
||||||
previewStore,
|
previewStore,
|
||||||
tables,
|
tables,
|
||||||
componentTreeNodesStore,
|
componentTreeNodesStore,
|
||||||
} from "stores/builder/index"
|
} from "stores/builder"
|
||||||
import { buildFormSchema, getSchemaForDatasource } from "dataBinding"
|
import { buildFormSchema, getSchemaForDatasource } from "dataBinding"
|
||||||
import {
|
import {
|
||||||
BUDIBASE_INTERNAL_DB_ID,
|
BUDIBASE_INTERNAL_DB_ID,
|
||||||
|
@ -30,6 +30,7 @@ import {
|
||||||
} from "constants/backend"
|
} from "constants/backend"
|
||||||
import BudiStore from "../BudiStore"
|
import BudiStore from "../BudiStore"
|
||||||
import { Utils } from "@budibase/frontend-core"
|
import { Utils } from "@budibase/frontend-core"
|
||||||
|
import { FieldType } from "@budibase/types"
|
||||||
|
|
||||||
export const INITIAL_COMPONENTS_STATE = {
|
export const INITIAL_COMPONENTS_STATE = {
|
||||||
components: {},
|
components: {},
|
||||||
|
@ -296,6 +297,80 @@ export class ComponentStore extends BudiStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Add default bindings to card blocks
|
||||||
|
if (component._component.endsWith("/cardsblock")) {
|
||||||
|
// Only proceed if the card is empty, i.e. we just changed datasource or
|
||||||
|
// just created the card
|
||||||
|
const cardKeys = ["cardTitle", "cardSubtitle", "cardDescription"]
|
||||||
|
if (cardKeys.every(key => !component[key]) && !component.cardImageURL) {
|
||||||
|
const { _id, dataSource } = component
|
||||||
|
if (dataSource) {
|
||||||
|
const { schema, table } = getSchemaForDatasource(screen, dataSource)
|
||||||
|
|
||||||
|
// Finds fields by types from the schema of the configured datasource
|
||||||
|
const findFieldTypes = fieldTypes => {
|
||||||
|
if (!Array.isArray(fieldTypes)) {
|
||||||
|
fieldTypes = [fieldTypes]
|
||||||
|
}
|
||||||
|
return Object.entries(schema || {})
|
||||||
|
.filter(([name, fieldSchema]) => {
|
||||||
|
return (
|
||||||
|
fieldTypes.includes(fieldSchema.type) &&
|
||||||
|
!fieldSchema.autoColumn &&
|
||||||
|
name !== table?.primaryDisplay &&
|
||||||
|
!name.startsWith("_")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map(([name]) => name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inserts a card binding for a certain setting
|
||||||
|
const addBinding = (key, fallback, ...parts) => {
|
||||||
|
if (parts.some(x => x == null)) {
|
||||||
|
component[key] = fallback
|
||||||
|
} else {
|
||||||
|
parts.unshift(`${_id}-repeater`)
|
||||||
|
component[key] = `{{ ${parts.map(safe).join(".")} }}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract good field candidates to prefill our cards with.
|
||||||
|
// Use the primary display as the best field, if it exists.
|
||||||
|
const shortFields = [
|
||||||
|
...findFieldTypes(FieldType.STRING),
|
||||||
|
...findFieldTypes(FieldType.OPTIONS),
|
||||||
|
...findFieldTypes(FieldType.ARRAY),
|
||||||
|
...findFieldTypes(FieldType.NUMBER),
|
||||||
|
]
|
||||||
|
const longFields = findFieldTypes(FieldType.LONGFORM)
|
||||||
|
if (schema?.[table?.primaryDisplay]) {
|
||||||
|
shortFields.unshift(table.primaryDisplay)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill title and subtitle with short fields
|
||||||
|
addBinding("cardTitle", "Title", shortFields[0])
|
||||||
|
addBinding("cardSubtitle", "Subtitle", shortFields[1])
|
||||||
|
|
||||||
|
// Fill description with a long field if possible
|
||||||
|
const longField = longFields[0] ?? shortFields[2]
|
||||||
|
addBinding("cardDescription", "Description", longField)
|
||||||
|
|
||||||
|
// Attempt to fill the image setting.
|
||||||
|
// Check single attachment fields first.
|
||||||
|
let imgField = findFieldTypes(FieldType.ATTACHMENT_SINGLE)[0]
|
||||||
|
if (imgField) {
|
||||||
|
addBinding("cardImageURL", null, imgField, "url")
|
||||||
|
} else {
|
||||||
|
// Then try multi-attachment fields if no single ones exist
|
||||||
|
imgField = findFieldTypes(FieldType.ATTACHMENTS)[0]
|
||||||
|
if (imgField) {
|
||||||
|
addBinding("cardImageURL", null, imgField, 0, "url")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -324,21 +399,21 @@ export class ComponentStore extends BudiStore {
|
||||||
...presetProps,
|
...presetProps,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enrich empty settings
|
// Standard post processing
|
||||||
this.enrichEmptySettings(instance, {
|
this.enrichEmptySettings(instance, {
|
||||||
parent,
|
parent,
|
||||||
screen: get(selectedScreen),
|
screen: get(selectedScreen),
|
||||||
useDefaultValues: true,
|
useDefaultValues: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Migrate nested component settings
|
|
||||||
this.migrateSettings(instance)
|
this.migrateSettings(instance)
|
||||||
|
|
||||||
// Add any extra properties the component needs
|
// Custom post processing for creation only
|
||||||
let extras = {}
|
let extras = {}
|
||||||
if (definition.hasChildren) {
|
if (definition.hasChildren) {
|
||||||
extras._children = []
|
extras._children = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add step name to form steps
|
||||||
if (componentName.endsWith("/formstep")) {
|
if (componentName.endsWith("/formstep")) {
|
||||||
const parentForm = findClosestMatchingComponent(
|
const parentForm = findClosestMatchingComponent(
|
||||||
get(selectedScreen).props,
|
get(selectedScreen).props,
|
||||||
|
@ -351,6 +426,7 @@ export class ComponentStore extends BudiStore {
|
||||||
extras.step = formSteps.length + 1
|
extras.step = formSteps.length + 1
|
||||||
extras._instanceName = `Step ${formSteps.length + 1}`
|
extras._instanceName = `Step ${formSteps.length + 1}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...cloneDeep(instance),
|
...cloneDeep(instance),
|
||||||
...extras,
|
...extras,
|
||||||
|
@ -463,7 +539,6 @@ export class ComponentStore extends BudiStore {
|
||||||
if (!componentId || !screenId) {
|
if (!componentId || !screenId) {
|
||||||
const state = get(this.store)
|
const state = get(this.store)
|
||||||
componentId = componentId || state.selectedComponentId
|
componentId = componentId || state.selectedComponentId
|
||||||
|
|
||||||
const screenState = get(screenStore)
|
const screenState = get(screenStore)
|
||||||
screenId = screenId || screenState.selectedScreenId
|
screenId = screenId || screenState.selectedScreenId
|
||||||
}
|
}
|
||||||
|
@ -471,7 +546,6 @@ export class ComponentStore extends BudiStore {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const patchScreen = screen => {
|
const patchScreen = screen => {
|
||||||
// findComponent looks in the tree not comp.settings[0]
|
|
||||||
let component = findComponent(screen.props, componentId)
|
let component = findComponent(screen.props, componentId)
|
||||||
if (!component) {
|
if (!component) {
|
||||||
return false
|
return false
|
||||||
|
@ -480,7 +554,7 @@ export class ComponentStore extends BudiStore {
|
||||||
// Mutates the fetched component with updates
|
// Mutates the fetched component with updates
|
||||||
const patchResult = patchFn(component, screen)
|
const patchResult = patchFn(component, screen)
|
||||||
|
|
||||||
// Mutates the component with any required settings updates
|
// Post processing
|
||||||
const migrated = this.migrateSettings(component)
|
const migrated = this.migrateSettings(component)
|
||||||
|
|
||||||
// Returning an explicit false signifies that we should skip this
|
// Returning an explicit false signifies that we should skip this
|
||||||
|
|
|
@ -23,6 +23,7 @@ import {
|
||||||
DB_TYPE_EXTERNAL,
|
DB_TYPE_EXTERNAL,
|
||||||
DEFAULT_BB_DATASOURCE_ID,
|
DEFAULT_BB_DATASOURCE_ID,
|
||||||
} from "constants/backend"
|
} from "constants/backend"
|
||||||
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
|
|
||||||
// Could move to fixtures
|
// Could move to fixtures
|
||||||
const COMP_PREFIX = "@budibase/standard-components"
|
const COMP_PREFIX = "@budibase/standard-components"
|
||||||
|
@ -360,8 +361,30 @@ describe("Component store", () => {
|
||||||
resourceId: internalTableDoc._id,
|
resourceId: internalTableDoc._id,
|
||||||
type: "table",
|
type: "table",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return comp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it("enrichEmptySettings - initialise cards blocks with correct fields", async ctx => {
|
||||||
|
const comp = enrichSettingsDS("cardsblock", ctx)
|
||||||
|
const expectBinding = (setting, ...parts) => {
|
||||||
|
expect(comp[setting]).toStrictEqual(
|
||||||
|
`{{ ${safe(`${comp._id}-repeater`)}.${parts.map(safe).join(".")} }}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
expectBinding("cardTitle", internalTableDoc.schema.MediaTitle.name)
|
||||||
|
expectBinding("cardSubtitle", internalTableDoc.schema.MediaVersion.name)
|
||||||
|
expectBinding(
|
||||||
|
"cardDescription",
|
||||||
|
internalTableDoc.schema.MediaDescription.name
|
||||||
|
)
|
||||||
|
expectBinding(
|
||||||
|
"cardImageURL",
|
||||||
|
internalTableDoc.schema.MediaImage.name,
|
||||||
|
"url"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
it("enrichEmptySettings - set default datasource for 'table' setting type", async ctx => {
|
it("enrichEmptySettings - set default datasource for 'table' setting type", async ctx => {
|
||||||
enrichSettingsDS("formblock", ctx)
|
enrichSettingsDS("formblock", ctx)
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
DB_TYPE_EXTERNAL,
|
DB_TYPE_EXTERNAL,
|
||||||
DEFAULT_BB_DATASOURCE_ID,
|
DEFAULT_BB_DATASOURCE_ID,
|
||||||
} from "constants/backend"
|
} from "constants/backend"
|
||||||
|
import { FieldType } from "@budibase/types"
|
||||||
|
|
||||||
const getDocId = () => {
|
const getDocId = () => {
|
||||||
return v4().replace(/-/g, "")
|
return v4().replace(/-/g, "")
|
||||||
|
@ -45,6 +46,52 @@ export const COMPONENT_DEFINITIONS = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
cardsblock: {
|
||||||
|
block: true,
|
||||||
|
name: "Cards Block",
|
||||||
|
settings: [
|
||||||
|
{
|
||||||
|
type: "dataSource",
|
||||||
|
label: "Data",
|
||||||
|
key: "dataSource",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: true,
|
||||||
|
name: "Cards",
|
||||||
|
settings: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
key: "cardTitle",
|
||||||
|
label: "Title",
|
||||||
|
nested: true,
|
||||||
|
resetOn: "dataSource",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
key: "cardSubtitle",
|
||||||
|
label: "Subtitle",
|
||||||
|
nested: true,
|
||||||
|
resetOn: "dataSource",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
key: "cardDescription",
|
||||||
|
label: "Description",
|
||||||
|
nested: true,
|
||||||
|
resetOn: "dataSource",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
key: "cardImageURL",
|
||||||
|
label: "Image URL",
|
||||||
|
nested: true,
|
||||||
|
resetOn: "dataSource",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
container: {
|
container: {
|
||||||
name: "Container",
|
name: "Container",
|
||||||
},
|
},
|
||||||
|
@ -262,14 +309,23 @@ export const internalTableDoc = {
|
||||||
name: "Media",
|
name: "Media",
|
||||||
sourceId: BUDIBASE_INTERNAL_DB_ID,
|
sourceId: BUDIBASE_INTERNAL_DB_ID,
|
||||||
sourceType: DB_TYPE_INTERNAL,
|
sourceType: DB_TYPE_INTERNAL,
|
||||||
|
primaryDisplay: "MediaTitle",
|
||||||
schema: {
|
schema: {
|
||||||
MediaTitle: {
|
MediaTitle: {
|
||||||
name: "MediaTitle",
|
name: "MediaTitle",
|
||||||
type: "string",
|
type: FieldType.STRING,
|
||||||
},
|
},
|
||||||
MediaVersion: {
|
MediaVersion: {
|
||||||
name: "MediaVersion",
|
name: "MediaVersion",
|
||||||
type: "string",
|
type: FieldType.STRING,
|
||||||
|
},
|
||||||
|
MediaDescription: {
|
||||||
|
name: "MediaDescription",
|
||||||
|
type: FieldType.LONGFORM,
|
||||||
|
},
|
||||||
|
MediaImage: {
|
||||||
|
name: "MediaImage",
|
||||||
|
type: FieldType.ATTACHMENT_SINGLE,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -6243,27 +6243,28 @@
|
||||||
"key": "cardTitle",
|
"key": "cardTitle",
|
||||||
"label": "Title",
|
"label": "Title",
|
||||||
"nested": true,
|
"nested": true,
|
||||||
"defaultValue": "Title"
|
"resetOn": "dataSource"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"key": "cardSubtitle",
|
"key": "cardSubtitle",
|
||||||
"label": "Subtitle",
|
"label": "Subtitle",
|
||||||
"nested": true,
|
"nested": true,
|
||||||
"defaultValue": "Subtitle"
|
"resetOn": "dataSource"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"key": "cardDescription",
|
"key": "cardDescription",
|
||||||
"label": "Description",
|
"label": "Description",
|
||||||
"nested": true,
|
"nested": true,
|
||||||
"defaultValue": "Description"
|
"resetOn": "dataSource"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"key": "cardImageURL",
|
"key": "cardImageURL",
|
||||||
"label": "Image URL",
|
"label": "Image URL",
|
||||||
"nested": true
|
"nested": true,
|
||||||
|
"resetOn": "dataSource"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
|
Loading…
Reference in New Issue