diff --git a/packages/builder/package.json b/packages/builder/package.json
index e4b9040088..8645f285af 100644
--- a/packages/builder/package.json
+++ b/packages/builder/package.json
@@ -112,7 +112,7 @@
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-url": "^2.2.2",
"start-server-and-test": "^1.11.0",
- "svelte": "^3.24.1",
+ "svelte": "^3.29.0",
"svelte-jester": "^1.0.6"
},
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
diff --git a/packages/builder/src/builderStore/getNewComponentName.js b/packages/builder/src/builderStore/getNewComponentName.js
index b3ddc4e953..9ab8ef16dc 100644
--- a/packages/builder/src/builderStore/getNewComponentName.js
+++ b/packages/builder/src/builderStore/getNewComponentName.js
@@ -8,8 +8,9 @@ export default function(component, state) {
const findMatches = props => {
walkProps(props, c => {
- if ((c._instanceName || "").startsWith(capitalised)) {
- matchingComponents.push(c._instanceName)
+ const thisInstanceName = get_capitalised_name(c._instanceName)
+ if ((thisInstanceName || "").startsWith(capitalised)) {
+ matchingComponents.push(thisInstanceName)
}
})
}
diff --git a/packages/builder/src/builderStore/store/index.js b/packages/builder/src/builderStore/store/index.js
index 70b88eb778..6941c74b22 100644
--- a/packages/builder/src/builderStore/store/index.js
+++ b/packages/builder/src/builderStore/store/index.js
@@ -124,17 +124,18 @@ const saveScreen = store => screen => {
}
const _saveScreen = async (store, s, screen) => {
- const currentPageScreens = s.pages[s.currentPageName]._screens
+ const pageName = s.currentPageName || "main"
+ const currentPageScreens = s.pages[pageName]._screens
await api
- .post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen)
+ .post(`/_builder/api/${s.appId}/pages/${pageName}/screen`, screen)
.then(() => {
if (currentPageScreens.includes(screen)) return
const screens = [...currentPageScreens, screen]
store.update(innerState => {
- innerState.pages[s.currentPageName]._screens = screens
+ innerState.pages[pageName]._screens = screens
innerState.screens = screens
innerState.currentPreviewItem = screen
innerState.allScreens = [...innerState.allScreens, screen]
@@ -153,27 +154,17 @@ const _saveScreen = async (store, s, screen) => {
return s
}
-const createScreen = store => (screenName, route, layoutComponentName) => {
+const createScreen = store => async screen => {
+ let savePromise
store.update(state => {
- const rootComponent = state.components[layoutComponentName]
-
- const newScreen = {
- description: "",
- url: "",
- _css: "",
- props: createProps(rootComponent).props,
- }
- newScreen.route = route
- newScreen.name = newScreen.props._id
- newScreen.props._instanceName = screenName || ""
- state.currentPreviewItem = newScreen
- state.currentComponentInfo = newScreen.props
+ state.currentPreviewItem = screen
+ state.currentComponentInfo = screen.props
state.currentFrontEndType = "screen"
-
- _saveScreen(store, state, newScreen)
-
+ savePromise = _saveScreen(store, state, screen)
+ regenerateCssForCurrentScreen(state)
return state
})
+ await savePromise
}
const setCurrentScreen = store => screenName => {
diff --git a/packages/builder/src/builderStore/store/screenTemplates/createFromScratchScreen.js b/packages/builder/src/builderStore/store/screenTemplates/createFromScratchScreen.js
new file mode 100644
index 0000000000..a8ab27df3d
--- /dev/null
+++ b/packages/builder/src/builderStore/store/screenTemplates/createFromScratchScreen.js
@@ -0,0 +1,22 @@
+export default {
+ name: `Create from scratch`,
+ create: () => createScreen(),
+}
+
+const createScreen = () => ({
+ props: {
+ _id: "",
+ _component: "@budibase/standard-components/container",
+ _styles: {
+ normal: {},
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ type: "div",
+ _children: [],
+ _instanceName: "",
+ },
+ route: "",
+ name: "screen-id",
+})
diff --git a/packages/builder/src/builderStore/store/screenTemplates/emptyNewRecordScreen.js b/packages/builder/src/builderStore/store/screenTemplates/emptyNewRecordScreen.js
new file mode 100644
index 0000000000..db6910809f
--- /dev/null
+++ b/packages/builder/src/builderStore/store/screenTemplates/emptyNewRecordScreen.js
@@ -0,0 +1,22 @@
+export default {
+ name: `New Row (Empty)`,
+ create: () => createScreen(),
+}
+
+const createScreen = () => ({
+ props: {
+ _id: "",
+ _component: "@budibase/standard-components/newrow",
+ _styles: {
+ normal: {},
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ _children: [],
+ _instanceName: "",
+ model: "",
+ },
+ route: "",
+ name: "screen-id",
+})
diff --git a/packages/builder/src/builderStore/store/screenTemplates/emptyRecordDetailScreen.js b/packages/builder/src/builderStore/store/screenTemplates/emptyRecordDetailScreen.js
new file mode 100644
index 0000000000..5c0b31a2df
--- /dev/null
+++ b/packages/builder/src/builderStore/store/screenTemplates/emptyRecordDetailScreen.js
@@ -0,0 +1,22 @@
+export default {
+ name: `Row Detail (Empty)`,
+ create: () => createScreen(),
+}
+
+const createScreen = () => ({
+ props: {
+ _id: "",
+ _component: "@budibase/standard-components/rowdetail",
+ _styles: {
+ normal: {},
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ _children: [],
+ _instanceName: "",
+ model: "",
+ },
+ route: "",
+ name: "screen-id",
+})
diff --git a/packages/builder/src/builderStore/store/screenTemplates/index.js b/packages/builder/src/builderStore/store/screenTemplates/index.js
new file mode 100644
index 0000000000..55a93c6e4d
--- /dev/null
+++ b/packages/builder/src/builderStore/store/screenTemplates/index.js
@@ -0,0 +1,35 @@
+import newRecordScreen from "./newRecordScreen"
+import recordDetailScreen from "./recordDetailScreen"
+import recordListScreen from "./recordListScreen"
+import emptyNewRecordScreen from "./emptyNewRecordScreen"
+import createFromScratchScreen from "./createFromScratchScreen"
+import emptyRecordDetailScreen from "./emptyRecordDetailScreen"
+import { generateNewIdsForComponent } from "../../storeUtils"
+import { uuid } from "builderStore/uuid"
+
+const allTemplates = models => [
+ createFromScratchScreen,
+ ...newRecordScreen(models),
+ ...recordDetailScreen(models),
+ ...recordListScreen(models),
+ emptyNewRecordScreen,
+ emptyRecordDetailScreen,
+]
+
+// allows us to apply common behaviour to all create() functions
+const createTemplateOverride = (frontendState, create) => () => {
+ const screen = create()
+ for (let component of screen.props._children) {
+ generateNewIdsForComponent(component, frontendState, false)
+ }
+ screen.props._id = uuid()
+ screen.name = screen.props._id
+ screen.route = screen.route.toLowerCase()
+ return screen
+}
+
+export default (frontendState, models) =>
+ allTemplates(models).map(template => ({
+ ...template,
+ create: createTemplateOverride(frontendState, template.create),
+ }))
diff --git a/packages/builder/src/builderStore/store/screenTemplates/newRecordScreen.js b/packages/builder/src/builderStore/store/screenTemplates/newRecordScreen.js
new file mode 100644
index 0000000000..bbe9b0d911
--- /dev/null
+++ b/packages/builder/src/builderStore/store/screenTemplates/newRecordScreen.js
@@ -0,0 +1,135 @@
+export default function(models) {
+ return models.map(model => {
+ const fields = Object.keys(model.schema)
+ const heading = fields.length > 0 ? `{{ data.${fields[0]} }}` : "Add Row"
+ return {
+ name: `${model.name} - New`,
+ create: () => createScreen(model, heading),
+ id: NEW_RECORD_TEMPLATE,
+ }
+ })
+}
+
+export const NEW_RECORD_TEMPLATE = "NEW_RECORD_TEMPLATE"
+
+const createScreen = (model, heading) => ({
+ props: {
+ _id: "",
+ _component: "@budibase/standard-components/newrow",
+ _styles: {
+ normal: {},
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ model: model._id,
+ _children: [
+ {
+ _id: "",
+ _component: "@budibase/standard-components/heading",
+ _styles: {
+ normal: {},
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ _code: "",
+ className: "",
+ text: heading,
+ type: "h1",
+ _instanceName: "Heading 1",
+ _children: [],
+ },
+ {
+ _id: "",
+ _component: "@budibase/standard-components/dataform",
+ _styles: {
+ normal: {},
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ _code: "",
+ _instanceName: `${model.name} Form`,
+ _children: [],
+ },
+ {
+ _id: "",
+ _component: "@budibase/standard-components/container",
+ _styles: {
+ normal: {
+ display: "flex",
+ "flex-direction": "row",
+ "align-items": "center",
+ "justify-content": "flex-end",
+ },
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ _code: "",
+ className: "",
+ onLoad: [],
+ type: "div",
+ _instanceName: "Buttons Container",
+ _children: [
+ {
+ _id: "",
+ _component: "@budibase/standard-components/button",
+ _styles: {
+ normal: {
+ "margin-right": "20px",
+ },
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ _code: "",
+ text: "Back",
+ className: "",
+ disabled: false,
+ onClick: [
+ {
+ parameters: {
+ url: `/${model.name.toLowerCase()}`,
+ },
+ "##eventHandlerType": "Navigate To",
+ },
+ ],
+ _instanceName: "Back Button",
+ _children: [],
+ },
+ {
+ _id: "",
+ _component: "@budibase/standard-components/button",
+ _styles: {
+ normal: {},
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ _code: "",
+ text: "Save",
+ className: "",
+ disabled: false,
+ onClick: [
+ {
+ parameters: {
+ contextPath: "data",
+ modelId: model._id,
+ },
+ "##eventHandlerType": "Save Record",
+ },
+ ],
+ _instanceName: "Save Button",
+ _children: [],
+ },
+ ],
+ },
+ ],
+ _instanceName: `${model.name} - New`,
+ _code: "",
+ },
+ route: `/${model.name.toLowerCase()}/new`,
+ name: "",
+})
diff --git a/packages/builder/src/builderStore/store/screenTemplates/recordDetailScreen.js b/packages/builder/src/builderStore/store/screenTemplates/recordDetailScreen.js
new file mode 100644
index 0000000000..fb4c3230ee
--- /dev/null
+++ b/packages/builder/src/builderStore/store/screenTemplates/recordDetailScreen.js
@@ -0,0 +1,135 @@
+export default function(models) {
+ return models.map(model => {
+ const fields = Object.keys(model.schema)
+ const heading = fields.length > 0 ? `{{ data.${fields[0]} }}` : "Detail"
+ return {
+ name: `${model.name} - Detail`,
+ create: () => createScreen(model, heading),
+ id: RECORD_DETAIL_TEMPLATE,
+ }
+ })
+}
+
+export const RECORD_DETAIL_TEMPLATE = "RECORD_DETAIL_TEMPLATE"
+
+const createScreen = (model, heading) => ({
+ props: {
+ _id: "",
+ _component: "@budibase/standard-components/rowdetail",
+ _styles: {
+ normal: {},
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ model: model._id,
+ _children: [
+ {
+ _id: "",
+ _component: "@budibase/standard-components/heading",
+ _styles: {
+ normal: {},
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ _code: "",
+ className: "",
+ text: heading,
+ type: "h1",
+ _instanceName: "Heading 1",
+ _children: [],
+ },
+ {
+ _id: "",
+ _component: "@budibase/standard-components/dataform",
+ _styles: {
+ normal: {},
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ _code: "",
+ _instanceName: `${model.name} Form`,
+ _children: [],
+ },
+ {
+ _id: "",
+ _component: "@budibase/standard-components/container",
+ _styles: {
+ normal: {
+ display: "flex",
+ "flex-direction": "row",
+ "align-items": "center",
+ "justify-content": "flex-end",
+ },
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ _code: "",
+ className: "",
+ onLoad: [],
+ type: "div",
+ _instanceName: "Buttons Container",
+ _children: [
+ {
+ _id: "",
+ _component: "@budibase/standard-components/button",
+ _styles: {
+ normal: {
+ "margin-right": "20px",
+ },
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ _code: "",
+ text: "Back",
+ className: "",
+ disabled: false,
+ onClick: [
+ {
+ parameters: {
+ url: `/${model.name.toLowerCase()}`,
+ },
+ "##eventHandlerType": "Navigate To",
+ },
+ ],
+ _instanceName: "Back Button",
+ _children: [],
+ },
+ {
+ _id: "",
+ _component: "@budibase/standard-components/button",
+ _styles: {
+ normal: {},
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ _code: "",
+ text: "Save",
+ className: "",
+ disabled: false,
+ onClick: [
+ {
+ parameters: {
+ contextPath: "data",
+ modelId: model._id,
+ },
+ "##eventHandlerType": "Save Record",
+ },
+ ],
+ _instanceName: "Save Button",
+ _children: [],
+ },
+ ],
+ },
+ ],
+ _instanceName: `${model.name} - Detail`,
+ _code: "",
+ },
+ route: `/${model.name.toLowerCase()}/:id`,
+ name: "",
+})
diff --git a/packages/builder/src/builderStore/store/screenTemplates/recordListScreen.js b/packages/builder/src/builderStore/store/screenTemplates/recordListScreen.js
new file mode 100644
index 0000000000..32f4d0696b
--- /dev/null
+++ b/packages/builder/src/builderStore/store/screenTemplates/recordListScreen.js
@@ -0,0 +1,118 @@
+export default function(models) {
+ return models.map(model => {
+ return {
+ name: `${model.name} - List`,
+ create: () => createScreen(model),
+ id: RECORD_LIST_TEMPLATE,
+ }
+ })
+}
+
+export const RECORD_LIST_TEMPLATE = "RECORD_LIST_TEMPLATE"
+
+const createScreen = model => ({
+ props: {
+ _id: "",
+ _component: "@budibase/standard-components/container",
+ _styles: {
+ normal: {},
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ type: "div",
+ _children: [
+ {
+ _id: "",
+ _component: "@budibase/standard-components/container",
+ _styles: {
+ normal: {
+ display: "flex",
+ "flex-direction": "row",
+ "justify-content": "space-between",
+ "align-items": "center",
+ },
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ _code: "",
+ className: "",
+ onLoad: [],
+ type: "div",
+ _instanceName: "Header",
+ _children: [
+ {
+ _id: "",
+ _component: "@budibase/standard-components/heading",
+ _styles: {
+ normal: {},
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ _code: "",
+ className: "",
+ text: `${model.name} List`,
+ type: "h1",
+ _instanceName: "Heading 1",
+ _children: [],
+ },
+ {
+ _id: "",
+ _component: "@budibase/standard-components/button",
+ _styles: {
+ normal: {},
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ _code: "",
+ text: "Create New",
+ className: "",
+ disabled: false,
+ onClick: [
+ {
+ parameters: {
+ url: `/${model.name}/new`,
+ },
+ "##eventHandlerType": "Navigate To",
+ },
+ ],
+ _instanceName: "Create New Button",
+ _children: [],
+ },
+ ],
+ },
+ {
+ _id: "",
+ _component: "@budibase/standard-components/datatable",
+ _styles: {
+ normal: {},
+ hover: {},
+ active: {},
+ selected: {},
+ },
+ _code: "",
+ datasource: {
+ label: "Deals",
+ name: `all_${model._id}`,
+ modelId: model._id,
+ isModel: true,
+ },
+ stripeColor: "",
+ borderColor: "",
+ backgroundColor: "",
+ color: "",
+ _instanceName: `${model.name} Table`,
+ _children: [],
+ },
+ ],
+ _instanceName: `${model.name} - List`,
+ _code: "",
+ className: "",
+ onLoad: [],
+ },
+ route: `/${model.name.toLowerCase()}`,
+ name: "",
+})
diff --git a/packages/builder/src/builderStore/storeUtils.js b/packages/builder/src/builderStore/storeUtils.js
index 2efffc9d4c..aeff3f2528 100644
--- a/packages/builder/src/builderStore/storeUtils.js
+++ b/packages/builder/src/builderStore/storeUtils.js
@@ -85,10 +85,10 @@ export const regenerateCssForCurrentScreen = state => {
return state
}
-export const generateNewIdsForComponent = (c, state) =>
+export const generateNewIdsForComponent = (c, state, changeName = true) =>
walkProps(c, p => {
p._id = uuid()
- p._instanceName = getNewComponentName(p._component, state)
+ if (changeName) p._instanceName = getNewComponentName(p._component, state)
})
export const getComponentDefinition = (state, name) =>
diff --git a/packages/builder/src/components/backend/ModelNavigator/modals/CreateTableModal.svelte b/packages/builder/src/components/backend/ModelNavigator/modals/CreateTableModal.svelte
index 1cc0e8894c..c4b62d6cec 100644
--- a/packages/builder/src/components/backend/ModelNavigator/modals/CreateTableModal.svelte
+++ b/packages/builder/src/components/backend/ModelNavigator/modals/CreateTableModal.svelte
@@ -1,11 +1,21 @@
diff --git a/packages/builder/src/components/settings/tabs/APIKeys.svelte b/packages/builder/src/components/settings/tabs/APIKeys.svelte
index 8a3c9fc710..8e569596a4 100644
--- a/packages/builder/src/components/settings/tabs/APIKeys.svelte
+++ b/packages/builder/src/components/settings/tabs/APIKeys.svelte
@@ -1,6 +1,7 @@
+
+
+ {#if idFields.length === 0}
+
+ Update record can only be used within a component that provides data, such
+ as a List
+
+ {:else}
+
+
+ {/if}
+
+ {#if parameters.contextPath}
+
+ {/if}
+
+
+
+
diff --git a/packages/builder/src/components/userInterface/EventsEditor/actions/index.js b/packages/builder/src/components/userInterface/EventsEditor/actions/index.js
index e1a85e307e..85f90d124b 100644
--- a/packages/builder/src/components/userInterface/EventsEditor/actions/index.js
+++ b/packages/builder/src/components/userInterface/EventsEditor/actions/index.js
@@ -1,6 +1,5 @@
import NavigateTo from "./NavigateTo.svelte"
-import UpdateRecord from "./UpdateRecord.svelte"
-import CreateRecord from "./CreateRecord.svelte"
+import SaveRecord from "./SaveRecord.svelte"
// defines what actions are available, when adding a new one
// the component is the setup panel for the action
@@ -9,15 +8,11 @@ import CreateRecord from "./CreateRecord.svelte"
export default [
{
- name: "Create Record",
- component: CreateRecord,
+ name: "Save Record",
+ component: SaveRecord,
},
{
name: "Navigate To",
component: NavigateTo,
},
- {
- name: "Update Record",
- component: UpdateRecord,
- },
]
diff --git a/packages/builder/src/components/userInterface/NewScreenModal.svelte b/packages/builder/src/components/userInterface/NewScreenModal.svelte
index d38d4bbb7b..0a9bc81987 100644
--- a/packages/builder/src/components/userInterface/NewScreenModal.svelte
+++ b/packages/builder/src/components/userInterface/NewScreenModal.svelte
@@ -1,27 +1,49 @@
+
+
+
+
-
+
diff --git a/packages/builder/src/components/userInterface/SettingsView.svelte b/packages/builder/src/components/userInterface/SettingsView.svelte
index 159093fe8e..0460e15ad6 100644
--- a/packages/builder/src/components/userInterface/SettingsView.svelte
+++ b/packages/builder/src/components/userInterface/SettingsView.svelte
@@ -11,6 +11,7 @@
export let componentDefinition = {}
export let componentInstance = {}
export let onChange = () => {}
+ export let onScreenPropChange = () => {}
export let displayNameField = false
export let screenOrPageInstance
@@ -91,7 +92,7 @@
label={def.label}
key={def.key}
value={screenOrPageInstance[def.key]}
- {onChange}
+ onChange={onScreenPropChange}
props={{ ...excludeProps(def, ['control', 'label']) }} />
{/each}
diff --git a/packages/builder/src/components/userInterface/propertyCategories.js b/packages/builder/src/components/userInterface/propertyCategories.js
index 9c622a383a..bae44df7f5 100644
--- a/packages/builder/src/components/userInterface/propertyCategories.js
+++ b/packages/builder/src/components/userInterface/propertyCategories.js
@@ -361,19 +361,18 @@ export const typography = [
label: "Font",
key: "font-family",
control: OptionSelect,
- defaultValue: "initial",
+ defaultValue: "Arial",
options: [
- "initial",
"Arial",
"Arial Black",
"Cursive",
"Courier",
"Comic Sans MS",
"Helvetica",
+ "Helvetica Neue",
"Impact",
"Inter",
"Lucida Sans Unicode",
- "Open Sans",
"Roboto",
"Roboto Mono",
"Times New Roman",
@@ -467,9 +466,9 @@ export const background = [
label: "Gradient",
key: "background-image",
control: OptionSelect,
- defaultValue: "None",
+ defaultValue: "",
options: [
- { label: "None", value: "None" },
+ { label: "Select option", value: "" },
{
label: "Warm Flame",
value: "linear-gradient(45deg, #ff9a9e 0%, #fad0c4 99%, #fad0c4 100%);",
@@ -518,9 +517,9 @@ export const background = [
},
{
label: "Image",
- key: "background-image",
+ key: "background",
control: Input,
- placeholder: "Src",
+ placeholder: "url",
},
]
@@ -665,7 +664,7 @@ export const transitions = [
control: OptionSelect,
textAlign: "center",
placeholder: "sec",
- options: ["0.2ms", "0.4ms", "0.8ms", "1s", "2s", "4s"],
+ options: ["0.4s", "0.6s", "0.8s", "1s", "2s", "4s"],
},
{
label: "Ease",
diff --git a/packages/builder/src/components/userInterface/temporaryPanelStructure.js b/packages/builder/src/components/userInterface/temporaryPanelStructure.js
index 296ae26606..3f853efcfb 100644
--- a/packages/builder/src/components/userInterface/temporaryPanelStructure.js
+++ b/packages/builder/src/components/userInterface/temporaryPanelStructure.js
@@ -583,23 +583,7 @@ export default {
icon: "ri-file-edit-line",
properties: {
design: { ...all },
- settings: [
- {
- label: "Table",
- key: "model",
- control: ModelSelect,
- },
- {
- label: "Title",
- key: "title",
- control: Input,
- },
- {
- label: "Button Text",
- key: "buttonText",
- control: Input,
- },
- ],
+ settings: [],
},
},
{
@@ -608,23 +592,7 @@ export default {
icon: "ri-file-edit-line",
properties: {
design: { ...all },
- settings: [
- {
- label: "Table",
- key: "model",
- control: ModelSelect,
- },
- {
- label: "Title",
- key: "title",
- control: Input,
- },
- {
- label: "Button Text",
- key: "buttonText",
- control: Input,
- },
- ],
+ settings: [],
},
},
],
@@ -1125,8 +1093,8 @@ export default {
// children: [],
// },
{
- name: "Record Detail",
- _component: "@budibase/standard-components/recorddetail",
+ name: "Row Detail",
+ _component: "@budibase/standard-components/rowdetail",
description:
"Loads a record, using an id from the URL, which can be used with {{ context }}, in children",
icon: "ri-profile-line",
@@ -1136,6 +1104,18 @@ export default {
},
children: [],
},
+ {
+ name: "New Row",
+ _component: "@budibase/standard-components/newrow",
+ description:
+ "Sets up a new record for creation, which can be used with {{ context }}, in children",
+ icon: "ri-profile-line",
+ properties: {
+ design: { ...all },
+ settings: [{ label: "Table", key: "model", control: ModelSelect }],
+ },
+ children: [],
+ },
// {
// name: "Map",
// _component: "@budibase/standard-components/datamap",
diff --git a/packages/builder/yarn.lock b/packages/builder/yarn.lock
index 94a7501bce..dfeebc7214 100644
--- a/packages/builder/yarn.lock
+++ b/packages/builder/yarn.lock
@@ -5850,10 +5850,10 @@ svelte-portal@^1.0.0:
resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-1.0.0.tgz#36a47c5578b1a4d9b4dc60fa32a904640ec4cdd3"
integrity sha512-nHf+DS/jZ6jjnZSleBMSaZua9JlG5rZv9lOGKgJuaZStfevtjIlUJrkLc3vbV8QdBvPPVmvcjTlazAzfKu0v3Q==
-svelte@^3.24.1:
- version "3.25.1"
- resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.25.1.tgz#218def1243fea5a97af6eb60f5e232315bb57ac4"
- integrity sha512-IbrVKTmuR0BvDw4ii8/gBNy8REu7nWTRy9uhUz+Yuae5lIjWgSGwKlWtJGC2Vg95s+UnXPqDu0Kk/sUwe0t2GQ==
+svelte@^3.29.0:
+ version "3.29.0"
+ resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.29.0.tgz#80acac4254341ad8f3301e5ef03f4127ea967d96"
+ integrity sha512-f+A65eyOQ5ujETLy+igNXtlr6AEjAQLYd1yJE1VwNiXMQO5Z/Vmiy3rL+zblV/9jd7rtTTWqO1IcuXsP2Qv0OA==
symbol-observable@^1.1.0:
version "1.2.0"
diff --git a/packages/client/src/api/index.js b/packages/client/src/api/index.js
index 34441d563b..f66cfcf69b 100644
--- a/packages/client/src/api/index.js
+++ b/packages/client/src/api/index.js
@@ -52,14 +52,14 @@ const apiOpts = {
delete: del,
}
-const createRecord = async params =>
+const saveRecord = async (params, state) =>
await post({
url: `/api/${params.modelId}/records`,
- body: makeRecordRequestBody(params),
+ body: makeRecordRequestBody(params, state),
})
-const updateRecord = async params => {
- const record = makeRecordRequestBody(params)
+const updateRecord = async (params, state) => {
+ const record = makeRecordRequestBody(params, state)
record._id = params._id
await patch({
url: `/api/${params.modelId}/records/${params._id}`,
@@ -67,8 +67,14 @@ const updateRecord = async params => {
})
}
-const makeRecordRequestBody = parameters => {
- const body = {}
+const makeRecordRequestBody = (parameters, state) => {
+ // start with the record thats currently in context
+ const body = { ...(state.data || {}) }
+
+ // dont send the model
+ if (body._model) delete body._model
+
+ // then override with supplied parameters
for (let fieldName in parameters.fields) {
const field = parameters.fields[fieldName]
@@ -95,6 +101,6 @@ const makeRecordRequestBody = parameters => {
export default {
authenticate: authenticate(apiOpts),
- createRecord,
+ saveRecord,
updateRecord,
}
diff --git a/packages/client/src/state/eventHandlers.js b/packages/client/src/state/eventHandlers.js
index 05d8ef2fa3..fa80605b42 100644
--- a/packages/client/src/state/eventHandlers.js
+++ b/packages/client/src/state/eventHandlers.js
@@ -6,8 +6,8 @@ export const EVENT_TYPE_MEMBER_NAME = "##eventHandlerType"
export const eventHandlers = routeTo => {
const handlers = {
"Navigate To": param => routeTo(param && param.url),
- "Create Record": api.createRecord,
"Update Record": api.updateRecord,
+ "Save Record": api.saveRecord,
"Trigger Workflow": api.triggerWorkflow,
}
@@ -19,7 +19,7 @@ export const eventHandlers = routeTo => {
const handler = handlers[action[EVENT_TYPE_MEMBER_NAME]]
const parameters = createParameters(action.parameters, state)
if (handler) {
- await handler(parameters)
+ await handler(parameters, state)
}
}
}
diff --git a/packages/client/src/state/store.js b/packages/client/src/state/store.js
index 6464589b03..cf284ec08e 100644
--- a/packages/client/src/state/store.js
+++ b/packages/client/src/state/store.js
@@ -95,7 +95,7 @@ const getState = contextStoreKey =>
contextStoreKey ? contextStores[contextStoreKey].state : rootState
const getStore = contextStoreKey =>
- contextStoreKey ? contextStores[contextStoreKey] : rootStore
+ contextStoreKey ? contextStores[contextStoreKey].store : rootStore
export default {
subscribe,
diff --git a/packages/server/src/api/controllers/static.js b/packages/server/src/api/controllers/static.js
index f14b794210..5aaa9ab125 100644
--- a/packages/server/src/api/controllers/static.js
+++ b/packages/server/src/api/controllers/static.js
@@ -136,7 +136,7 @@ exports.performLocalFileProcessing = async function(ctx) {
}
exports.serveApp = async function(ctx) {
- const mainOrAuth = ctx.isAuthenticated ? "main" : "unauthenticated"
+ const mainOrAuth = ctx.auth.authenticated ? "main" : "unauthenticated"
// default to homedir
const appPath = resolve(
@@ -154,7 +154,7 @@ exports.serveApp = async function(ctx) {
// only set the appId cookie for /appId .. we COULD check for valid appIds
// but would like to avoid that DB hit
const looksLikeAppId = /^(app_)?[0-9a-f]{32}$/.test(appId)
- if (looksLikeAppId && !ctx.isAuthenticated) {
+ if (looksLikeAppId && !ctx.auth.authenticated) {
const anonUser = {
userId: "ANON",
accessLevelId: ANON_LEVEL_ID,
@@ -200,7 +200,7 @@ exports.serveAttachment = async function(ctx) {
exports.serveAppAsset = async function(ctx) {
// default to homedir
- const mainOrAuth = ctx.isAuthenticated ? "main" : "unauthenticated"
+ const mainOrAuth = ctx.auth.authenticated ? "main" : "unauthenticated"
const appPath = resolve(
budibaseAppsDir(),
diff --git a/packages/server/src/app.js b/packages/server/src/app.js
index 7560c9cfa4..4157534365 100644
--- a/packages/server/src/app.js
+++ b/packages/server/src/app.js
@@ -24,6 +24,7 @@ app.use(
)
app.context.eventEmitter = eventEmitter
+app.context.auth = {}
// api routes
app.use(api.routes())
diff --git a/packages/server/src/db/linkedRecords/index.js b/packages/server/src/db/linkedRecords/index.js
index 115553696c..ba93f3d2e8 100644
--- a/packages/server/src/db/linkedRecords/index.js
+++ b/packages/server/src/db/linkedRecords/index.js
@@ -1,5 +1,6 @@
const LinkController = require("./LinkController")
const { IncludeDocs, getLinkDocuments, createLinkView } = require("./linkUtils")
+const _ = require("lodash")
/**
* This functionality makes sure that when records with links are created, updated or deleted they are processed
@@ -88,23 +89,23 @@ exports.attachLinkInfo = async (instanceId, records) => {
records = [records]
wasArray = false
}
+ let modelIds = [...new Set(records.map(el => el.modelId))]
// start by getting all the link values for performance reasons
- let responses = await Promise.all(
- records.map(record =>
- getLinkDocuments({
- instanceId,
- modelId: record.modelId,
- recordId: record._id,
- includeDocs: IncludeDocs.EXCLUDE,
- })
+ let responses = _.flatten(
+ await Promise.all(
+ modelIds.map(modelId =>
+ getLinkDocuments({
+ instanceId,
+ modelId: modelId,
+ includeDocs: IncludeDocs.EXCLUDE,
+ })
+ )
)
)
- // can just use an index to access responses, order maintained
- let index = 0
// now iterate through the records and all field information
for (let record of records) {
// get all links for record, ignore fieldName for now
- const linkVals = responses[index++]
+ const linkVals = responses.filter(el => el.thisId === record._id)
for (let linkVal of linkVals) {
// work out which link pertains to this record
if (!(record[linkVal.fieldName] instanceof Array)) {
diff --git a/packages/server/src/db/linkedRecords/linkUtils.js b/packages/server/src/db/linkedRecords/linkUtils.js
index 7680a2603f..f5fe1f6786 100644
--- a/packages/server/src/db/linkedRecords/linkUtils.js
+++ b/packages/server/src/db/linkedRecords/linkUtils.js
@@ -27,10 +27,12 @@ exports.createLinkView = async instanceId => {
let doc2 = doc.doc2
emit([doc1.modelId, doc1.recordId], {
id: doc2.recordId,
+ thisId: doc1.recordId,
fieldName: doc1.fieldName,
})
emit([doc2.modelId, doc2.recordId], {
id: doc1.recordId,
+ thisId: doc2.recordId,
fieldName: doc2.fieldName,
})
}
diff --git a/packages/server/src/middleware/authenticated.js b/packages/server/src/middleware/authenticated.js
index 93ee66b6d4..1203ea0033 100644
--- a/packages/server/src/middleware/authenticated.js
+++ b/packages/server/src/middleware/authenticated.js
@@ -20,8 +20,10 @@ module.exports = async (ctx, next) => {
if (builderToken) {
try {
const jwtPayload = jwt.verify(builderToken, ctx.config.jwtSecret)
- ctx.apiKey = jwtPayload.apiKey
- ctx.isAuthenticated = jwtPayload.accessLevelId === BUILDER_LEVEL_ID
+ ctx.auth = {
+ apiKey: jwtPayload.apiKey,
+ authenticated: jwtPayload.accessLevelId === BUILDER_LEVEL_ID,
+ }
ctx.user = {
...jwtPayload,
accessLevel: await getAccessLevel(
@@ -38,14 +40,13 @@ module.exports = async (ctx, next) => {
}
if (!appToken) {
- ctx.isAuthenticated = false
+ ctx.auth.authenticated = false
await next()
return
}
try {
const jwtPayload = jwt.verify(appToken, ctx.config.jwtSecret)
- ctx.apiKey = jwtPayload.apiKey
ctx.user = {
...jwtPayload,
accessLevel: await getAccessLevel(
@@ -53,7 +54,10 @@ module.exports = async (ctx, next) => {
jwtPayload.accessLevelId
),
}
- ctx.isAuthenticated = ctx.user.accessLevelId !== ANON_LEVEL_ID
+ ctx.auth = {
+ authenticated: ctx.user.accessLevelId !== ANON_LEVEL_ID,
+ apiKey: jwtPayload.apiKey,
+ }
} catch (err) {
ctx.throw(err.status || STATUS_CODES.FORBIDDEN, err.text)
}
diff --git a/packages/server/src/middleware/authorized.js b/packages/server/src/middleware/authorized.js
index 4cce4c4670..bd09029471 100644
--- a/packages/server/src/middleware/authorized.js
+++ b/packages/server/src/middleware/authorized.js
@@ -5,9 +5,36 @@ const {
BUILDER_LEVEL_ID,
BUILDER,
} = require("../utilities/accessLevels")
+const environment = require("../environment")
+const { apiKeyTable } = require("../db/dynamoClient")
module.exports = (permName, getItemId) => async (ctx, next) => {
- if (!ctx.isAuthenticated) {
+ if (
+ environment.CLOUD &&
+ ctx.headers["x-api-key"] &&
+ ctx.headers["x-instanceid"]
+ ) {
+ // api key header passed by external webhook
+ const apiKeyInfo = await apiKeyTable.get({
+ primary: ctx.headers["x-api-key"],
+ })
+
+ if (apiKeyInfo) {
+ ctx.auth = {
+ authenticated: true,
+ external: true,
+ apiKey: ctx.headers["x-api-key"],
+ }
+ ctx.user = {
+ instanceId: ctx.headers["x-instanceid"],
+ }
+ return next()
+ }
+
+ ctx.throw(403, "API key invalid")
+ }
+
+ if (!ctx.auth.authenticated) {
ctx.throw(403, "Session not authenticated")
}
diff --git a/packages/server/src/middleware/usageQuota.js b/packages/server/src/middleware/usageQuota.js
index e82305dc12..778f51f9d8 100644
--- a/packages/server/src/middleware/usageQuota.js
+++ b/packages/server/src/middleware/usageQuota.js
@@ -55,7 +55,7 @@ module.exports = async (ctx, next) => {
return next()
}
try {
- await usageQuota.update(ctx.apiKey, property, usage)
+ await usageQuota.update(ctx.auth.apiKey, property, usage)
return next()
} catch (err) {
ctx.throw(403, err)
diff --git a/packages/standard-components/components.json b/packages/standard-components/components.json
index e600b264be..c7d59ddde4 100644
--- a/packages/standard-components/components.json
+++ b/packages/standard-components/components.json
@@ -218,20 +218,12 @@
"dataform": {
"description": "an HTML table that fetches data from a table or view and displays it.",
"data": true,
- "props": {
- "model": "models",
- "title": "string",
- "buttonText": "string"
- }
+ "props": {}
},
"dataformwide": {
"description": "an HTML table that fetches data from a table or view and displays it.",
"data": true,
- "props": {
- "model": "models",
- "title": "string",
- "buttonText": "string"
- }
+ "props": {}
},
"datalist": {
"description": "A configurable data list that attaches to your backend models.",
@@ -269,11 +261,22 @@
"destinationUrl": "string"
}
},
- "recorddetail": {
+ "rowdetail": {
"description": "Loads a record, using an ID in the url",
"context": "model",
"children": true,
"data": true,
+ "baseComponent": true,
+ "props": {
+ "model": "models"
+ }
+ },
+ "newrow": {
+ "description": "Prepares a new record for creation",
+ "context": "model",
+ "children": true,
+ "data": true,
+ "baseComponent": true,
"props": {
"model": "models"
}
@@ -715,7 +718,7 @@
"default": "div"
}
},
- "container": true,
+ "baseComponent": true,
"tags": [
"div",
"container",
diff --git a/packages/standard-components/package.json b/packages/standard-components/package.json
index af12bfbf19..8ed9aeab0f 100644
--- a/packages/standard-components/package.json
+++ b/packages/standard-components/package.json
@@ -16,17 +16,17 @@
"@budibase/client": "^0.2.0",
"@rollup/plugin-commonjs": "^11.1.0",
"lodash": "^4.17.15",
- "rollup": "^1.11.0",
+ "rollup": "^2.11.2",
"rollup-plugin-commonjs": "^10.0.2",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-livereload": "^1.0.1",
"rollup-plugin-node-resolve": "^5.0.0",
"rollup-plugin-postcss": "^3.1.5",
- "rollup-plugin-svelte": "^5.0.0",
- "rollup-plugin-terser": "^5.1.1",
+ "rollup-plugin-svelte": "^5.0.3",
+ "rollup-plugin-terser": "^7.0.2",
"shortid": "^2.2.15",
"sirv-cli": "^0.4.4",
- "svelte": "^3.12.1"
+ "svelte": "^3.29.0"
},
"keywords": [
"svelte"
diff --git a/packages/standard-components/src/Form.svelte b/packages/standard-components/src/Form.svelte
index 937a58f271..c9777b52b4 100644
--- a/packages/standard-components/src/Form.svelte
+++ b/packages/standard-components/src/Form.svelte
@@ -1,168 +1,61 @@
-