Browse Source

running prettier over codebase, removing merge files

gh-pages
Martin McKeaveney 7 years ago
parent
commit
94eb93b186
  1. 3
      .eslintrc.json
  2. 1
      package.json
  3. 812
      packages/bootstrap-components/dist/generators.js
  4. 30666
      packages/bootstrap-components/public/bundle.js
  5. 104
      packages/bootstrap-components/public/clientAppDefinition.js
  6. 36
      packages/bootstrap-components/rollup.config.js
  7. 26
      packages/bootstrap-components/rollup.generatorsconfig.js
  8. 196
      packages/bootstrap-components/rollup.testconfig.js
  9. 137
      packages/bootstrap-components/scripts/publishDev.js
  10. 81
      packages/bootstrap-components/src/Test/createApp.js
  11. 470
      packages/bootstrap-components/src/Test/props.js
  12. 8
      packages/bootstrap-components/src/Test/testMain.js
  13. 16
      packages/bootstrap-components/src/buildStyle.js
  14. 34
      packages/bootstrap-components/src/cssVars.js
  15. 2
      packages/bootstrap-components/src/emptyProps.js
  16. 8
      packages/bootstrap-components/src/generators.js
  17. 66
      packages/bootstrap-components/src/generators/appGenerator.js
  18. 32
      packages/bootstrap-components/src/generators/buttonGenerators.js
  19. 223
      packages/bootstrap-components/src/generators/formsGenerator.js
  20. 23
      packages/bootstrap-components/src/generators/getRecordPath.js
  21. 85
      packages/bootstrap-components/src/generators/indexTablesGenerator.js
  22. 335
      packages/bootstrap-components/src/generators/recordHomePageGenerator.js
  23. 56
      packages/bootstrap-components/src/generators/selectedNavContentGenerator.js
  24. 5
      packages/bootstrap-components/src/index.js
  25. 25
      packages/builder/babel.config.js
  26. 16
      packages/builder/build/copy.js
  27. 316
      packages/builder/rollup.config.js
  28. 31
      packages/builder/src/builderStore/api.js
  29. 44
      packages/builder/src/builderStore/buildCodeForScreens.js
  30. 20
      packages/builder/src/builderStore/createPackage.js
  31. 183
      packages/builder/src/builderStore/generate_css.js
  32. 44
      packages/builder/src/builderStore/index.js
  33. 57
      packages/builder/src/builderStore/loadComponentLibraries.js
  34. 1290
      packages/builder/src/builderStore/store.js
  35. 799
      packages/builder/src/builderStore/store.js.orig
  36. 10
      packages/builder/src/builderStore/uuid.js
  37. 18
      packages/builder/src/common/Icons/index.js
  38. 12
      packages/builder/src/common/Icons/index.js.orig
  39. 41
      packages/builder/src/common/binding.js
  40. 164
      packages/builder/src/common/core.js
  41. 43
      packages/builder/src/common/eventHandlers.js
  42. 7
      packages/builder/src/common/icon.js
  43. 34
      packages/builder/src/main.js
  44. 149
      packages/builder/src/userInterface/ComponentPanel.svelte.orig
  45. 2
      packages/builder/src/userInterface/EventsEditor/index.js
  46. 74
      packages/builder/src/userInterface/PropsView.svelte.orig
  47. 99
      packages/builder/src/userInterface/pagesParsing/buildPropsHierarchy.js
  48. 212
      packages/builder/src/userInterface/pagesParsing/createProps.js
  49. 27
      packages/builder/src/userInterface/pagesParsing/defaultPagesObject.js
  50. 135
      packages/builder/src/userInterface/pagesParsing/findDependencies.js
  51. 12
      packages/builder/src/userInterface/pagesParsing/getRootComponent.js
  52. 105
      packages/builder/src/userInterface/pagesParsing/renameScreen.js
  53. 74
      packages/builder/src/userInterface/pagesParsing/searchComponents.js
  54. 22
      packages/builder/src/userInterface/pagesParsing/splitRootComponentName.js
  55. 121
      packages/builder/src/userInterface/pagesParsing/types.js
  56. 85
      packages/builder/src/userInterface/pagesParsing/validatePages.js
  57. 71
      packages/builder/tests/buildCodeForScreen.spec.js
  58. 42
      packages/builder/tests/buildPropsHierarchy.spec.js
  59. 101
      packages/builder/tests/componentDependencies.spec.js
  60. 330
      packages/builder/tests/createDefaultProps.spec.js
  61. 85
      packages/builder/tests/expandPropDef.spec.js
  62. 503
      packages/builder/tests/generate_css.spec.js
  63. 177
      packages/builder/tests/getComponentInfo.spec.js
  64. 58
      packages/builder/tests/renameScreen.spec.js
  65. 152
      packages/builder/tests/searchComponentsProps.spec.js
  66. 197
      packages/builder/tests/testData.js
  67. 174
      packages/builder/tests/validatePages.spec.js
  68. 18
      packages/cli/src/cli.js
  69. 64
      packages/cli/src/commands/init/index.js
  70. 173
      packages/cli/src/commands/init/initHandler.js
  71. 36
      packages/cli/src/commands/instance/index.js
  72. 200
      packages/cli/src/commands/instance/instanceHandler.js
  73. 3
      packages/cli/src/commands/new/appPackageTemplate/plugins.js
  74. 35
      packages/cli/src/commands/new/index.js
  75. 90
      packages/cli/src/commands/new/newHandler.js
  76. 29
      packages/cli/src/commands/run/index.js
  77. 16
      packages/cli/src/commands/run/runHandler.js
  78. 36
      packages/cli/src/common.js
  79. 25
      packages/client/babel.config.js
  80. 165
      packages/client/rollup.config.js
  81. 91
      packages/client/scripts/publishDev.js
  82. 38
      packages/client/src/api/authenticate.js
  83. 127
      packages/client/src/api/index.js
  84. 33
      packages/client/src/api/listRecords.js
  85. 32
      packages/client/src/api/loadRecord.js
  86. 46
      packages/client/src/api/saveRecord.js
  87. 2
      packages/client/src/common/trimSlash.js
  88. 6
      packages/client/src/core/common.js
  89. 22
      packages/client/src/core/createCoreApp.js
  90. 32
      packages/client/src/core/index.js
  91. 159
      packages/client/src/createApp.js
  92. 106
      packages/client/src/index.js
  93. 120
      packages/client/src/render/initialiseChildren.js
  94. 102
      packages/client/src/render/renderComponent.js
  95. 112
      packages/client/src/state/coreHandlers.js
  96. 110
      packages/client/src/state/eventHandlers.js
  97. 90
      packages/client/src/state/getState.js
  98. 27
      packages/client/src/state/isState.js
  99. 56
      packages/client/src/state/setState.js
  100. 3
      packages/client/src/state/standardState.js

3
.eslintrc.json

@ -2,7 +2,8 @@
"env": { "env": {
"browser": true, "browser": true,
"es6": true, "es6": true,
"jest": true "jest": true,
"node": true
}, },
"parserOptions": { "parserOptions": {
"ecmaVersion": 2019, "ecmaVersion": 2019,

1
package.json

@ -18,6 +18,7 @@
"dev": "lerna run --parallel --stream dev:builder", "dev": "lerna run --parallel --stream dev:builder",
"test": "lerna run test", "test": "lerna run test",
"lint": "eslint packages", "lint": "eslint packages",
"lint:fix": "eslint --fix packages",
"format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx}\"" "format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx}\""
} }
} }

812
packages/bootstrap-components/dist/generators.js

File diff suppressed because one or more lines are too long

30666
packages/bootstrap-components/public/bundle.js

File diff suppressed because it is too large

104
packages/bootstrap-components/public/clientAppDefinition.js

@ -1 +1,103 @@
window['##BUDIBASE_APPDEFINITION##'] = {"hierarchy":{"name":"root","type":"root","children":[{"name":"customer","type":"record","fields":[{"name":"name","type":"string","typeOptions":{"maxLength":1000,"values":null,"allowDeclaredValuesOnly":false},"label":"name","getInitialValue":"default","getUndefinedValue":"default"}],"children":[{"name":"invoiceyooo","type":"record","fields":[{"name":"amount","type":"number","typeOptions":{"minValue":99999999999,"maxValue":99999999999,"decimalPlaces":2},"label":"amount","getInitialValue":"default","getUndefinedValue":"default"}],"children":[],"validationRules":[],"nodeId":2,"indexes":[],"allidsShardFactor":1,"collectionName":"invoices","isSingle":false}],"validationRules":[],"nodeId":1,"indexes":[{"name":"customer_invoices","type":"index","map":"return {...record};","filter":"","indexType":"ancestor","getShardName":"","getSortKey":"record.id","aggregateGroups":[],"allowedRecordNodeIds":[2],"nodeId":5}],"allidsShardFactor":64,"collectionName":"customers","isSingle":false}],"pathMaps":[],"indexes":[{"name":"Yeo index","type":"index","map":"return {...record};","filter":"","indexType":"ancestor","getShardName":"","getSortKey":"record.id","aggregateGroups":[],"allowedRecordNodeIds":[1],"nodeId":4},{"name":"everyones_invoices","type":"index","map":"return {...record};","filter":"","indexType":"ancestor","getShardName":"","getSortKey":"record.id","aggregateGroups":[],"allowedRecordNodeIds":[2],"nodeId":6}],"nodeId":0},"componentLibraries":["budibase-standard-components"],"appRootPath":"/testApp2","props":{}} window["##BUDIBASE_APPDEFINITION##"] = {
hierarchy: {
name: "root",
type: "root",
children: [
{
name: "customer",
type: "record",
fields: [
{
name: "name",
type: "string",
typeOptions: {
maxLength: 1000,
values: null,
allowDeclaredValuesOnly: false,
},
label: "name",
getInitialValue: "default",
getUndefinedValue: "default",
},
],
children: [
{
name: "invoiceyooo",
type: "record",
fields: [
{
name: "amount",
type: "number",
typeOptions: {
minValue: 99999999999,
maxValue: 99999999999,
decimalPlaces: 2,
},
label: "amount",
getInitialValue: "default",
getUndefinedValue: "default",
},
],
children: [],
validationRules: [],
nodeId: 2,
indexes: [],
allidsShardFactor: 1,
collectionName: "invoices",
isSingle: false,
},
],
validationRules: [],
nodeId: 1,
indexes: [
{
name: "customer_invoices",
type: "index",
map: "return {...record};",
filter: "",
indexType: "ancestor",
getShardName: "",
getSortKey: "record.id",
aggregateGroups: [],
allowedRecordNodeIds: [2],
nodeId: 5,
},
],
allidsShardFactor: 64,
collectionName: "customers",
isSingle: false,
},
],
pathMaps: [],
indexes: [
{
name: "Yeo index",
type: "index",
map: "return {...record};",
filter: "",
indexType: "ancestor",
getShardName: "",
getSortKey: "record.id",
aggregateGroups: [],
allowedRecordNodeIds: [1],
nodeId: 4,
},
{
name: "everyones_invoices",
type: "index",
map: "return {...record};",
filter: "",
indexType: "ancestor",
getShardName: "",
getSortKey: "record.id",
aggregateGroups: [],
allowedRecordNodeIds: [2],
nodeId: 6,
},
],
nodeId: 0,
},
componentLibraries: ["budibase-standard-components"],
appRootPath: "/testApp2",
props: {},
}

36
packages/bootstrap-components/rollup.config.js

@ -1,20 +1,20 @@
import svelte from 'rollup-plugin-svelte'; import svelte from "rollup-plugin-svelte"
import resolve from 'rollup-plugin-node-resolve'; import resolve from "rollup-plugin-node-resolve"
export default { export default {
input: 'src/index.js', input: "src/index.js",
output: [ output: [
{ {
file: "dist/index.js", file: "dist/index.js",
format: 'esm', format: "esm",
name:"budibaseStandardComponents", name: "budibaseStandardComponents",
sourcemap: "inline" sourcemap: "inline",
} },
], ],
plugins: [ plugins: [
svelte({ svelte({
hydratable:true hydratable: true,
}), }),
resolve() resolve(),
] ],
}; }

26
packages/bootstrap-components/rollup.generatorsconfig.js

@ -1,16 +1,14 @@
import resolve from 'rollup-plugin-node-resolve'; import resolve from "rollup-plugin-node-resolve"
export default { export default {
input: 'src/generators.js', input: "src/generators.js",
output: [ output: [
{ {
file: "dist/generators.js", file: "dist/generators.js",
format: 'esm', format: "esm",
name:"budibaseStandardComponents", name: "budibaseStandardComponents",
sourcemap: "inline" sourcemap: "inline",
} },
], ],
plugins: [ plugins: [resolve()],
resolve() }
]
};

196
packages/bootstrap-components/rollup.testconfig.js

@ -1,89 +1,131 @@
import svelte from 'rollup-plugin-svelte'; import svelte from "rollup-plugin-svelte"
import resolve from 'rollup-plugin-node-resolve'; import resolve from "rollup-plugin-node-resolve"
import commonjs from 'rollup-plugin-commonjs'; import commonjs from "rollup-plugin-commonjs"
import livereload from 'rollup-plugin-livereload'; import livereload from "rollup-plugin-livereload"
import { terser } from 'rollup-plugin-terser'; import { terser } from "rollup-plugin-terser"
import json from 'rollup-plugin-json'; import json from "rollup-plugin-json"
const production = !process.env.ROLLUP_WATCH; const production = !process.env.ROLLUP_WATCH
const lodash_fp_exports = [ const lodash_fp_exports = [
"find", "isUndefined", "split", "find",
"last", "union", "reduce", "isObject", "isUndefined",
"cloneDeep", "some", "isArray", "map", "split",
"filter", "keys", "isFunction", "isEmpty", "last",
"countBy", "join", "includes", "flatten", "union",
"constant", "first", "intersection", "take", "reduce",
"has", "mapValues", "isString", "isBoolean", "isObject",
"isNull", "isNumber", "isObjectLike", "isDate", "cloneDeep",
"clone", "values", "keyBy", "isNaN", "some",
"isInteger", "toNumber"]; "isArray",
"map",
"filter",
"keys",
"isFunction",
"isEmpty",
"countBy",
"join",
"includes",
"flatten",
"constant",
"first",
"intersection",
"take",
"has",
"mapValues",
"isString",
"isBoolean",
"isNull",
"isNumber",
"isObjectLike",
"isDate",
"clone",
"values",
"keyBy",
"isNaN",
"isInteger",
"toNumber",
]
const lodash_exports = [ const lodash_exports = [
"flow", "head", "flow",
"tail", "findIndex", "startsWith", "head",
"dropRight", "takeRight", "tail",
"trim", "split", "replace", "findIndex",
"merge", "assign"]; "startsWith",
"dropRight",
"takeRight",
"trim",
"split",
"replace",
"merge",
"assign",
]
const coreExternal = [ const coreExternal = [
"lodash", "lodash/fp", "date-fns", "lodash",
"lunr", "safe-buffer", "shortid", "lodash/fp",
"@nx-js/compiler-util" "date-fns",
]; "lunr",
"safe-buffer",
"shortid",
"@nx-js/compiler-util",
]
export default { export default {
input: 'src/Test/testMain.js', input: "src/Test/testMain.js",
output: { output: {
sourcemap: true, sourcemap: true,
format: 'iife', format: "iife",
name: 'app', name: "app",
file: 'public/bundle.js' file: "public/bundle.js",
}, },
plugins: [ plugins: [
svelte({ svelte({
// enable run-time checks when not in production // enable run-time checks when not in production
dev: !production, dev: !production,
// we'll extract any component CSS out into // we'll extract any component CSS out into
// a separate file — better for performance // a separate file — better for performance
css: css => { css: css => {
css.write('public/bundle.css'); css.write("public/bundle.css")
}, },
hydratable:true
}),
// If you have external dependencies installed from hydratable: true,
// npm, you'll most likely need these plugins. In }),
// some cases you'll need additional configuration —
// consult the documentation for details:
// https://github.com/rollup/rollup-plugin-commonjs
resolve({
browser: true,
dedupe: importee => {
return importee === 'svelte'
|| importee.startsWith('svelte/')
|| coreExternal.includes(importee);
}
}),
commonjs({
namedExports: {
"lodash/fp": lodash_fp_exports,
"lodash":lodash_exports,
"shortid": ["generate"]
}
}),
json(),
// Watch the `public` directory and refresh the // If you have external dependencies installed from
// browser on changes when not in production // npm, you'll most likely need these plugins. In
!production && livereload('public'), // some cases you'll need additional configuration —
// consult the documentation for details:
// https://github.com/rollup/rollup-plugin-commonjs
resolve({
browser: true,
dedupe: importee => {
return (
importee === "svelte" ||
importee.startsWith("svelte/") ||
coreExternal.includes(importee)
)
},
}),
commonjs({
namedExports: {
"lodash/fp": lodash_fp_exports,
lodash: lodash_exports,
shortid: ["generate"],
},
}),
json(),
// If we're building for production (npm run build // Watch the `public` directory and refresh the
// instead of npm run dev), minify // browser on changes when not in production
production && terser() !production && livereload("public"),
],
watch: { // If we're building for production (npm run build
clearScreen: false // instead of npm run dev), minify
} production && terser(),
}; ],
watch: {
clearScreen: false,
},
}

137
packages/bootstrap-components/scripts/publishDev.js

@ -1,61 +1,92 @@
const { readdir, stat, copyFile } = require("fs-extra"); const { readdir, stat, copyFile } = require("fs-extra")
const { constants } = require("fs"); const { constants } = require("fs")
const { join, basename } = require("path"); const { join, basename } = require("path")
const packagesFolder = "..";
const jsFile = dir => join(dir, "index.js");
const generatorsFile = dir => join(dir, "generators.js");
const jsMapFile = dir => join(dir, "index.js.map");
const sourceJs = jsFile("dist");
const sourceJsMap = jsMapFile("dist");
const componentsFile = "components.json";
const sourceGenerators = generatorsFile("dist");
const appPackages = join(packagesFolder, "server", "appPackages");
const publicMain = appName => join(appPackages, appName, "public", "main", "lib", "node_modules", "@budibase", "bootstrap-components");
const publicUnauth = appName => join(appPackages, appName, "public", "unauthenticated", "lib", "node_modules", "@budibase", "bootstrap-components");
const nodeModulesDist = appName => join(appPackages, appName, "node_modules", "@budibase", "bootstrap-components", "dist");
const nodeModules = appName => join(appPackages, appName, "node_modules", "@budibase", "bootstrap-components");
(async () => {
const apps = await readdir(appPackages);
const copySource = file => async toDir => {
const dest = join(toDir, basename(file));
try {
await copyFile(file, dest, constants.COPYFILE_FICLONE);
console.log(`COPIED ${file} to ${dest}`);
} catch(e) {
console.log(`COPY FAILED ${file} to ${dest}: ${e}`);
}
}
const copySourceJs = copySource(sourceJs);
const copySourceJsMap = copySource(sourceJsMap);
const copyGenerators = copySource(sourceGenerators);
const copyComponentsJson = copySource(componentsFile);
for(let app of apps) { const packagesFolder = ".."
if(!(await stat(join(appPackages, app))).isDirectory()) continue;
await copySourceJs(nodeModulesDist(app)); const jsFile = dir => join(dir, "index.js")
await copySourceJsMap(nodeModulesDist(app)); const generatorsFile = dir => join(dir, "generators.js")
await copyGenerators(nodeModulesDist(app)); const jsMapFile = dir => join(dir, "index.js.map")
const sourceJs = jsFile("dist")
const sourceJsMap = jsMapFile("dist")
const componentsFile = "components.json"
const sourceGenerators = generatorsFile("dist")
await copyComponentsJson(nodeModules(app)); const appPackages = join(packagesFolder, "server", "appPackages")
await copySourceJs(join(publicMain(app), "dist")); const publicMain = appName =>
await copySourceJsMap(join(publicMain(app), "dist")); join(
await copyGenerators(join(publicMain(app), "dist")); appPackages,
appName,
"public",
"main",
"lib",
"node_modules",
"@budibase",
"bootstrap-components"
)
const publicUnauth = appName =>
join(
appPackages,
appName,
"public",
"unauthenticated",
"lib",
"node_modules",
"@budibase",
"bootstrap-components"
)
const nodeModulesDist = appName =>
join(
appPackages,
appName,
"node_modules",
"@budibase",
"bootstrap-components",
"dist"
)
const nodeModules = appName =>
join(
appPackages,
appName,
"node_modules",
"@budibase",
"bootstrap-components"
)
;(async () => {
const apps = await readdir(appPackages)
await copySourceJs(join(publicUnauth(app), "dist")); const copySource = file => async toDir => {
await copySourceJsMap(join(publicUnauth(app), "dist")); const dest = join(toDir, basename(file))
await copyGenerators(join(publicUnauth(app), "dist")); try {
await copyFile(file, dest, constants.COPYFILE_FICLONE)
console.log(`COPIED ${file} to ${dest}`)
} catch (e) {
console.log(`COPY FAILED ${file} to ${dest}: ${e}`)
} }
}
const copySourceJs = copySource(sourceJs)
const copySourceJsMap = copySource(sourceJsMap)
const copyGenerators = copySource(sourceGenerators)
const copyComponentsJson = copySource(componentsFile)
for (let app of apps) {
if (!(await stat(join(appPackages, app))).isDirectory()) continue
await copySourceJs(nodeModulesDist(app))
await copySourceJsMap(nodeModulesDist(app))
await copyGenerators(nodeModulesDist(app))
await copyComponentsJson(nodeModules(app))
await copySourceJs(join(publicMain(app), "dist"))
await copySourceJsMap(join(publicMain(app), "dist"))
await copyGenerators(join(publicMain(app), "dist"))
})(); await copySourceJs(join(publicUnauth(app), "dist"))
await copySourceJsMap(join(publicUnauth(app), "dist"))
await copyGenerators(join(publicUnauth(app), "dist"))
}
})()

81
packages/bootstrap-components/src/Test/createApp.js

@ -1,46 +1,43 @@
import { writable } from "svelte/store"; import Login from "../Login.svelte"
import Login from "../Login.svelte"; import Grid from "../Grid.svelte"
import Grid from "../Grid.svelte"; import Form from "../Form.svelte"
import Form from "../Form.svelte"; import Textbox from "../Textbox.svelte"
import Textbox from "../Textbox.svelte"; import Text from "../Text.svelte"
import Text from "../Text.svelte"; import Nav from "../Nav.svelte"
import Nav from "../Nav.svelte"; import Panel from "../Panel.svelte"
import Panel from "../Panel.svelte"; import StackPanel from "../StackPanel.svelte"
import StackPanel from "../StackPanel.svelte"; import Table from "../Table.svelte"
import Table from "../Table.svelte"; import Button from "../Button.svelte"
import Button from "../Button.svelte"; import { createApp } from "@budibase/client/src/createApp"
import { createApp } from "@budibase/client/src/createApp";
export default async () => { export default async () => {
const componentLibraries = {
components: {
login: Login,
grid: Grid,
form: Form,
textbox: Textbox,
text: Text,
nav: Nav,
panel: Panel,
table: Table,
stackpanel: StackPanel,
button: Button,
},
}
const componentLibraries = { const appDef = { hierarchy: {}, actions: {} }
components : { const user = { name: "yeo", permissions: [] }
login : Login,
grid : Grid,
form : Form,
textbox : Textbox,
text: Text,
nav: Nav,
panel: Panel,
table: Table,
stackpanel: StackPanel,
button: Button
}
}
const appDef = {hierarchy:{}, actions:{}}; var app = createApp(componentLibraries, appDef, user)
const user = {name:"yeo", permissions:[]}; app.store.update(s => {
s.people = [
var app = createApp(componentLibraries, appDef, user); { name: "bob", address: "123 Main Street", status: "Open" },
app.store.update(s => { { name: "poppy", address: "456 Side Road", status: "Closed" },
s.people = [ { name: "Oscar", address: "678 Dodgy Alley", status: "Open" },
{name:"bob", address: "123 Main Street", status: "Open"}, ]
{name:"poppy", address: "456 Side Road", status: "Closed"}, return s
{name:"Oscar", address: "678 Dodgy Alley", status: "Open"}, })
];
return s;
})
return app; return app
}
}

470
packages/bootstrap-components/src/Test/props.js

@ -1,253 +1,247 @@
export const props = { export const props = {
login: { _component: "components/login" },
login: { _component:"components/login" }, form: {
_component: "components/form",
form: { formControls: [
_component: "components/form", {
formControls: [ control: {
{ _component: "components/textbox",
control: { },
_component: "components/textbox" label: "First Name",
}, },
label:"First Name" {
}, control: {
{ _component: "components/textbox",
control: { },
_component: "components/textbox" label: "Last Name",
}, },
label:"Last Name" ],
} },
]
},
nav: { nav: {
_component: "components/nav", _component: "components/nav",
navBarBackground: "red", navBarBackground: "red",
navBarBorder: "1px solid maroon", navBarBorder: "1px solid maroon",
navBarColor: "black", navBarColor: "black",
selectedItemBackground: "maroon", selectedItemBackground: "maroon",
selectedItemColor: "white", selectedItemColor: "white",
selectedItemBorder: "green", selectedItemBorder: "green",
itemHoverBackground: "yellow", itemHoverBackground: "yellow",
itemHoverColor: "pink", itemHoverColor: "pink",
items: [ items: [
{ {
title: "People", title: "People",
component: { component: {
_component: "components/panel", _component: "components/panel",
text:"People Panel", text: "People Panel",
padding: "40px", padding: "40px",
border: "2px solid pink", border: "2px solid pink",
background: "mistyrose" background: "mistyrose",
},
},
{
title: "Animals",
component: {
_component: "components/panel",
text: "Animals Panel",
padding: "40px",
border: "2px solid green",
background: "azure",
},
},
],
},
} table: {
}, _component: "components/table",
{ columns: [
title: "Animals", {
component: { title: {
_component: "components/panel", "##bbstate": "NameColumnName",
text:"Animals Panel", "##bbsource": "store",
padding: "40px", "##bbstatefallback": "Name",
border: "2px solid green", },
background: "azure" value: {
} "##bbstate": "name",
} "##bbsource": "context",
] },
},
{
title: "Address",
value: {
"##bbstate": "address",
"##bbsource": "context",
},
},
{
title: "Status",
value: {
"##bbstate": "status",
"##bbsource": "context",
},
},
],
data: {
"##bbstate": "people",
}, },
onRowClick: [
{
"##eventHandlerType": "Set State",
parameters: {
path: "NameColumnName",
value: {
"##bbstate": "name",
"##bbsource": "context",
"##bbstatefallback": "balls to that",
},
},
},
],
tableClass: "table-default",
theadClass: "thead-default",
tbodyClass: "tbody-default",
trClass: "tr-default",
thClass: "th-default",
},
table: { grid: {
_component:"components/table", _component: "components/grid",
columns: [ gridTemplateColumns: "[left] auto [center] auto [right] auto",
{ gridTemplateRows: "[top] auto [middle] auto [bottom] auto",
title: { children: [
"##bbstate":"NameColumnName", {
"##bbsource":"store", control: {
"##bbstatefallback": "Name" _component: "components/text",
}, value: "1",
value: { background: "blue",
"##bbstate":"name", textAlign: "center",
"##bbsource":"context" color: "white",
} },
}, gridColumn: "left",
{ gridRow: "top",
title: "Address", },
value: { {
"##bbstate":"address", control: {
"##bbsource":"context" _component: "components/text",
} value: "2",
}, background: "red",
{ textAlign: "center",
title: "Status", color: "white",
value: { padding: "10px",
"##bbstate":"status", },
"##bbsource":"context" gridColumn: "center",
} gridRow: "middle",
} },
], {
data: { control: {
"##bbstate":"people" _component: "components/text",
}, value: "3",
onRowClick: [ background: "yellow",
{ textAlign: "center",
"##eventHandlerType": "Set State", color: "black",
parameters: { },
path: "NameColumnName", gridColumn: "right",
value: { gridRow: "bottom",
"##bbstate":"name", },
"##bbsource":"context", ],
"##bbstatefallback": "balls to that" },
} boundStackPanel: {
} _component: "components/stackpanel",
} direction: "horizontal",
], children: [
tableClass: "table-default", {
theadClass: "thead-default", control: {
tbodyClass: "tbody-default", _component: "components/text",
trClass: "tr-default", value: "STATIC",
thClass: "th-default" },
},
],
data: {
"##bbstate": "people",
}, },
dataItemComponent: {
grid: { _component: "components/panel",
_component: "components/grid", text: {
gridTemplateColumns: "[left] auto [center] auto [right] auto", "##bbstate": "name",
gridTemplateRows: "[top] auto [middle] auto [bottom] auto", "##bbsource": "context",
children : [ "##bbstatefallback": "balls to that",
{ },
control: { padding: "10px",
_component: "components/text", border: "5px solid black",
value: "1", margin: "10px",
background: "blue", hoverColor: "white",
textAlign:"center", hoverBackground: "black",
color: "white" height: "200px",
}, weight: "200px",
gridColumn: "left",
gridRow: "top"
},
{
control: {
_component: "components/text",
value: "2",
background: "red",
textAlign:"center",
color: "white",
padding: "10px"
},
gridColumn: "center",
gridRow: "middle"
},
{
control: {
_component: "components/text",
value: "3",
background: "yellow",
textAlign:"center",
color: "black"
},
gridColumn: "right",
gridRow: "bottom"
}
]
}, },
boundStackPanel: { },
_component: "components/stackpanel", hiddenNav: {
direction: "horizontal", _component: "components/stackpanel",
children: [ children: [
{
control: {
_component: "components/button",
contentText: "Peep",
onClick: [
{ {
control: { "##eventHandlerType": "Set State",
_component: "components/text", parameters: {
value: "STATIC" path: "selected",
} value: "People",
} },
],
data: {
"##bbstate":"people"
},
dataItemComponent: {
_component: "components/panel",
text: {
"##bbstate":"name",
"##bbsource":"context",
"##bbstatefallback": "balls to that"
}, },
padding: "10px", ],
border: "5px solid black", },
margin: "10px", },
hoverColor: "white", {
hoverBackground: "black", control: {
height:"200px", _component: "components/button",
weight:"200px" contentText: "Ani",
} onClick: [
},
hiddenNav: {
_component: "components/stackpanel",
children: [
{ {
control:{ "##eventHandlerType": "Set State",
_component: "components/button", parameters: {
contentText: "Peep", path: "selected",
onClick: [ value: "Animals",
{ },
"##eventHandlerType": "Set State",
parameters: {
path: "selected",
value: "People"
}
}
]
}
}, },
],
},
},
{
control: {
_component: "components/nav",
hideNavBar: true,
selectedItem: {
"##bbstate": "selected",
"##bbsource": "store",
"##bbstatefallback": "Animals",
},
items: [
{ {
control:{ title: "People",
_component: "components/button", component: {
contentText: "Ani", _component: "components/panel",
onClick: [ text: "People Panel",
{ padding: "40px",
"##eventHandlerType": "Set State", border: "2px solid pink",
parameters: { background: "mistyrose",
path: "selected", },
value: "Animals"
}
}
]
}
}, },
{ {
control: { title: "Animals",
_component: "components/nav", component: {
hideNavBar: true, _component: "components/panel",
selectedItem: { text: "Animals Panel",
"##bbstate":"selected", padding: "40px",
"##bbsource":"store", border: "2px solid green",
"##bbstatefallback": "Animals" background: "azure",
}, },
items: [ },
{ ],
title: "People", },
component: { },
_component: "components/panel", ],
text:"People Panel", },
padding: "40px", }
border: "2px solid pink",
background: "mistyrose"
}
},
{
title: "Animals",
component: {
_component: "components/panel",
text:"Animals Panel",
padding: "40px",
border: "2px solid green",
background: "azure"
}
}
]
}
}
]
}
}

8
packages/bootstrap-components/src/Test/testMain.js

@ -1,7 +1,7 @@
import App from './TestApp.svelte'; import App from "./TestApp.svelte"
const app = new App({ const app = new App({
target: document.body, target: document.body,
}); })
export default app; export default app

16
packages/bootstrap-components/src/buildStyle.js

@ -1,9 +1,9 @@
export const buildStyle = (styles) => { export const buildStyle = styles => {
let str = ""; let str = ""
for(let s in styles) { for (let s in styles) {
if(styles[s]) { if (styles[s]) {
str += `${s}: ${styles[s]}; ` str += `${s}: ${styles[s]}; `
}
} }
return str; }
} return str
}

34
packages/bootstrap-components/src/cssVars.js

@ -1,21 +1,19 @@
// https://github.com/kaisermann/svelte-css-vars // https://github.com/kaisermann/svelte-css-vars
export default (node, props) => { export default (node, props) => {
Object.entries(props).forEach(([key, value]) => { Object.entries(props).forEach(([key, value]) => {
node.style.setProperty(`--${key}`, value); node.style.setProperty(`--${key}`, value)
}); })
return { return {
update(new_props) { update(new_props) {
Object.entries(new_props).forEach(([key, value]) => { Object.entries(new_props).forEach(([key, value]) => {
node.style.setProperty(`--${key}`, value); node.style.setProperty(`--${key}`, value)
delete props[key]; delete props[key]
}); })
Object.keys(props).forEach(name => Object.keys(props).forEach(name => node.style.removeProperty(`--${name}`))
node.style.removeProperty(`--${name}`), props = new_props
); },
props = new_props; }
}, }
};
};

2
packages/bootstrap-components/src/emptyProps.js

@ -1 +1 @@
export const emptyProps = () => ({_component:""}); export const emptyProps = () => ({ _component: "" })

8
packages/bootstrap-components/src/generators.js

@ -1,4 +1,4 @@
export { forms } from "./generators/formsGenerator"; export { forms } from "./generators/formsGenerator"
export { indexTables } from "./generators/indexTablesGenerator"; export { indexTables } from "./generators/indexTablesGenerator"
export { app } from "./generators/appGenerator"; export { app } from "./generators/appGenerator"
export { recordHomePageComponents as recordHomepages } from "./generators/recordHomePageGenerator"; export { recordHomePageComponents as recordHomepages } from "./generators/recordHomePageGenerator"

66
packages/bootstrap-components/src/generators/appGenerator.js

@ -1,37 +1,37 @@
import { navContentComponentName, selectNavContent } from "./selectedNavContentGenerator"; import {
import { recordHomepages } from "./recordHomePageGenerator"; navContentComponentName,
export const app = ({records, indexes, helpers}) => [ selectNavContent,
{ } from "./selectedNavContentGenerator"
name: "Application Root", import { recordHomepages } from "./recordHomePageGenerator"
inherits: "@budibase/bootstrap-components/nav", export const app = ({ records, indexes, helpers }) => [
props: { {
items: recordHomepages({indexes, records}) name: "Application Root",
.map(navItem), inherits: "@budibase/bootstrap-components/nav",
orientation: "horizontal", props: {
alignment: "start", items: recordHomepages({ indexes, records }).map(navItem),
fill: false, orientation: "horizontal",
pills: true, alignment: "start",
selectedItem: { fill: false,
"##bbstate":"selectedNav", pills: true,
"##bbstatefallback":`${records[0].name}`, selectedItem: {
"##bbsource": "store" "##bbstate": "selectedNav",
}, "##bbstatefallback": `${records[0].name}`,
className: "p-3" "##bbsource": "store",
} },
className: "p-3",
}, },
{ },
name: "Login", {
inherits: "@budibase/standard-components/login", name: "Login",
props: {} inherits: "@budibase/standard-components/login",
}, props: {},
...selectNavContent({records, indexes, helpers}) },
...selectNavContent({ records, indexes, helpers }),
] ]
export const navItem = ({ record }) => ({
export const navItem = ({record}) => ({ title: record.collectionName,
title: record.collectionName, component: {
component : { _component: navContentComponentName(record),
_component: navContentComponentName(record) },
}
}) })

32
packages/bootstrap-components/src/generators/buttonGenerators.js

@ -1,18 +1,18 @@
export const buttons = () => [ export const buttons = () => [
{ {
name: "common/Primary Button", name: "common/Primary Button",
description: "Bootstrap primary button ", description: "Bootstrap primary button ",
inherits: "@budibase/standard-components/button", inherits: "@budibase/standard-components/button",
props: { props: {
className: "btn btn-primary" className: "btn btn-primary",
}
}, },
{ },
name: "common/Default Button", {
description: "Bootstrap default button", name: "common/Default Button",
inherits: "@budibase/standard-components/button", description: "Bootstrap default button",
props: { inherits: "@budibase/standard-components/button",
className: "btn btn-secondary" props: {
} className: "btn btn-secondary",
} },
] },
]

223
packages/bootstrap-components/src/generators/formsGenerator.js

@ -1,123 +1,128 @@
import {buttons} from "./buttonGenerators"; import { buttons } from "./buttonGenerators"
export const forms = ({records, indexes, helpers}) => export const forms = ({ records, indexes, helpers }) => [
[ ...records.map(root),
...records.map(root), ...buttons({ records, indexes, helpers }),
...buttons({records, indexes, helpers}) ]
];
export const formName = record => `${record.name}/${record.name} Form`; export const formName = record => `${record.name}/${record.name} Form`
const root = record => ({ const root = record => ({
name: formName(record), name: formName(record),
description: `Control for creating/updating '${record.nodeKey()}' `, description: `Control for creating/updating '${record.nodeKey()}' `,
inherits: "@budibase/standard-components/div", inherits: "@budibase/standard-components/div",
props: { props: {
className:"p-1", className: "p-1",
children: [ children: [
{ {
component: { component: {
_component: "@budibase/standard-components/h3", _component: "@budibase/standard-components/h3",
text: `Edit ${record.name}`, text: `Edit ${record.name}`,
} },
}, },
form(record), form(record),
saveCancelButtons(record) saveCancelButtons(record),
] ],
} },
}) })
const form = record => ({ const form = record => ({
component: { component: {
_component: "@budibase/standard-components/form", _component: "@budibase/standard-components/form",
formControls: formControls: record.fields.map(f => formControl(record, f)),
record.fields.map(f => formControl(record, f)) },
}
}) })
const formControl = (record, field) => { const formControl = (record, field) => {
if(field.type === "string" && field.typeOptions.values && field.typeOptions.values.length > 0) { if (
return ({ field.type === "string" &&
control: { field.typeOptions.values &&
_component: "@budibase/standard-components/select", field.typeOptions.values.length > 0
options: field.typeOptions.values.map(v => ({id:v, value:v})), ) {
value: { return {
"##bbstate":`${record.name}.${field.name}`, control: {
"##bbsource":"store" _component: "@budibase/standard-components/select",
}, options: field.typeOptions.values.map(v => ({ id: v, value: v })),
className: "form-control" value: {
}, "##bbstate": `${record.name}.${field.name}`,
label: field.label "##bbsource": "store",
}); },
} else { className: "form-control",
return ({ },
control: { label: field.label,
_component: "@budibase/standard-components/input", }
value: { } else {
"##bbstate":`${record.name}.${field.name}`, return {
"##bbsource":"store" control: {
}, _component: "@budibase/standard-components/input",
className: "form-control", value: {
type: field.type === "string" ? "text" "##bbstate": `${record.name}.${field.name}`,
: field.type === "datetime" ? "date" "##bbsource": "store",
: field.type === "number" ? "number" },
: "text" className: "form-control",
}, type:
label: field.label field.type === "string"
}); ? "text"
: field.type === "datetime"
? "date"
: field.type === "number"
? "number"
: "text",
},
label: field.label,
} }
}
} }
const saveCancelButtons = (record) => ({ const saveCancelButtons = record => ({
component: { component: {
_component: "@budibase/standard-components/stackpanel", _component: "@budibase/standard-components/stackpanel",
direction: "horizontal", direction: "horizontal",
children: [ children: [
paddedPanelForButton({ paddedPanelForButton({
_component: "common/Primary Button", _component: "common/Primary Button",
contentText: `Save ${record.name}`, contentText: `Save ${record.name}`,
onClick: [ onClick: [
{ {
"##eventHandlerType": "Save Record", "##eventHandlerType": "Save Record",
parameters: { parameters: {
statePath: `${record.name}`, statePath: `${record.name}`,
} },
}, },
{ {
"##eventHandlerType": "Set State", "##eventHandlerType": "Set State",
parameters: { parameters: {
path: `isEditing${record.name}`, path: `isEditing${record.name}`,
value: "" value: "",
} },
} },
] ],
}), }),
paddedPanelForButton({ paddedPanelForButton({
_component: "common/Default Button", _component: "common/Default Button",
contentText: `Cancel`, contentText: `Cancel`,
onClick: [ onClick: [
{ {
"##eventHandlerType": "Set State", "##eventHandlerType": "Set State",
parameters: { parameters: {
path: `isEditing${record.name}`, path: `isEditing${record.name}`,
value: "" value: "",
} },
} },
] ],
}) }),
] ],
} },
}) })
const paddedPanelForButton = (button) => ({ const paddedPanelForButton = button => ({
control: { control: {
_component: "@budibase/standard-components/div", _component: "@budibase/standard-components/div",
className: "btn-group", className: "btn-group",
children: [ children: [
{ {
component: button component: button,
} },
] ],
} },
}); })

23
packages/bootstrap-components/src/generators/getRecordPath.js

@ -1,15 +1,14 @@
export const getRecordPath = (record) => { export const getRecordPath = () => {
const parts = []
const parts = []; const add = current => {
parts.push(current.name)
const add = (current) => { if (current.parent().type === "root") {
parts.push(current.name); return
if(current.parent().type === "root") {
return;
}
add(current.parent());
} }
return parts.reverse().join("/"); add(current.parent())
} }
return parts.reverse().join("/")
}

85
packages/bootstrap-components/src/generators/indexTablesGenerator.js

@ -1,54 +1,53 @@
import { getRecordPath } from "./getRecordPath"; import { getRecordPath } from "./getRecordPath"
export const indexTables = ({indexes, helpers}) => export const indexTables = ({ indexes, helpers }) =>
indexes.map(i => indexTable(i, helpers)); indexes.map(i => indexTable(i, helpers))
const excludedColumns = ["id", "isNew", "key", "type", "sortKey"]; const excludedColumns = ["id", "isNew", "key", "type", "sortKey"]
export const indexTableProps = (index, helpers) => ({ export const indexTableProps = (index, helpers) => ({
data: { data: {
"##bbstate":index.nodeKey(), "##bbstate": index.nodeKey(),
"##bbsource":"store" "##bbsource": "store",
},
tableClass: "table table-hover",
theadClass: "thead-dark",
columns: helpers
.indexSchema(index)
.filter(c => !excludedColumns.includes(c.name))
.map(column),
onRowClick: [
{
"##eventHandlerType": "Set State",
parameters: {
path: `selectedrow_${index.name}`,
value: {
"##bbstate": "key",
"##bbsource": "event",
},
},
}, },
tableClass: "table table-hover", ],
theadClass: "thead-dark", })
columns: helpers
.indexSchema(index)
.filter(c => !excludedColumns.includes(c.name))
.map(column),
onRowClick: [
{
"##eventHandlerType": "Set State",
parameters: {
path: `selectedrow_${index.name}`,
value: {
"##bbstate": "key",
"##bbsource": "event"
}
},
}
]
});
export const getIndexTableName = (index, record) => { export const getIndexTableName = (index, record) => {
record = record record = record || index.parent().type === "record" ? index.parent() : null
|| index.parent().type === "record" ? index.parent() : null;
return record
return (record ? `${getRecordPath(record)}/${index.name} Table`
? `${getRecordPath(record)}/${index.name} Table` : `${index.name} Table`
: `${index.name} Table`);
} }
const indexTable = (index, helpers) => ({ const indexTable = (index, helpers) => ({
name: getIndexTableName(index), name: getIndexTableName(index),
inherits: "@budibase/standard-components/table", inherits: "@budibase/standard-components/table",
props: indexTableProps(index, helpers) props: indexTableProps(index, helpers),
}); })
const column = (col) => ({ const column = col => ({
title: col.name, title: col.name,
value: { value: {
"##bbstate": col.name, "##bbstate": col.name,
"##bbsource":"context" "##bbsource": "context",
} },
}) })

335
packages/bootstrap-components/src/generators/recordHomePageGenerator.js

@ -1,190 +1,183 @@
import { import { getIndexTableName, indexTables } from "./indexTablesGenerator"
getIndexTableName, indexTables
} from "./indexTablesGenerator";
import { import { buttons } from "./buttonGenerators"
buttons
} from "./buttonGenerators";
export const recordHomePageComponents = ({indexes, records, helpers}) => export const recordHomePageComponents = ({ indexes, records, helpers }) => [
[ ...recordHomepages({ indexes, records }).map(component),
...recordHomepages({indexes, records})
.map(component),
...recordHomepages({indexes, records}) ...recordHomepages({ indexes, records }).map(homePageButtons),
.map(homePageButtons),
...indexTables({indexes, records, helpers}),
...buttons({indexes, buttons, helpers}) ...indexTables({ indexes, records, helpers }),
]
...buttons({ indexes, buttons, helpers }),
]
const findIndexForRecord = (indexes, record) => { const findIndexForRecord = (indexes, record) => {
const forRecord = indexes.filter(i => i.allowedRecordNodeIds.includes(record.nodeId)); const forRecord = indexes.filter(i =>
if(forRecord.length === 0) return; i.allowedRecordNodeIds.includes(record.nodeId)
if(forRecord.length === 1) return forRecord[0]; )
const noMap = forRecord.filter(i => !i.filter || !i.filter.trim()); if (forRecord.length === 0) return
if(noMap.length === 0) forRecord[0]; if (forRecord.length === 1) return forRecord[0]
return noMap[0]; const noMap = forRecord.filter(i => !i.filter || !i.filter.trim())
if (noMap.length === 0) forRecord[0]
return noMap[0]
} }
export const recordHomepages = ({indexes, records}) => export const recordHomepages = ({ indexes, records }) =>
records.filter(r => r.parent().type === "root") records
.map(r =>({ .filter(r => r.parent().type === "root")
record:r, .map(r => ({
index:findIndexForRecord(indexes, r) record: r,
})) index: findIndexForRecord(indexes, r),
.filter(r => r.index); }))
.filter(r => r.index)
export const homepageComponentName = record =>
`${record.name}/${record.name} homepage`
export const homepageComponentName = (record) => const component = ({ record, index }) => ({
`${record.name}/${record.name} homepage`; inherits: "@budibase/standard-components/div",
name: homepageComponentName(record),
props: {
className: "d-flex flex-column h-100",
children: [
{
component: {
_component: `${record.name}/homepage buttons`,
},
},
{
component: {
_component: getIndexTableName(index),
},
className: "flex-gow-1 overflow-auto",
},
],
onLoad: [
{
"##eventHandlerType": "Set State",
parameters: {
path: `isEditing${record.name}`,
value: "",
},
},
{
"##eventHandlerType": "List Records",
parameters: {
statePath: index.nodeKey(),
indexKey: index.nodeKey(),
},
},
],
},
})
const component = ({record, index}) => ({ const homePageButtons = ({ index, record }) => ({
inherits: "@budibase/standard-components/div", inherits: "@budibase/standard-components/div",
name: homepageComponentName(record), name: `${record.name}/homepage buttons`,
props: { props: {
className: "d-flex flex-column h-100", className: "btn-toolbar mt-4 mb-2",
children: [ children: [
{
component: {
_component: "@budibase/standard-components/div",
className: "btn-group mr-3",
children: [
{ {
component: { component: {
_component: `${record.name}/homepage buttons`, _component: "common/Default Button",
} contentText: `Create ${record.name}`,
onClick: [
{
"##eventHandlerType": "Get New Record",
parameters: {
statePath: record.name,
collectionKey: `/${record.collectionName}`,
childRecordType: record.name,
},
},
{
"##eventHandlerType": "Set State",
parameters: {
path: `isEditing${record.name}`,
value: "true",
},
},
],
},
}, },
{ {
component: { component: {
_component: getIndexTableName(index) _component: "common/Default Button",
}, contentText: `Refresh`,
className: "flex-gow-1 overflow-auto" onClick: [
} {
], "##eventHandlerType": "List Records",
onLoad: [ parameters: {
{ statePath: index.nodeKey(),
"##eventHandlerType": "Set State", indexKey: index.nodeKey(),
parameters: { },
path: `isEditing${record.name}`, },
value: "" ],
} },
}, },
{ ],
"##eventHandlerType": "List Records", },
parameters: { },
statePath: index.nodeKey(), {
indexKey: index.nodeKey() component: {
} _component: "@budibase/standard-components/if",
} condition: `$store.selectedrow_${index.name} && $store.selectedrow_${index.name}.length > 0`,
] thenComponent: {
} _component: "@budibase/standard-components/div",
className: "btn-group",
}); children: [
{
const homePageButtons = ({index, record}) => ({
inherits: "@budibase/standard-components/div",
name: `${record.name}/homepage buttons`,
props: {
className: "btn-toolbar mt-4 mb-2",
children: [
{
component: { component: {
_component: "@budibase/standard-components/div", _component: "common/Default Button",
className: "btn-group mr-3", contentText: `Edit ${record.name}`,
children: [ onClick: [
{ {
component: { "##eventHandlerType": "Load Record",
_component: "common/Default Button", parameters: {
contentText: `Create ${record.name}`, statePath: record.name,
onClick: [ recordKey: {
{ "##bbstate": `selectedrow_${index.name}`,
"##eventHandlerType": "Get New Record", "##source": "store",
parameters: {
statePath: record.name,
collectionKey: `/${record.collectionName}`,
childRecordType: record.name
}
},
{
"##eventHandlerType": "Set State",
parameters: {
path: `isEditing${record.name}`,
value: "true"
}
}
]
}
}, },
{ },
component: { },
_component: "common/Default Button", {
contentText: `Refresh`, "##eventHandlerType": "Set State",
onClick: [ parameters: {
{ path: `isEditing${record.name}`,
"##eventHandlerType": "List Records", value: "true",
parameters: { },
statePath: index.nodeKey(), },
indexKey: index.nodeKey() ],
} },
} },
] {
}
}
]
}
},
{
component: { component: {
_component: "@budibase/standard-components/if", _component: "common/Default Button",
condition: `$store.selectedrow_${index.name} && $store.selectedrow_${index.name}.length > 0`, contentText: `Delete ${record.name}`,
thenComponent: { onClick: [
_component: "@budibase/standard-components/div", {
className: "btn-group", "##eventHandlerType": "Delete Record",
children: [ parameters: {
{ recordKey: {
component: { "##bbstate": `selectedrow_${index.name}`,
_component: "common/Default Button", "##source": "store",
contentText: `Edit ${record.name}`, },
onClick: [ },
{ },
"##eventHandlerType": "Load Record", ],
parameters: { },
statePath: record.name, },
recordKey: { ],
"##bbstate" : `selectedrow_${index.name}`, },
"##source": "store" },
} },
} ],
}, },
{ })
"##eventHandlerType": "Set State",
parameters: {
path: `isEditing${record.name}`,
value: "true"
}
}
]
}
},
{
component: {
_component: "common/Default Button",
contentText: `Delete ${record.name}`,
onClick: [
{
"##eventHandlerType": "Delete Record",
parameters: {
recordKey: {
"##bbstate" : `selectedrow_${index.name}`,
"##source": "store"
}
}
}
]
}
}
]
}
}
}
]
}
})

56
packages/bootstrap-components/src/generators/selectedNavContentGenerator.js

@ -1,36 +1,32 @@
import { import {
recordHomepages, recordHomepages,
homepageComponentName, homepageComponentName,
recordHomePageComponents recordHomePageComponents,
} from "./recordHomePageGenerator"; } from "./recordHomePageGenerator"
import { formName, forms } from "./formsGenerator"; import { formName, forms } from "./formsGenerator"
export const selectNavContent = ({indexes, records, helpers}) => export const selectNavContent = ({ indexes, records, helpers }) => [
[ ...recordHomepages({ indexes, records }).map(component),
...recordHomepages({indexes, records})
.map(component),
...recordHomePageComponents({indexes, records, helpers}), ...recordHomePageComponents({ indexes, records, helpers }),
...forms({indexes, records, helpers})
]
...forms({ indexes, records, helpers }),
]
export const navContentComponentName = record => export const navContentComponentName = record =>
`${record.name}/${record.name} Nav Content`; `${record.name}/${record.name} Nav Content`
const component = ({record, index}) => ({ const component = ({ record }) => ({
inherits: "@budibase/standard-components/if", inherits: "@budibase/standard-components/if",
description: `the component that gets displayed when the ${record.collectionName} nav is selected`, description: `the component that gets displayed when the ${record.collectionName} nav is selected`,
name: navContentComponentName(record), name: navContentComponentName(record),
props: { props: {
condition: `$store.isEditing${record.name}`, condition: `$store.isEditing${record.name}`,
thenComponent: { thenComponent: {
_component: formName(record) _component: formName(record),
}, },
elseComponent: { elseComponent: {
_component: homepageComponentName(record) _component: homepageComponentName(record),
} },
} },
}); })

5
packages/bootstrap-components/src/index.js

@ -1,3 +1,2 @@
export {default as form} from "./Form.svelte"; export { default as form } from "./Form.svelte"
export {default as nav} from "./Nav.svelte"; export { default as nav } from "./Nav.svelte"

25
packages/builder/babel.config.js

@ -1,12 +1,13 @@
module.exports = ({ module.exports = {
"presets": ["@babel/preset-env"], presets: ["@babel/preset-env"],
"sourceMaps": "inline", sourceMaps: "inline",
"retainLines": true, retainLines: true,
"plugins": [ plugins: [
["@babel/plugin-transform-runtime", [
{ "@babel/plugin-transform-runtime",
"regenerator": true {
} regenerator: true,
] },
] ],
}); ],
}

16
packages/builder/build/copy.js

@ -1,8 +1,8 @@
const ncp = require('ncp').ncp; const ncp = require("ncp").ncp
ncp("./dist", "../server/builder", function (err) { ncp("./dist", "../server/builder", function(err) {
if (err) { if (err) {
return console.error(err); return console.error(err)
} }
console.log('Copied dist folder to ../server/builder'); console.log("Copied dist folder to ../server/builder")
}) })

316
packages/builder/rollup.config.js

@ -1,126 +1,218 @@
import svelte from 'rollup-plugin-svelte'; import svelte from "rollup-plugin-svelte"
import resolve from 'rollup-plugin-node-resolve'; import resolve from "rollup-plugin-node-resolve"
import commonjs from 'rollup-plugin-commonjs'; import commonjs from "rollup-plugin-commonjs"
import url from 'rollup-plugin-url'; import url from "rollup-plugin-url"
import livereload from 'rollup-plugin-livereload'; import livereload from "rollup-plugin-livereload"
import { terser } from 'rollup-plugin-terser'; import { terser } from "rollup-plugin-terser"
import builtins from 'rollup-plugin-node-builtins'; import builtins from "rollup-plugin-node-builtins"
import nodeglobals from 'rollup-plugin-node-globals'; import nodeglobals from "rollup-plugin-node-globals"
import copy from 'rollup-plugin-copy'; import copy from "rollup-plugin-copy"
import browsersync from "rollup-plugin-browsersync"; import browsersync from "rollup-plugin-browsersync"
import proxy from "http-proxy-middleware"; import proxy from "http-proxy-middleware"
const target = 'http://localhost:4001'; const target = "http://localhost:4001"
const _builderProxy = proxy('/_builder', { const _builderProxy = proxy("/_builder", {
target: "http://localhost:3000", target: "http://localhost:3000",
pathRewrite: { '^/_builder': '' } pathRewrite: { "^/_builder": "" },
}); })
const apiProxy = proxy(['/_builder/api/**', '/_builder/**/componentlibrary', '/_builder/**/componentlibraryGenerators'], { const apiProxy = proxy(
target, [
logLevel: "debug", "/_builder/api/**",
changeOrigin: true, "/_builder/**/componentlibrary",
cookieDomainRewrite: true, "/_builder/**/componentlibraryGenerators",
onProxyReq(proxyReq) { ],
if (proxyReq.getHeader("origin")) { {
proxyReq.setHeader("origin", target) target,
} logLevel: "debug",
} changeOrigin: true,
}); cookieDomainRewrite: true,
onProxyReq(proxyReq) {
if (proxyReq.getHeader("origin")) {
proxyReq.setHeader("origin", target)
}
},
}
)
const production = !process.env.ROLLUP_WATCH; const production = !process.env.ROLLUP_WATCH
const lodash_fp_exports = ["union", "reduce", "isUndefined", "cloneDeep", "split", "some", "map", "filter", "isEmpty", "countBy", "includes", "last", "find", "constant", const lodash_fp_exports = [
"take", "first", "intersection", "mapValues", "isNull", "has", "isInteger", "isNumber", "isString", "isBoolean", "isDate", "isArray", "isObject", "clone", "values", "keyBy", "isNaN", "union",
"keys", "orderBy", "concat", "reverse", "difference", "merge", "flatten", "each", "pull", "join", "defaultCase", "uniqBy", "every", "uniqWith", "isFunction", "groupBy", "reduce",
"differenceBy", "intersectionBy", "isEqual", "max", "sortBy", "assign", "uniq", "trimChars", "trimCharsStart", "isObjectLike", "flattenDeep", "indexOf", "isPlainObject", "isUndefined",
"toNumber", "takeRight", "toPairs"]; "cloneDeep",
"split",
"some",
"map",
"filter",
"isEmpty",
"countBy",
"includes",
"last",
"find",
"constant",
"take",
"first",
"intersection",
"mapValues",
"isNull",
"has",
"isInteger",
"isNumber",
"isString",
"isBoolean",
"isDate",
"isArray",
"isObject",
"clone",
"values",
"keyBy",
"isNaN",
"keys",
"orderBy",
"concat",
"reverse",
"difference",
"merge",
"flatten",
"each",
"pull",
"join",
"defaultCase",
"uniqBy",
"every",
"uniqWith",
"isFunction",
"groupBy",
"differenceBy",
"intersectionBy",
"isEqual",
"max",
"sortBy",
"assign",
"uniq",
"trimChars",
"trimCharsStart",
"isObjectLike",
"flattenDeep",
"indexOf",
"isPlainObject",
"toNumber",
"takeRight",
"toPairs",
]
const lodash_exports = ["flow", "join", "replace", "trim", "dropRight", "takeRight", "head", "reduce", const lodash_exports = [
"tail", "startsWith", "findIndex", "merge", "flow",
"assign", "each", "find", "orderBy", "union"]; "join",
"replace",
"trim",
"dropRight",
"takeRight",
"head",
"reduce",
"tail",
"startsWith",
"findIndex",
"merge",
"assign",
"each",
"find",
"orderBy",
"union",
]
const outputpath = "../server/builder"; const outputpath = "../server/builder"
const coreExternal = [ const coreExternal = [
"lodash", "lodash/fp", "date-fns", "lodash",
"lunr", "safe-buffer", "shortid", "lodash/fp",
"@nx-js/compiler-util" "date-fns",
]; "lunr",
"safe-buffer",
"shortid",
"@nx-js/compiler-util",
]
export default { export default {
input: 'src/main.js', input: "src/main.js",
output: { output: {
sourcemap: true, sourcemap: true,
format: 'iife', format: "iife",
name: 'app', name: "app",
file: `${outputpath}/bundle.js` file: `${outputpath}/bundle.js`,
}, },
plugins: [ plugins: [
copy({ copy({
targets: [ targets: [
{ src: 'src/index.html', dest: outputpath }, { src: "src/index.html", dest: outputpath },
{ src: 'src/favicon.png', dest: outputpath }, { src: "src/favicon.png", dest: outputpath },
{ src: 'src/assets', dest: outputpath }, { src: "src/assets", dest: outputpath },
{ src: 'node_modules/@budibase/client/dist/budibase-client.esm.mjs', dest: outputpath }, {
] src: "node_modules/@budibase/client/dist/budibase-client.esm.mjs",
}), dest: outputpath,
},
],
}),
svelte({ svelte({
// enable run-time checks when not in production // enable run-time checks when not in production
dev: !production, dev: !production,
include: 'src/**/*.svelte', include: "src/**/*.svelte",
// we'll extract any component CSS out into // we'll extract any component CSS out into
// a separate file — better for performance // a separate file — better for performance
css: css => { css: css => {
css.write(`${outputpath}/bundle.css`); css.write(`${outputpath}/bundle.css`)
} },
}), }),
resolve({ resolve({
browser: true, browser: true,
dedupe: importee => { dedupe: importee => {
return importee === 'svelte' return (
|| importee.startsWith('svelte/') importee === "svelte" ||
|| coreExternal.includes(importee); importee.startsWith("svelte/") ||
} coreExternal.includes(importee)
)
},
}),
commonjs({
namedExports: {
"lodash/fp": lodash_fp_exports,
lodash: lodash_exports,
shortid: ["generate"],
},
}),
url({
limit: 0,
include: ["**/*.woff2", "**/*.png"],
fileName: "[dirname][name][extname]",
emitFiles: true,
}),
url({
limit: 0,
include: ["**/*.css"],
fileName: "[name][extname]",
emitFiles: true,
}),
builtins(),
nodeglobals(),
}), // Watch the `dist` directory and refresh the
commonjs({ // browser on changes when not in production
namedExports: { !production && livereload(outputpath),
"lodash/fp": lodash_fp_exports, !production &&
"lodash": lodash_exports, browsersync({
"shortid": ["generate"] server: outputpath,
} middleware: [apiProxy, _builderProxy],
}), }),
url({
limit: 0,
include: ["**/*.woff2", "**/*.png"],
fileName: "[dirname][name][extname]",
emitFiles: true
}),
url({
limit: 0,
include: ["**/*.css"],
fileName: "[name][extname]",
emitFiles: true
}),
builtins(),
nodeglobals(),
// Watch the `dist` directory and refresh the // If we're building for production (npm run build
// browser on changes when not in production // instead of npm run dev), minify
!production && livereload(outputpath), production && terser(),
!production && browsersync({ ],
server: outputpath, watch: {
middleware: [apiProxy, _builderProxy] clearScreen: false,
}), },
}
// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser()
],
watch: {
clearScreen: false
}
};

31
packages/builder/src/builderStore/api.js

@ -1,17 +1,20 @@
const apiCall = (method) => (url, body) => const apiCall = method => (url, body) =>
fetch(url, { fetch(url, {
method: method, method: method,
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
}, },
body: body && JSON.stringify(body), body: body && JSON.stringify(body),
}); })
const post = apiCall("POST"); const post = apiCall("POST")
const get = apiCall("GET"); const get = apiCall("GET")
const patch = apiCall("PATCH"); const patch = apiCall("PATCH")
const del = apiCall("DELETE"); const del = apiCall("DELETE")
export default { export default {
post, get, patch, delete:del post,
}; get,
patch,
delete: del,
}

44
packages/builder/src/builderStore/buildCodeForScreens.js

@ -1,35 +1,33 @@
const buildCodeForSingleScreen = screen => {
let code = ""
const walkProps = props => {
if (props._code && props._code.trim().length > 0) {
code += buildComponentCode(props)
}
if (!props._children) return
const buildCodeForSingleScreen = (screen) => { for (let child of props._children) {
let code = ""; walkProps(child)
const walkProps = (props) => {
if(props._code && props._code.trim().length > 0) {
code += buildComponentCode(props)
}
if(!props._children) return;
for(let child of props._children) {
walkProps(child);
}
} }
}
walkProps(screen.props); walkProps(screen.props)
return code; return code
} }
export const buildCodeForScreens = screens => { export const buildCodeForScreens = screens => {
let allfunctions = ""; let allfunctions = ""
for(let screen of screens) { for (let screen of screens) {
allfunctions += buildCodeForSingleScreen(screen); allfunctions += buildCodeForSingleScreen(screen)
} }
return (`return ({ ${allfunctions} });`); return `return ({ ${allfunctions} });`
} }
const buildComponentCode = (componentProps) => const buildComponentCode = componentProps =>
`"${componentProps._id}" : (render, context) => { `"${componentProps._id}" : (render, context) => {
${componentProps._code} ${componentProps._code}
}, },
`; `

20
packages/builder/src/builderStore/createPackage.js

@ -1,12 +1,12 @@
import {createNewHierarchy} from "../common/core"; import { createNewHierarchy } from "../common/core"
export const createPackage = (packageInfo, store) => { export const createPackage = (packageInfo, store) => {
packageInfo.createNewPackage(""); packageInfo.createNewPackage("")
const root = createNewHierarchy(); const root = createNewHierarchy()
store.importAppDefinition({ store.importAppDefinition({
hierarchy:root, hierarchy: root,
actions:[], actions: [],
triggers:[], triggers: [],
accessLevels: {version:0, levels:[]} accessLevels: { version: 0, levels: [] },
}); })
}; }

183
packages/builder/src/builderStore/generate_css.js

@ -1,118 +1,113 @@
import { filter, map, reduce, toPairs } from "lodash/fp"; import { filter, map, reduce, toPairs } from "lodash/fp"
import { pipe } from "../common/core"; import { pipe } from "../common/core"
const self = n => n; const self = n => n
const join_with = delimiter => a => a.join(delimiter); const join_with = delimiter => a => a.join(delimiter)
const empty_string_to_unset = s => s.length ? s : "0"; const empty_string_to_unset = s => (s.length ? s : "0")
const add_suffix = suffix => s => s + suffix; const add_suffix = suffix => s => s + suffix
export const make_margin = (values) => pipe(values, [ export const make_margin = values =>
map(empty_string_to_unset), pipe(values, [
map(add_suffix('px')), map(empty_string_to_unset),
join_with(' ') map(add_suffix("px")),
]); join_with(" "),
])
const tap = message => x => {
console.log(x);
return x;
}
const css_map = { const css_map = {
templaterows: { templaterows: {
name: 'grid-template-columns', name: "grid-template-columns",
generate: self generate: self,
}, },
templatecolumns: { templatecolumns: {
name: 'grid-template-rows', name: "grid-template-rows",
generate: self generate: self,
}, },
gridarea: { gridarea: {
name: 'grid-area', name: "grid-area",
generate: make_margin generate: make_margin,
}, },
gap: { gap: {
name: 'grid-gap', name: "grid-gap",
generate: n => `${n}px` generate: n => `${n}px`,
}, },
columnstart: { columnstart: {
name: 'grid-column-start', name: "grid-column-start",
generate: self generate: self,
}, },
columnend: { columnend: {
name: 'grid-column-end', name: "grid-column-end",
generate: self generate: self,
}, },
rowstart: { rowstart: {
name: 'grid-row-start', name: "grid-row-start",
generate: self generate: self,
}, },
rowend: { rowend: {
name: 'grid-row-end', name: "grid-row-end",
generate: self generate: self,
}, },
padding: { padding: {
name: 'padding', name: "padding",
generate: make_margin generate: make_margin,
}, },
margin: { margin: {
name: 'margin', name: "margin",
generate: make_margin generate: make_margin,
}, },
zindex: { zindex: {
name: 'z-index', name: "z-index",
generate: self generate: self,
} },
} }
export const generate_rule = ([name, values]) => export const generate_rule = ([name, values]) =>
`${css_map[name].name}: ${css_map[name].generate(values)};` `${css_map[name].name}: ${css_map[name].generate(values)};`
const handle_grid = (acc, [name, value]) => { const handle_grid = (acc, [name, value]) => {
let tmp = []; let tmp = []
if (name === 'row' || name === 'column') { if (name === "row" || name === "column") {
if (value[0]) tmp.push([`${name}start`, value[0]]); if (value[0]) tmp.push([`${name}start`, value[0]])
if (value[1]) tmp.push([`${name}end`, value[1]]); if (value[1]) tmp.push([`${name}end`, value[1]])
return acc.concat(tmp) return acc.concat(tmp)
} }
return acc.concat([[name, value]]); return acc.concat([[name, value]])
} }
const object_to_css_string = [ const object_to_css_string = [
toPairs, toPairs,
reduce(handle_grid, []), reduce(handle_grid, []),
filter(v => Array.isArray(v[1]) ? v[1].some(s => s.length) : v[1].length), filter(v => (Array.isArray(v[1]) ? v[1].some(s => s.length) : v[1].length)),
map(generate_rule), map(generate_rule),
join_with('\n'), join_with("\n"),
]; ]
export const generate_css = ({ layout, position }) => { export const generate_css = ({ layout, position }) => {
let _layout = pipe(layout, object_to_css_string); let _layout = pipe(layout, object_to_css_string)
_layout = _layout.length ? _layout + "\ndisplay: grid;" : _layout; _layout = _layout.length ? _layout + "\ndisplay: grid;" : _layout
return { return {
layout: _layout, layout: _layout,
position: pipe(position, object_to_css_string) position: pipe(position, object_to_css_string),
} }
} }
const apply_class = (id, name, styles) => `.${name}-${id} {\n${styles}\n}`; const apply_class = (id, name, styles) => `.${name}-${id} {\n${styles}\n}`
export const generate_screen_css = (component_array) => {
let styles = "";
for (let i = 0; i < component_array.length; i += 1) { export const generate_screen_css = component_array => {
const { _styles, _id, _children } = component_array[i]; let styles = ""
const { layout, position } = generate_css(_styles);
styles += apply_class(_id, 'pos', position) + "\n"; for (let i = 0; i < component_array.length; i += 1) {
styles += apply_class(_id, 'lay', layout) + "\n"; const { _styles, _id, _children } = component_array[i]
const { layout, position } = generate_css(_styles)
if (_children && _children.length) { styles += apply_class(_id, "pos", position) + "\n"
styles += generate_screen_css(_children) + "\n"; styles += apply_class(_id, "lay", layout) + "\n"
}
} if (_children && _children.length) {
return styles.trim(); styles += generate_screen_css(_children) + "\n"
}
}
return styles.trim()
} }

44
packages/builder/src/builderStore/index.js

@ -1,29 +1,27 @@
import {createPackage} from "./createPackage"; import { createPackage } from "./createPackage"
import getStore from "./store"; import getStore from "./store"
export const store = getStore(); export const store = getStore()
export const createNewPackage = () => export const createNewPackage = () => createPackage(packageInfo, store)
createPackage(packageInfo, store);
export const initialise = async () => { export const initialise = async () => {
try { try {
setupRouter(store); setupRouter(store)
await store.initialise(); await store.initialise()
} catch(err) { } catch (err) {
console.log(err); console.log(err)
} }
}
}
const setupRouter = (writable) => { const setupRouter = writable => {
const pushState = history.pushState; const pushState = history.pushState
history.pushState = () => { history.pushState = () => {
pushState.apply(history, arguments); pushState.apply(history, arguments)
//fireEvents('pushState', arguments); //fireEvents('pushState', arguments);
writable.initialise(); writable.initialise()
} }
window.addEventListener('hashchange',()=>{ window.addEventListener("hashchange", () => {
writable.initialise(); writable.initialise()
}) })
} }

57
packages/builder/src/builderStore/loadComponentLibraries.js

@ -1,50 +1,47 @@
import { map } from "lodash/fp"; import { map } from "lodash/fp"
export const loadLibs = async (appName, appPackage) => { export const loadLibs = async (appName, appPackage) => {
const allLibraries = {}
for (let lib of appPackage.pages.componentLibraries) {
const libModule = await import(makeLibraryUrl(appName, lib))
allLibraries[lib] = libModule
}
const allLibraries = {}; return allLibraries
for(let lib of appPackage.pages.componentLibraries) {
const libModule = await import(makeLibraryUrl(appName, lib));
allLibraries[lib] = libModule;
}
return allLibraries;
} }
export const loadGeneratorLibs = async (appName, appPackage) => { export const loadGeneratorLibs = async (appName, appPackage) => {
const allGeneratorLibs = {}
for (let lib of appPackage.pages.componentLibraries) {
const generatorModule = await import(makeGeneratorLibraryUrl(appName, lib))
allGeneratorLibs[lib] = generatorModule
}
const allGeneratorLibs = {}; return allGeneratorLibs
for(let lib of appPackage.pages.componentLibraries) {
const generatorModule = await import(makeGeneratorLibraryUrl(appName, lib));
allGeneratorLibs[lib] = generatorModule;
}
return allGeneratorLibs;
} }
export const loadLibUrls = (appName, appPackage) => { export const loadLibUrls = (appName, appPackage) => {
const allLibraries = []
for (let lib of appPackage.pages.componentLibraries) {
const libUrl = makeLibraryUrl(appName, lib)
allLibraries.push({ libName: lib, importPath: libUrl })
}
const allLibraries = []; return allLibraries
for(let lib of appPackage.pages.componentLibraries) {
const libUrl = makeLibraryUrl(appName, lib);
allLibraries.push({libName:lib, importPath:libUrl});
}
return allLibraries;
} }
export const loadLib = async (appName, lib, allLibs) => { export const loadLib = async (appName, lib, allLibs) => {
allLibs[lib] = await import(makeLibraryUrl(appName, lib)); allLibs[lib] = await import(makeLibraryUrl(appName, lib))
return allLibs; return allLibs
} }
export const loadGeneratorLib = async (appName, lib, allGeneratorLibs) => { export const loadGeneratorLib = async (appName, lib, allGeneratorLibs) => {
allGeneratorLibs[lib] = await import(makeGeneratorLibraryUrl(appName, lib)); allGeneratorLibs[lib] = await import(makeGeneratorLibraryUrl(appName, lib))
return allGeneratorLibs; return allGeneratorLibs
} }
export const makeLibraryUrl = (appName, lib) => export const makeLibraryUrl = (appName, lib) =>
`/_builder/${appName}/componentlibrary?lib=${encodeURI(lib)}` `/_builder/${appName}/componentlibrary?lib=${encodeURI(lib)}`
export const makeGeneratorLibraryUrl = (appName, lib) => export const makeGeneratorLibraryUrl = (appName, lib) =>
`/_builder/${appName}/componentlibraryGenerators?lib=${encodeURI(lib)}` `/_builder/${appName}/componentlibraryGenerators?lib=${encodeURI(lib)}`

1290
packages/builder/src/builderStore/store.js

File diff suppressed because it is too large

799
packages/builder/src/builderStore/store.js.orig

@ -1,799 +0,0 @@
import {
hierarchy as hierarchyFunctions,
} from "../../../core/src";
import {
filter, cloneDeep, sortBy,
map, last, keys, concat, keyBy,
find, isEmpty, values,
} from "lodash/fp";
import {
pipe, getNode, validate,
constructHierarchy, templateApi
} from "../common/core";
import { writable } from "svelte/store";
import { defaultPagesObject } from "../userInterface/pagesParsing/defaultPagesObject"
import { buildPropsHierarchy } from "../userInterface/pagesParsing/buildPropsHierarchy"
import api from "./api";
import { isRootComponent, getExactComponent } from "../userInterface/pagesParsing/searchComponents";
import { rename } from "../userInterface/pagesParsing/renameScreen";
import {
getNewComponentInfo, getScreenInfo,
} from "../userInterface/pagesParsing/createProps";
import {
loadLibs, loadLibUrls, loadGeneratorLibs
} from "./loadComponentLibraries";
<<<<<<< HEAD
import { buildCodeForScreens } from "./buildCodeForScreens";
=======
import { uuid } from './uuid';
import { generate_screen_css } from './generate_css';
>>>>>>> master
let appname = "";
export const getStore = () => {
const initial = {
apps: [],
appname: "",
hierarchy: {},
actions: [],
triggers: [],
pages: defaultPagesObject(),
mainUi: {},
unauthenticatedUi: {},
components: [],
currentFrontEndItem: null,
currentComponentInfo: null,
currentFrontEndType: "none",
currentPageName: "",
currentComponentProps: null,
currentNodeIsNew: false,
errors: [],
activeNav: "database",
isBackend: true,
hasAppPackage: false,
accessLevels: { version: 0, levels: [] },
currentNode: null,
libraries: null,
showSettings: false,
useAnalytics: true,
};
const store = writable(initial);
store.initialise = initialise(store, initial);
store.newChildRecord = newRecord(store, false);
store.newRootRecord = newRecord(store, true);
store.selectExistingNode = selectExistingNode(store);
store.newChildIndex = newIndex(store, false);
store.newRootIndex = newIndex(store, true);
store.saveCurrentNode = saveCurrentNode(store);
store.importAppDefinition = importAppDefinition(store);
store.deleteCurrentNode = deleteCurrentNode(store);
store.saveField = saveField(store);
store.deleteField = deleteField(store);
store.saveAction = saveAction(store);
store.deleteAction = deleteAction(store);
store.saveTrigger = saveTrigger(store);
store.deleteTrigger = deleteTrigger(store);
store.saveLevel = saveLevel(store);
store.deleteLevel = deleteLevel(store);
store.setActiveNav = setActiveNav(store);
store.saveScreen = saveScreen(store);
store.refreshComponents = refreshComponents(store);
store.addComponentLibrary = addComponentLibrary(store);
store.renameScreen = renameScreen(store);
store.deleteScreen = deleteScreen(store);
store.setCurrentScreen = setCurrentScreen(store);
store.setCurrentPage = setCurrentPage(store);
store.createScreen = createScreen(store);
store.removeComponentLibrary = removeComponentLibrary(store);
store.addStylesheet = addStylesheet(store);
store.removeStylesheet = removeStylesheet(store);
store.savePage = savePage(store);
store.showFrontend = showFrontend(store);
store.showBackend = showBackend(store);
store.showSettings = showSettings(store);
store.useAnalytics = useAnalytics(store);
store.createGeneratedComponents = createGeneratedComponents(store);
store.addChildComponent = addChildComponent(store);
store.selectComponent = selectComponent(store);
store.setComponentProp = setComponentProp(store);
store.setComponentStyle = setComponentStyle(store);
store.setComponentCode = setComponentCode(store);
return store;
}
export default getStore;
const initialise = (store, initial) => async () => {
appname = window.location.hash
? last(window.location.hash.substr(1).split("/"))
: "";
if (!appname) {
initial.apps = await api.get(`/_builder/api/apps`).then(r => r.json());
initial.hasAppPackage = false;
store.set(initial);
return initial;
}
const pkg = await api.get(`/_builder/api/${appname}/appPackage`)
.then(r => r.json());
initial.libraries = await loadLibs(appname, pkg);
initial.generatorLibraries = await loadGeneratorLibs(appname, pkg);
initial.loadLibraryUrls = () => loadLibUrls(appname, pkg);
initial.appname = appname;
initial.pages = pkg.pages;
initial.hasAppPackage = true;
initial.hierarchy = pkg.appDefinition.hierarchy;
initial.accessLevels = pkg.accessLevels;
initial.screens = values(pkg.screens);
initial.generators = generatorsArray(pkg.components.generators);
initial.components = values(pkg.components.components);
initial.actions = values(pkg.appDefinition.actions);
initial.triggers = pkg.appDefinition.triggers;
if (!!initial.hierarchy && !isEmpty(initial.hierarchy)) {
initial.hierarchy = constructHierarchy(initial.hierarchy);
const shadowHierarchy = createShadowHierarchy(initial.hierarchy);
if (initial.currentNode !== null)
initial.currentNode = getNode(
shadowHierarchy, initial.currentNode.nodeId
);
}
store.set(initial);
return initial;
}
const generatorsArray = generators =>
pipe(generators, [
keys,
filter(k => k !== "_lib"),
map(k => generators[k])
]);
const showSettings = store => show => {
store.update(s => {
s.showSettings = !s.showSettings;
return s;
});
}
const useAnalytics = store => useAnalytics => {
store.update(s => {
s.useAnalytics = !s.useAnalytics;
return s;
});
}
const showBackend = store => () => {
store.update(s => {
s.isBackend = true;
return s;
})
}
const showFrontend = store => () => {
store.update(s => {
s.isBackend = false;
return s;
})
}
const newRecord = (store, useRoot) => () => {
store.update(s => {
s.currentNodeIsNew = true;
const shadowHierarchy = createShadowHierarchy(s.hierarchy);
parent = useRoot ? shadowHierarchy
: getNode(
shadowHierarchy,
s.currentNode.nodeId);
s.errors = [];
s.currentNode = templateApi(shadowHierarchy)
.getNewRecordTemplate(parent, "", true);
return s;
});
}
const selectExistingNode = (store) => (nodeId) => {
store.update(s => {
const shadowHierarchy = createShadowHierarchy(s.hierarchy);
s.currentNode = getNode(
shadowHierarchy, nodeId
);
s.currentNodeIsNew = false;
s.errors = [];
s.activeNav = "database";
return s;
})
}
const newIndex = (store, useRoot) => () => {
store.update(s => {
s.currentNodeIsNew = true;
s.errors = [];
const shadowHierarchy = createShadowHierarchy(s.hierarchy);
parent = useRoot ? shadowHierarchy
: getNode(
shadowHierarchy,
s.currentNode.nodeId);
s.currentNode = templateApi(shadowHierarchy)
.getNewIndexTemplate(parent);
return s;
});
}
const saveCurrentNode = (store) => () => {
store.update(s => {
const errors = validate.node(s.currentNode);
s.errors = errors;
if (errors.length > 0) {
return s;
}
const parentNode = getNode(
s.hierarchy, s.currentNode.parent().nodeId);
const existingNode = getNode(
s.hierarchy, s.currentNode.nodeId);
let index = parentNode.children.length;
if (!!existingNode) {
// remove existing
index = existingNode.parent().children.indexOf(existingNode);
existingNode.parent().children = pipe(existingNode.parent().children, [
filter(c => c.nodeId !== existingNode.nodeId)
]);
}
// should add node into existing hierarchy
const cloned = cloneDeep(s.currentNode);
templateApi(s.hierarchy).constructNode(
parentNode,
cloned
);
const newIndexOfchild = child => {
if (child === cloned) return index;
const currentIndex = parentNode.children.indexOf(child);
return currentIndex >= index ? currentIndex + 1 : currentIndex;
}
parentNode.children = pipe(parentNode.children, [
sortBy(newIndexOfchild)
]);
if (!existingNode && s.currentNode.type === "record") {
const defaultIndex = templateApi(s.hierarchy)
.getNewIndexTemplate(cloned.parent());
defaultIndex.name = `all_${cloned.collectionName}`;
defaultIndex.allowedRecordNodeIds = [cloned.nodeId];
}
s.currentNodeIsNew = false;
savePackage(store, s);
return s;
});
}
const importAppDefinition = store => appDefinition => {
store.update(s => {
s.hierarchy = appDefinition.hierarchy;
s.currentNode = appDefinition.hierarchy.children.length > 0
? appDefinition.hierarchy.children[0]
: null;
s.actions = appDefinition.actions;
s.triggers = appDefinition.triggers;
s.currentNodeIsNew = false;
return s;
});
}
const deleteCurrentNode = store => () => {
store.update(s => {
const nodeToDelete = getNode(s.hierarchy, s.currentNode.nodeId);
s.currentNode = hierarchyFunctions.isRoot(nodeToDelete.parent())
? find(n => n != s.currentNode)
(s.hierarchy.children)
: nodeToDelete.parent();
if (hierarchyFunctions.isRecord(nodeToDelete)) {
nodeToDelete.parent().children = filter(c => c.nodeId !== nodeToDelete.nodeId)
(nodeToDelete.parent().children);
} else {
nodeToDelete.parent().indexes = filter(c => c.nodeId !== nodeToDelete.nodeId)
(nodeToDelete.parent().indexes);
}
s.errors = [];
savePackage(store, s);
return s;
});
}
const saveField = databaseStore => (field) => {
databaseStore.update(db => {
db.currentNode.fields = filter(f => f.name !== field.name)
(db.currentNode.fields);
templateApi(db.hierarchy).addField(db.currentNode, field);
return db;
});
}
const deleteField = databaseStore => field => {
databaseStore.update(db => {
db.currentNode.fields = filter(f => f.name !== field.name)
(db.currentNode.fields);
return db;
});
}
const saveAction = store => (newAction, isNew, oldAction = null) => {
store.update(s => {
const existingAction = isNew
? null
: find(a => a.name === oldAction.name)(s.actions);
if (existingAction) {
s.actions = pipe(s.actions, [
map(a => a === existingAction ? newAction : a)
]);
} else {
s.actions.push(newAction);
}
savePackage(store, s);
return s;
});
}
const deleteAction = store => action => {
store.update(s => {
s.actions = filter(a => a.name !== action.name)(s.actions);
savePackage(store, s);
return s;
});
}
const saveTrigger = store => (newTrigger, isNew, oldTrigger = null) => {
store.update(s => {
const existingTrigger = isNew
? null
: find(a => a.name === oldTrigger.name)(s.triggers);
if (existingTrigger) {
s.triggers = pipe(s.triggers, [
map(a => a === existingTrigger ? newTrigger : a)
]);
} else {
s.triggers.push(newTrigger);
}
savePackage(store, s);
return s;
});
}
const deleteTrigger = store => trigger => {
store.update(s => {
s.triggers = filter(t => t.name !== trigger.name)(s.triggers);
return s;
});
}
const incrementAccessLevelsVersion = (s) =>
s.accessLevels.version = (s.accessLevels.version || 0) + 1;
const saveLevel = store => (newLevel, isNew, oldLevel = null) => {
store.update(s => {
const levels = s.accessLevels.levels;
const existingLevel = isNew
? null
: find(a => a.name === oldLevel.name)(levels);
if (existingLevel) {
s.accessLevels.levels = pipe(levels, [
map(a => a === existingLevel ? newLevel : a)
]);
} else {
s.accessLevels.levels.push(newLevel);
}
incrementAccessLevelsVersion(s);
savePackage(store, s);
return s;
});
}
const deleteLevel = store => level => {
store.update(s => {
s.accessLevels.levels = filter(t => t.name !== level.name)(s.accessLevels.levels);
incrementAccessLevelsVersion(s);
savePackage(store, s);
return s;
});
}
const setActiveNav = store => navName => {
store.update(s => {
s.activeNav = navName;
return s;
});
}
const createShadowHierarchy = hierarchy =>
constructHierarchy(JSON.parse(JSON.stringify(hierarchy)));
const saveScreen = store => (screen) => {
store.update(s => {
return _saveScreen(store, s, screen);
})
};
const _saveScreen = (store, s, screen) => {
const screens = pipe(s.screens, [
filter(c => c.name !== screen.name),
concat([screen])
]);
s.screens = screens;
s.currentFrontEndItem = screen;
s.currentComponentInfo = getScreenInfo(
s.components, screen);
api.post(`/_builder/api/${s.appname}/screen`, screen)
.then(() => savePackage(store, s));
return s;
}
const _save = (appname, screen, store, s) =>
api.post(`/_builder/api/${appname}/screen`, screen)
.then(() => savePackage(store, s));
const createScreen = store => (screenName, layoutComponentName) => {
store.update(s => {
const newComponentInfo = getNewComponentInfo(
s.components, layoutComponentName, screenName);
s.currentFrontEndItem = newComponentInfo.component;
s.currentComponentInfo = newComponentInfo;
s.currentFrontEndType = "screen";
return _saveScreen(store, s, newComponentInfo.component);
});
};
const createGeneratedComponents = store => components => {
store.update(s => {
s.components = [...s.components, ...components];
s.screens = [...s.screens, ...components];
const doCreate = async () => {
for (let c of components) {
await api.post(`/_builder/api/${s.appname}/screen`, c);
}
await savePackage(store, s);
}
doCreate();
return s;
});
};
const deleteScreen = store => name => {
store.update(s => {
const components = pipe(s.components, [
filter(c => c.name !== name)
]);
const screens = pipe(s.screens, [
filter(c => c.name !== name)
]);
s.components = components;
s.screens = screens;
if (s.currentFrontEndItem.name === name) {
s.currentFrontEndItem = null;
s.currentFrontEndType = "";
}
api.delete(`/_builder/api/${s.appname}/screen/${name}`);
return s;
})
}
const renameScreen = store => (oldname, newname) => {
store.update(s => {
const {
screens, pages, error, changedScreens
} = rename(s.pages, s.screens, oldname, newname);
if (error) {
// should really do something with this
return s;
}
s.screens = screens;
s.pages = pages;
if (s.currentFrontEndItem.name === oldname)
s.currentFrontEndItem.name = newname;
const saveAllChanged = async () => {
for (let screenName of changedScreens) {
const changedScreen
= getExactComponent(screens, screenName);
await api.post(`/_builder/api/${s.appname}/screen`, changedScreen);
}
}
api.patch(`/_builder/api/${s.appname}/screen`, {
oldname, newname
})
.then(() => saveAllChanged())
.then(() => {
savePackage(store, s);
});
return s;
})
}
const savePage = store => async page => {
store.update(s => {
if (s.currentFrontEndType !== "page" || !s.currentPageName) {
return s;
}
s.pages[s.currentPageName] = page;
savePackage(store, s);
return s;
});
}
const addComponentLibrary = store => async lib => {
const response =
await api.get(`/_builder/api/${appname}/componentlibrary?lib=${encodeURI(lib)}`, undefined, false);
const success = response.status === 200;
const error = response.status === 404
? `Could not find library ${lib}`
: success
? ""
: response.statusText;
const components = success
? await response.json()
: [];
store.update(s => {
if (success) {
const componentsArray = [];
for (let c in components) {
componentsArray.push(components[c]);
}
s.components = pipe(s.components, [
filter(c => !c.name.startsWith(`${lib}/`)),
concat(componentsArray)
]);
s.pages.componentLibraries.push(lib);
savePackage(store, s);
}
return s;
})
}
const removeComponentLibrary = store => lib => {
store.update(s => {
s.pages.componentLibraries = filter(l => l !== lib)(
s.pages.componentLibraries);
savePackage(store, s);
return s;
})
}
const addStylesheet = store => stylesheet => {
store.update(s => {
s.pages.stylesheets.push(stylesheet);
savePackage(store, s);
return s;
})
}
const removeStylesheet = store => stylesheet => {
store.update(s => {
s.pages.stylesheets = filter(s => s !== stylesheet)(s.pages.stylesheets);
savePackage(store, s);
return s;
});
}
const refreshComponents = store => async () => {
const componentsAndGenerators =
await api.get(`/_builder/api/${db.appname}/components`).then(r => r.json());
const components = pipe(componentsAndGenerators.components, [
keys,
map(k => ({ ...componentsAndGenerators[k], name: k }))
]);
store.update(s => {
s.components = pipe(s.components, [
filter(c => !isRootComponent(c)),
concat(components)
]);
s.generators = componentsAndGenerators.generators;
return s;
});
};
const savePackage = (store, s) => {
const appDefinition = {
hierarchy: s.hierarchy,
triggers: s.triggers,
actions: keyBy("name")(s.actions),
props: {
main: buildPropsHierarchy(
s.components,
s.screens,
s.pages.main.appBody),
unauthenticated: buildPropsHierarchy(
s.components,
s.screens,
s.pages.unauthenticated.appBody)
},
uiFunctions: buildCodeForScreens(s.screens)
};
const data = {
appDefinition,
accessLevels: s.accessLevels,
pages: s.pages,
}
return api.post(`/_builder/api/${s.appname}/appPackage`, data);
}
const setCurrentScreen = store => screenName => {
store.update(s => {
const screen = getExactComponent(s.screens, screenName);
s.currentFrontEndItem = screen;
s.currentFrontEndType = "screen";
s.currentComponentInfo = getScreenInfo(s.components, screen);
setCurrentScreenFunctions(s);
return s;
})
}
const setCurrentPage = store => pageName => {
store.update(s => {
s.currentFrontEndType = "page";
s.currentPageName = pageName;
setCurrentScreenFunctions(s);
return s;
});
}
const addChildComponent = store => component => {
store.update(s => {
const newComponent = getNewComponentInfo(
s.components, component);
let children = s.currentComponentInfo.component ?
s.currentComponentInfo.component.props._children :
s.currentComponentInfo._children;
const component_definition = Object.assign(
cloneDeep(newComponent.fullProps), {
_component: component,
_styles: { position: {}, layout: {} },
_id: uuid()
})
if (children) {
if (s.currentComponentInfo.component) {
s.currentComponentInfo.component.props._children = children.concat(component_definition);
} else {
s.currentComponentInfo._children = children.concat(component_definition)
}
} else {
if (s.currentComponentInfo.component) {
s.currentComponentInfo.component.props._children = [component_definition];
} else {
s.currentComponentInfo._children = [component_definition]
}
}
_saveScreen(store, s, s.currentFrontEndItem);
_saveScreen(store, s, s.currentFrontEndItem);
return s;
})
}
const selectComponent = store => component => {
store.update(s => {
s.currentComponentInfo = component;
return s;
})
}
const setComponentProp = store => (name, value) => {
store.update(s => {
const current_component = s.currentComponentInfo;
s.currentComponentInfo[name] = value;
_saveScreen(store, s, s.currentFrontEndItem);
s.currentComponentInfo = current_component;
return s;
})
}
const setComponentStyle = store => (type, name, value) => {
store.update(s => {
if (!s.currentComponentInfo._styles) {
s.currentComponentInfo._styles = {};
}
s.currentComponentInfo._styles[type][name] = value;
s.currentFrontEndItem._css = generate_screen_css(s.currentFrontEndItem.props._children)
// save without messing with the store
_save(s.appname, s.currentFrontEndItem, store, s)
return s;
})
}
const setComponentCode = store => (code) => {
store.update(s => {
s.currentComponentInfo._code = code;
setCurrentScreenFunctions(s);
// save without messing with the store
_save(s.appname, s.currentFrontEndItem, store, s)
return s;
})
}
const setCurrentScreenFunctions = (s) => {
s.currentScreenFunctions =
s.currentFrontEndItem === "screen"
? buildCodeForScreens([s.currentFrontEndItem])
: "({});";
}

10
packages/builder/src/builderStore/uuid.js

@ -1,7 +1,7 @@
export function uuid() { export function uuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => {
const r = (Math.random() * 16) | 0, const r = (Math.random() * 16) | 0,
v = c == 'x' ? r : (r & 0x3) | 0x8; v = c == "x" ? r : (r & 0x3) | 0x8
return v.toString(16); return v.toString(16)
}); })
} }

18
packages/builder/src/common/Icons/index.js

@ -1,9 +1,9 @@
export { default as LayoutIcon } from './Layout.svelte'; export { default as LayoutIcon } from "./Layout.svelte"
export { default as PaintIcon } from './Paint.svelte'; export { default as PaintIcon } from "./Paint.svelte"
export { default as TerminalIcon } from './Terminal.svelte'; export { default as TerminalIcon } from "./Terminal.svelte"
export { default as InputIcon } from './Input.svelte'; export { default as InputIcon } from "./Input.svelte"
export { default as ImageIcon } from './Image.svelte'; export { default as ImageIcon } from "./Image.svelte"
export { default as ArrowDownIcon } from './ArrowDown.svelte'; export { default as ArrowDownIcon } from "./ArrowDown.svelte"
export { default as CircleIndicator } from './CircleIndicator.svelte'; export { default as CircleIndicator } from "./CircleIndicator.svelte"
export { default as PencilIcon } from './Pencil.svelte'; export { default as PencilIcon } from "./Pencil.svelte"
export { default as EventsIcon } from './Events.svelte'; export { default as EventsIcon } from "./Events.svelte"

12
packages/builder/src/common/Icons/index.js.orig

@ -1,12 +0,0 @@
export { default as LayoutIcon } from './Layout.svelte';
export { default as PaintIcon } from './Paint.svelte';
export { default as TerminalIcon } from './Terminal.svelte';
export { default as InputIcon } from './Input.svelte';
export { default as ImageIcon } from './Image.svelte';
export { default as ArrowDownIcon } from './ArrowDown.svelte';
<<<<<<< HEAD
export { default as CircleIndicator } from './CircleIndicator.svelte';
=======
export { default as EventsIcon } from './Events.svelte';
export { default as PencilIcon } from './Pencil.svelte';
>>>>>>> master

41
packages/builder/src/common/binding.js

@ -1,29 +1,28 @@
import { import { isString } from "lodash/fp"
isString
} from "lodash/fp";
import { import {
BB_STATE_BINDINGPATH, BB_STATE_FALLBACK, BB_STATE_BINDINGPATH,
BB_STATE_BINDINGSOURCE BB_STATE_FALLBACK,
} from "@budibase/client/src/state/isState"; BB_STATE_BINDINGSOURCE,
} from "@budibase/client/src/state/isState"
export const isBinding = value => export const isBinding = value =>
!isString(value) !isString(value) &&
&& value value &&
&& isString(value[BB_STATE_BINDINGPATH]) isString(value[BB_STATE_BINDINGPATH]) &&
&& value[BB_STATE_BINDINGPATH].length > 0; value[BB_STATE_BINDINGPATH].length > 0
export const setBinding = ({path, fallback, source}, binding={} ) => { export const setBinding = ({ path, fallback, source }, binding = {}) => {
if(isNonEmptyString(path)) binding[BB_STATE_BINDINGPATH] = path; if (isNonEmptyString(path)) binding[BB_STATE_BINDINGPATH] = path
if(isNonEmptyString(fallback)) binding[BB_STATE_FALLBACK] = fallback; if (isNonEmptyString(fallback)) binding[BB_STATE_FALLBACK] = fallback
binding[BB_STATE_BINDINGSOURCE] = source || "store"; binding[BB_STATE_BINDINGSOURCE] = source || "store"
return binding return binding
} }
export const getBinding = binding => ({ export const getBinding = binding => ({
path: binding[BB_STATE_BINDINGPATH] || "", path: binding[BB_STATE_BINDINGPATH] || "",
fallback: binding[BB_STATE_FALLBACK] || "", fallback: binding[BB_STATE_FALLBACK] || "",
source: binding[BB_STATE_BINDINGSOURCE] || "store" source: binding[BB_STATE_BINDINGSOURCE] || "store",
}); })
const isNonEmptyString = s => isString(s) && s.length > 0; const isNonEmptyString = s => isString(s) && s.length > 0

164
packages/builder/src/common/core.js

@ -1,96 +1,108 @@
import {hierarchy as hierarchyFunctions,
common, getTemplateApi, getAuthApi } from "../../../core/src";
import {find, filter, includes, keyBy, some,
flatten, map} from "lodash/fp";
import { import {
generateSchema hierarchy as hierarchyFunctions,
} from "../../../core/src/indexing/indexSchemaCreator"; common,
getTemplateApi,
getAuthApi,
} from "../../../core/src"
import { find, filter, includes, keyBy, some, flatten, map } from "lodash/fp"
import { generateSchema } from "../../../core/src/indexing/indexSchemaCreator"
export { userWithFullAccess } from "../../../core/src/index"; export { userWithFullAccess } from "../../../core/src/index"
export const pipe = common.$; export const pipe = common.$
export const events = common.eventsList; export const events = common.eventsList
export const getNode = (hierarchy, nodeId) => export const getNode = (hierarchy, nodeId) =>
pipe(hierarchy, [ pipe(hierarchy, [
hierarchyFunctions.getFlattenedHierarchy, hierarchyFunctions.getFlattenedHierarchy,
find(n => n.nodeId === nodeId || n.nodeKey() === nodeId) find(n => n.nodeId === nodeId || n.nodeKey() === nodeId),
]); ])
export const constructHierarchy = node => { export const constructHierarchy = node => {
if(!node) return node; if (!node) return node
return templateApi(node).constructHierarchy(node); return templateApi(node).constructHierarchy(node)
} }
export const createNewHierarchy = () => { export const createNewHierarchy = () => {
return templateApi().getNewRootLevel(); return templateApi().getNewRootLevel()
} }
export const templateApi = hierarchy => getTemplateApi({hierarchy}) export const templateApi = hierarchy => getTemplateApi({ hierarchy })
export const authApi = (hierarchy, actions) => getAuthApi({ export const authApi = (hierarchy, actions) =>
hierarchy, actions: keyBy("name")(actions), publish:()=>{}}) getAuthApi({
hierarchy,
actions: keyBy("name")(actions),
publish: () => {},
})
export const allTypes = templateApi({}).allTypes; export const allTypes = templateApi({}).allTypes
export const validate = { export const validate = {
all: templateApi({}).validateAll, all: templateApi({}).validateAll,
node: templateApi({}).validateNode, node: templateApi({}).validateNode,
field: templateApi({}).validateField field: templateApi({}).validateField,
}; }
export const getPotentialReverseReferenceIndexes = (hierarchy, refIndex) => { export const getPotentialReverseReferenceIndexes = (hierarchy, refIndex) => {
const res = pipe(hierarchy, [ const res = pipe(hierarchy, [
hierarchyFunctions.getFlattenedHierarchy, hierarchyFunctions.getFlattenedHierarchy,
filter(n => hierarchyFunctions.isAncestor(refIndex)(n) filter(
|| hierarchyFunctions.isAncestor(refIndex)(n.parent())), n =>
map(n => n.indexes), hierarchyFunctions.isAncestor(refIndex)(n) ||
flatten, hierarchyFunctions.isAncestor(refIndex)(n.parent())
filter(hierarchyFunctions.isReferenceIndex) ),
]); map(n => n.indexes),
flatten,
return res; filter(hierarchyFunctions.isReferenceIndex),
])
return res
} }
export const getPotentialReferenceIndexes = (hierarchy, record) => export const getPotentialReferenceIndexes = (hierarchy, record) =>
pipe(hierarchy, [ pipe(hierarchy, [
hierarchyFunctions.getFlattenedHierarchy, hierarchyFunctions.getFlattenedHierarchy,
filter(hierarchyFunctions.isAncestorIndex), filter(hierarchyFunctions.isAncestorIndex),
filter(i => hierarchyFunctions.isAncestor(record)(i.parent()) filter(
|| i.parent().nodeId === record.parent().nodeId i =>
|| hierarchyFunctions.isRoot(i.parent())) hierarchyFunctions.isAncestor(record)(i.parent()) ||
]); i.parent().nodeId === record.parent().nodeId ||
hierarchyFunctions.isRoot(i.parent())
export const getDefaultTypeOptions = type => ),
!type ? {} : allTypes[type].getDefaultOptions(); ])
export const getNewAction = () => templateApi({}).createAction(); export const getDefaultTypeOptions = type =>
export const getNewTrigger = () => templateApi({}).createTrigger(); !type ? {} : allTypes[type].getDefaultOptions()
export const validateActions = actions => templateApi({}).validateActions(actions); export const getNewAction = () => templateApi({}).createAction()
export const validateTriggers = (triggers, actions) => templateApi({}).validateTriggers(triggers, actions); export const getNewTrigger = () => templateApi({}).createTrigger()
export const generateFullPermissions = (hierarchy, actions) => export const validateActions = actions =>
authApi(hierarchy,actions).generateFullPermissions(); templateApi({}).validateActions(actions)
export const validateTriggers = (triggers, actions) =>
export const getNewAccessLevel = () => templateApi({}).validateTriggers(triggers, actions)
authApi().getNewAccessLevel();
export const generateFullPermissions = (hierarchy, actions) =>
export const validateAccessLevels = (hierarchy, actions, accessLevels) => authApi(hierarchy, actions).generateFullPermissions()
authApi(hierarchy, actions).validateAccessLevels(accessLevels);
export const getNewAccessLevel = () => authApi().getNewAccessLevel()
export const getIndexNodes = (hierarchy) =>
pipe(hierarchy, [ export const validateAccessLevels = (hierarchy, actions, accessLevels) =>
hierarchyFunctions.getFlattenedHierarchy, authApi(hierarchy, actions).validateAccessLevels(accessLevels)
filter(hierarchyFunctions.isIndex)
]); export const getIndexNodes = hierarchy =>
pipe(hierarchy, [
export const getRecordNodes = (hierarchy) => hierarchyFunctions.getFlattenedHierarchy,
pipe(hierarchy, [ filter(hierarchyFunctions.isIndex),
hierarchyFunctions.getFlattenedHierarchy, ])
filter(hierarchyFunctions.isRecord)
]); export const getRecordNodes = hierarchy =>
pipe(hierarchy, [
export const getIndexSchema = hierarchy => index => hierarchyFunctions.getFlattenedHierarchy,
generateSchema(hierarchy, index); filter(hierarchyFunctions.isRecord),
])
export const getIndexSchema = hierarchy => index =>
generateSchema(hierarchy, index)

43
packages/builder/src/common/eventHandlers.js

@ -1,26 +1,21 @@
import { import { eventHandlers } from "../../../client/src/state/eventHandlers"
eventHandlers import { writable } from "svelte/store"
} from "../../../client/src/state/eventHandlers"; export { EVENT_TYPE_MEMBER_NAME } from "../../../client/src/state/eventHandlers"
import {writable} from "svelte/store"; import { createCoreApi } from "../../../client/src/core"
export {
EVENT_TYPE_MEMBER_NAME
} from "../../../client/src/state/eventHandlers";
import {
createCoreApi
} from "../../../client/src/core";
export const allHandlers = (appDefinition, user) => { export const allHandlers = (appDefinition, user) => {
const coreApi = createCoreApi(appDefinition, user)
appDefinition.hierarchy = coreApi.templateApi.constructHierarchy(
appDefinition.hierarchy
)
const store = writable({
_bbuser: user,
})
const coreApi = createCoreApi(appDefinition, user); const handlersObj = eventHandlers(store, coreApi)
appDefinition.hierarchy = coreApi.templateApi.constructHierarchy(appDefinition.hierarchy); const handlersArray = []
const store = writable({ for (let key in handlersObj) {
_bbuser: user handlersArray.push({ name: key, ...handlersObj[key] })
}); }
return handlersArray
const handlersObj = eventHandlers(store, coreApi); }
const handlersArray = [];
for(let key in handlersObj) {
handlersArray.push({name:key, ...handlersObj[key]});
}
return handlersArray;
}

7
packages/builder/src/common/icon.js

@ -1,3 +1,4 @@
import feather from "feather-icons"; import feather from "feather-icons"
const getIcon = (icon, size) => feather.icons[icon].toSvg({height:size||"16", width:size||"16"}); const getIcon = (icon, size) =>
export default getIcon; feather.icons[icon].toSvg({ height: size || "16", width: size || "16" })
export default getIcon

34
packages/builder/src/main.js

@ -1,20 +1,18 @@
import App from "./App.svelte"; import App from "./App.svelte"
import "./global.css"; import "./global.css"
import "./fonts.css"; import "./fonts.css"
import "/assets/roboto-v20-latin-ext_latin-300"; import "/assets/roboto-v20-latin-ext_latin-300"
import "/assets/roboto-v20-latin-ext_latin-400"; import "/assets/roboto-v20-latin-ext_latin-400"
import "/assets/roboto-v20-latin-ext_latin-500"; import "/assets/roboto-v20-latin-ext_latin-500"
import "/assets/roboto-v20-latin-ext_latin-700"; import "/assets/roboto-v20-latin-ext_latin-700"
import "/assets/roboto-v20-latin-ext_latin-900"; import "/assets/roboto-v20-latin-ext_latin-900"
import "/assets/budibase-logo.png"; import "/assets/budibase-logo.png"
import "/assets/budibase-logo-only.png"; import "/assets/budibase-logo-only.png"
import "uikit/dist/css/uikit.min.css"; import "uikit/dist/css/uikit.min.css"
import "uikit/dist/js/uikit.min.js"; import "uikit/dist/js/uikit.min.js"
import "codemirror/lib/codemirror.css"; import "codemirror/lib/codemirror.css"
import 'codemirror/theme/monokai.css'; import "codemirror/theme/monokai.css"
const app = new App({ const app = new App({
target: document.getElementById("app") target: document.getElementById("app"),
}); })

149
packages/builder/src/userInterface/ComponentPanel.svelte.orig

@ -1,149 +0,0 @@
<script>
import PropsView from "./PropsView.svelte";
import { store } from "../builderStore";
import IconButton from "../common/IconButton.svelte";
<<<<<<< HEAD
import { LayoutIcon, PaintIcon, TerminalIcon, CircleIndicator } from '../common/Icons/';
=======
import { LayoutIcon, PaintIcon, TerminalIcon, EventsIcon } from '../common/Icons/';
>>>>>>> master
import CodeEditor from './CodeEditor.svelte';
import LayoutEditor from './LayoutEditor.svelte';
import EventsEditor from "./EventsEditor";
let current_view = 'props';
let codeEditor;
$: component = $store.currentComponentInfo;
$: originalName = component.name;
$: name = component.name;
$: description = component.description;
$: componentInfo = $store.currentComponentInfo;
$: components = $store.components;
const onPropChanged = store.setComponentProp;
const onStyleChanged = store.setComponentStyle;
</script>
<div class="root">
<ul>
<li>
<button class:selected={current_view === 'props'} on:click={() => current_view = 'props'}>
<PaintIcon />
</button>
</li>
<li>
<button class:selected={current_view === 'layout'} on:click={() => current_view = 'layout'}>
<LayoutIcon />
</button>
</li>
<li>
<button class:selected={current_view === 'code'} on:click={() => codeEditor && codeEditor.show()}>
{#if componentInfo._code && componentInfo._code.trim().length > 0}
<div class="button-indicator">
<CircleIndicator />
</div>
{/if}
<TerminalIcon />
</button>
</li>
<li>
<button class:selected={current_view === 'events'} on:click={() => current_view = 'events'}>
<EventsIcon />
</button>
</li>
</ul>
{#if !componentInfo.component}
<div class="component-props-container">
{#if current_view === 'props'}
<PropsView {componentInfo} {components} {onPropChanged} />
{:else if current_view === 'layout'}
<LayoutEditor {onStyleChanged} {componentInfo}/>
<<<<<<< HEAD
=======
{:else if current_view === 'events'}
<EventsEditor {componentInfo} {components} {onPropChanged} />
{:else}
<CodeEditor />
>>>>>>> master
{/if}
<CodeEditor
bind:this={codeEditor}
code={$store.currentComponentInfo._code}
onCodeChanged={store.setComponentCode} />
</div>
{:else}
<h1> This is a screen, this will be dealt with later</h1>
{/if}
</div>
<style>
.root {
height: 100%;
display: flex;
flex-direction: column;
}
.title > div:nth-child(1) {
grid-column-start: name;
color: var(--secondary100);
}
.title > div:nth-child(2) {
grid-column-start: actions;
}
.component-props-container {
margin-top: 10px;
flex: 1 1 auto;
overflow-y: auto;
}
ul {
list-style: none;
display: flex;
padding: 0;
}
li {
margin-right: 20px;
background: none;
border-radius: 5px;
width: 48px;
height: 48px;
}
li button {
width: 100%;
height: 100%;
background: none;
border: none;
border-radius: 5px;
padding: 12px;
outline: none;
cursor: pointer;
position: relative;
}
.selected {
color: var(--button-text);
background: var(--background-button)!important;
}
.button-indicator {
position: absolute;
top: 6px;
right: 10px;
color: var(--button-text);
}
</style>

2
packages/builder/src/userInterface/EventsEditor/index.js

@ -1 +1 @@
export { default } from "./EventsEditor.svelte"; export { default } from "./EventsEditor.svelte"

74
packages/builder/src/userInterface/PropsView.svelte.orig

@ -1,74 +0,0 @@
<script>
import { some, includes, filter } from "lodash/fp";
import Textbox from "../common/Textbox.svelte";
import Dropdown from "../common/Dropdown.svelte";
import PropControl from "./PropControl.svelte";
import IconButton from "../common/IconButton.svelte";
export let componentInfo;
export let onPropChanged = () => {};
export let components;
let errors = [];
let props = {};
<<<<<<< HEAD
const props_to_ignore = ['_component','_children', '_layout', '_code', '_id'];
=======
const props_to_ignore = ['_component','_children', '_styles', '_id'];
>>>>>>> master
$: propDefs = componentInfo && Object.entries(componentInfo).filter(([name])=> !props_to_ignore.includes(name));
function find_type(prop_name) {
if(!componentInfo._component) return;
return components.find(({name}) => name === componentInfo._component).props[prop_name];
}
let setProp = (name, value) => {
onPropChanged(name, value);
}
const fieldHasError = (propName) =>
some(e => e.propName === propName)(errors);
</script>
<div class="root">
<form class="uk-form-stacked form-root">
{#each propDefs as [prop_name, prop_value], index}
<div class="prop-container">
<PropControl {setProp}
{prop_name}
{prop_value}
prop_type={find_type(prop_name)}
{index}
disabled={false} />
</div>
{/each}
</form>
</div>
<style>
.root {
font-size:10pt;
width: 100%;
}
.form-root {
display: flex;
flex-wrap: wrap;
}
.prop-container {
flex: 1 1 auto;
min-width: 250px;
}
</style>

99
packages/builder/src/userInterface/pagesParsing/buildPropsHierarchy.js

@ -1,60 +1,57 @@
import { import { getComponentInfo, createProps, getInstanceProps } from "./createProps"
getComponentInfo, createProps, getInstanceProps
} from "./createProps";
export const buildPropsHierarchy = (components, screens, baseComponent) => { export const buildPropsHierarchy = (components, screens, baseComponent) => {
const allComponents = [...components, ...screens]
const allComponents = [...components, ...screens];
const buildProps = (componentDefinition, derivedFromProps) => {
const buildProps = (componentDefinition, derivedFromProps) => { const { props } = createProps(componentDefinition, derivedFromProps)
const propsDefinition = componentDefinition.props
const {props} = createProps(componentDefinition, derivedFromProps); props._component = componentDefinition.name
const propsDefinition = componentDefinition.props; for (let propName in props) {
props._component = componentDefinition.name; if (propName === "_component") continue
for(let propName in props) {
if(propName === "_component") continue; const propDef = propsDefinition[propName]
if (!propDef) continue
const propDef = propsDefinition[propName]; if (propName === "_children") {
if(!propDef) continue; const childrenProps = props[propName]
if(propName === "_children") {
if (!childrenProps || childrenProps.length === 0) {
const childrenProps = props[propName]; continue
if(!childrenProps
|| childrenProps.length === 0) {
continue;
}
props[propName] = [];
for(let child of childrenProps) {
const propComponentInfo = getComponentInfo(
allComponents, child._component);
const subComponentInstanceProps = getInstanceProps(
propComponentInfo,
child
);
props[propName].push(
buildProps(
propComponentInfo.rootComponent.name,
propComponentInfo.propsDefinition,
subComponentInstanceProps));
}
}
} }
return props; props[propName] = []
for (let child of childrenProps) {
const propComponentInfo = getComponentInfo(
allComponents,
child._component
)
const subComponentInstanceProps = getInstanceProps(
propComponentInfo,
child
)
props[propName].push(
buildProps(
propComponentInfo.rootComponent.name,
propComponentInfo.propsDefinition,
subComponentInstanceProps
)
)
}
}
} }
if(!baseComponent) return {}; return props
}
const baseComponentInfo = getComponentInfo(allComponents, baseComponent); if (!baseComponent) return {}
return buildProps( const baseComponentInfo = getComponentInfo(allComponents, baseComponent)
baseComponentInfo.rootComponent,
baseComponentInfo.fullProps);
} return buildProps(
baseComponentInfo.rootComponent,
baseComponentInfo.fullProps
)
}

212
packages/builder/src/userInterface/pagesParsing/createProps.js

@ -1,146 +1,146 @@
import { import {
isString, isUndefined, find, keys, uniq, isString,
some, filter, reduce, cloneDeep, includes, last isUndefined,
} from "lodash/fp"; find,
import { types, expandComponentDefinition } from "./types"; keys,
import { assign } from "lodash"; uniq,
import { pipe } from "../../common/core"; some,
import { isRootComponent } from "./searchComponents"; filter,
import { ensureShardNameIsInShardMap } from "../../../../core/src/indexing/sharding"; reduce,
cloneDeep,
includes,
last,
} from "lodash/fp"
import { types, expandComponentDefinition } from "./types"
import { assign } from "lodash"
import { pipe } from "../../common/core"
import { isRootComponent } from "./searchComponents"
import { ensureShardNameIsInShardMap } from "../../../../core/src/indexing/sharding"
export const getInstanceProps = (componentInfo, props) => { export const getInstanceProps = (componentInfo, props) => {
const finalProps = cloneDeep(componentInfo.fullProps); const finalProps = cloneDeep(componentInfo.fullProps)
for (let p in props) { for (let p in props) {
finalProps[p] = props[p]; finalProps[p] = props[p]
} }
return finalProps; return finalProps
} }
export const getNewComponentInfo = (components, rootComponent, name) => { export const getNewComponentInfo = (components, rootComponent, name) => {
const component = { const component = {
name: name || "", name: name || "",
description: "", description: "",
props: { props: {
_component: rootComponent _component: rootComponent,
} },
}; }
return getComponentInfo( return getComponentInfo(components, component)
components,
component);
} }
export const getScreenInfo = (components, screen) => { export const getScreenInfo = (components, screen) => {
return getComponentInfo( return getComponentInfo(components, screen)
components,
screen);
} }
export const getComponentInfo = (components, comp) => { export const getComponentInfo = (components, comp) => {
const targetComponent = isString(comp) const targetComponent = isString(comp)
? find(c => c.name === comp)(components) ? find(c => c.name === comp)(components)
: comp; : comp
let component; let component
let subComponent; let subComponent
if (isRootComponent(targetComponent)) { if (isRootComponent(targetComponent)) {
component = targetComponent; component = targetComponent
} else { } else {
subComponent = targetComponent; subComponent = targetComponent
component = find(c => c.name === (subComponent.props ? subComponent.props._component : subComponent._component))( component = find(
components); c =>
} c.name ===
(subComponent.props
const subComponentProps = subComponent ? subComponent.props : {}; ? subComponent.props._component
const p = createProps(component, subComponentProps); : subComponent._component)
const rootProps = createProps(component); )(components)
}
const unsetProps = pipe(p.props, [
keys, const subComponentProps = subComponent ? subComponent.props : {}
filter(k => !includes(k)(keys(subComponentProps)) && k !== "_component") const p = createProps(component, subComponentProps)
]); const rootProps = createProps(component)
const fullProps = cloneDeep(p.props); const unsetProps = pipe(p.props, [
fullProps._component = targetComponent.name; keys,
filter(k => !includes(k)(keys(subComponentProps)) && k !== "_component"),
return ({ ])
propsDefinition: expandComponentDefinition(component),
rootDefaultProps: rootProps.props, const fullProps = cloneDeep(p.props)
unsetProps, fullProps._component = targetComponent.name
fullProps: fullProps,
errors: p.errors, return {
component: targetComponent, propsDefinition: expandComponentDefinition(component),
rootComponent: component rootDefaultProps: rootProps.props,
}); unsetProps,
fullProps: fullProps,
errors: p.errors,
component: targetComponent,
rootComponent: component,
}
} }
export const createProps = (componentDefinition, derivedFromProps) => { export const createProps = (componentDefinition, derivedFromProps) => {
const error = (propName, error) => errors.push({ propName, error })
const error = (propName, error) => const props = {
errors.push({ propName, error }); _component: componentDefinition.name,
}
const props = {
_component: componentDefinition.name
};
const errors = []; const errors = []
if (!componentDefinition.name) if (!componentDefinition.name)
error("_component", "Component name not supplied"); error("_component", "Component name not supplied")
const propsDef = componentDefinition.props; const propsDef = componentDefinition.props
for (let propDef in propsDef) { for (let propDef in propsDef) {
const parsedPropDef = parsePropDef(propsDef[propDef]); const parsedPropDef = parsePropDef(propsDef[propDef])
if (parsedPropDef.error) if (parsedPropDef.error) error(propDef, parsedPropDef.error)
error(propDef, parsedPropDef.error); else props[propDef] = parsedPropDef
else }
props[propDef] = parsedPropDef;
}
if (derivedFromProps) { if (derivedFromProps) {
assign(props, derivedFromProps); assign(props, derivedFromProps)
} }
if (componentDefinition.children !== false if (componentDefinition.children !== false && isUndefined(props._children)) {
&& isUndefined(props._children)) { props._children = []
props._children = []; }
}
return ({ return {
props, errors props,
}); errors,
}
} }
const parsePropDef = propDef => { const parsePropDef = propDef => {
const error = message => ({ error: message, propDef }); const error = message => ({ error: message, propDef })
if (isString(propDef)) { if (isString(propDef)) {
if (!types[propDef]) if (!types[propDef]) return error(`Do not recognise type ${propDef}`)
return error(`Do not recognise type ${propDef}`);
return types[propDef].default(); return types[propDef].default()
} }
if (!propDef.type) if (!propDef.type) return error("Property Definition must declare a type")
return error("Property Definition must declare a type");
const type = types[propDef.type]; const type = types[propDef.type]
if (!type) if (!type) return error(`Do not recognise type ${propDef.type}`)
return error(`Do not recognise type ${propDef.type}`);
if (isUndefined(propDef.default)) if (isUndefined(propDef.default)) return type.default(propDef)
return type.default(propDef);
if (!type.isOfType(propDef.default)) if (!type.isOfType(propDef.default))
return error(`${propDef.default} is not of type ${type}`); return error(`${propDef.default} is not of type ${type}`)
return propDef.default; return propDef.default
} }
export const arrayElementComponentName = (parentComponentName, arrayPropName) => export const arrayElementComponentName = (parentComponentName, arrayPropName) =>
`${parentComponentName}:${arrayPropName}`; `${parentComponentName}:${arrayPropName}`
/* /*
Allowed propDefOptions Allowed propDefOptions

27
packages/builder/src/userInterface/pagesParsing/defaultPagesObject.js

@ -1,17 +1,16 @@
export const defaultPagesObject = () => ({ export const defaultPagesObject = () => ({
main: { main: {
index: { index: {
_component : "./components/indexHtml" _component: "./components/indexHtml",
},
appBody: "bbapp.main.json"
}, },
unauthenticated: { appBody: "bbapp.main.json",
index: { },
_component : "./components/indexHtml" unauthenticated: {
}, index: {
appBody: "bbapp.unauthenticated.json" _component: "./components/indexHtml",
}, },
componentLibraries: [], appBody: "bbapp.unauthenticated.json",
stylesheets: [] },
componentLibraries: [],
}); stylesheets: [],
})

135
packages/builder/src/userInterface/pagesParsing/findDependencies.js

@ -1,89 +1,80 @@
import { splitName } from "./splitRootComponentName"; import { splitName } from "./splitRootComponentName"
import { import { find, filter, cloneDeep, isPlainObject, isArray } from "lodash/fp"
find, filter, cloneDeep, isPlainObject, import { isRootComponent } from "./searchComponents"
isArray
} from "lodash/fp";
import { isRootComponent } from "./searchComponents";
export const libraryDependencies = (components, lib) => { export const libraryDependencies = (components, lib) => {
const componentDependsOnLibrary = comp => {
const componentDependsOnLibrary = comp => { if (isRootComponent(comp)) {
if(isRootComponent(comp)) { const { libName } = splitName(component.name)
const {libName} = splitName(component.name); return libName === lib
return (libName === lib);
}
return componentDependsOnLibrary(
find(c => c.name === comp.props._component)(
components)
);
} }
return componentDependsOnLibrary(
return filter(c => !isRootComponent(c) find(c => c.name === comp.props._component)(components)
&& componentDependsOnLibrary(c))( )
components }
);
return filter(c => !isRootComponent(c) && componentDependsOnLibrary(c))(
components
)
} }
export const componentDependencies = (pages, screens, components, dependsOn) => { export const componentDependencies = (
pages,
const allComponents = [ screens,
...cloneDeep(components), components,
...cloneDeep(screens)]; dependsOn
) => {
pages = cloneDeep(pages); const allComponents = [...cloneDeep(components), ...cloneDeep(screens)]
const dependantComponents = [];
const dependantPages = []; pages = cloneDeep(pages)
const dependantComponents = []
const traverseProps = (props) => { const dependantPages = []
if(props._component && props._component === dependsOn.name) { const traverseProps = props => {
return true; if (props._component && props._component === dependsOn.name) {
} return true
}
for(let propName in props) { for (let propName in props) {
const prop = props[propName]; const prop = props[propName]
if(isPlainObject(prop) && prop._component) { if (isPlainObject(prop) && prop._component) {
if(traverseProps(prop)) return true; if (traverseProps(prop)) return true
} }
if(isArray(prop)) { if (isArray(prop)) {
for(let element of prop) { for (let element of prop) {
if(traverseProps(element)) return true; if (traverseProps(element)) return true
}
}
} }
}
return false;
} }
return false
}
for(let component of allComponents) { for (let component of allComponents) {
if (isRootComponent(component)) {
if(isRootComponent(component)) { continue
continue; }
}
if(component.name === dependsOn.name) {
continue;
}
if(component.props._component === dependsOn.name) { if (component.name === dependsOn.name) {
dependantComponents.push(component); continue
continue; }
}
if(traverseProps(component.props)) {
dependantComponents.push(component);
}
if (component.props._component === dependsOn.name) {
dependantComponents.push(component)
continue
} }
for(let pageName in pages) { if (traverseProps(component.props)) {
const page = pages[pageName]; dependantComponents.push(component)
if(page.appBody === dependsOn.name) {
dependantPages.push(pageName);
}
} }
}
return {dependantComponents, dependantPages}; for (let pageName in pages) {
const page = pages[pageName]
if (page.appBody === dependsOn.name) {
dependantPages.push(pageName)
}
}
} return { dependantComponents, dependantPages }
}

12
packages/builder/src/userInterface/pagesParsing/getRootComponent.js

@ -1,10 +1,10 @@
import { isRootComponent } from "./searchComponents"; import { isRootComponent } from "./searchComponents"
import { find } from "lodash/fp"; import { find } from "lodash/fp"
export const getRootComponent = (componentName, components) => { export const getRootComponent = (componentName, components) => {
const component = find(c => c.name === componentName)(components); const component = find(c => c.name === componentName)(components)
if(isRootComponent(component)) return component; if (isRootComponent(component)) return component
return getRootComponent(component.props._component, components); return getRootComponent(component.props._component, components)
} }

105
packages/builder/src/userInterface/pagesParsing/renameScreen.js

@ -1,71 +1,64 @@
import { import { isPlainObject, isArray, cloneDeep } from "lodash/fp"
isPlainObject, isArray, cloneDeep import { getExactComponent } from "./searchComponents"
} from "lodash/fp";
import {
getExactComponent
} from "./searchComponents";
export const rename = (pages, screens, oldname, newname) => { export const rename = (pages, screens, oldname, newname) => {
pages = cloneDeep(pages)
screens = cloneDeep(screens)
const changedScreens = []
pages = cloneDeep(pages); const existingWithNewName = getExactComponent(screens, newname)
screens = cloneDeep(screens); if (existingWithNewName)
const changedScreens = []; return {
components: screens,
const existingWithNewName = getExactComponent(screens, newname); pages,
if(existingWithNewName) return { error: "Component by that name already exists",
components: screens, pages, error: "Component by that name already exists"
};
const traverseProps = (props) => {
let hasEdited = false;
if(props._component && props._component === oldname) {
props._component = newname;
hasEdited = true;
}
for(let propName in props) {
const prop = props[propName];
if(isPlainObject(prop) && prop._component) {
hasEdited = traverseProps(prop) || hasEdited;
}
if(isArray(prop)) {
for(let element of prop) {
hasEdited = traverseProps(element) || hasEdited;
}
}
}
return hasEdited;
} }
const traverseProps = props => {
let hasEdited = false
if (props._component && props._component === oldname) {
props._component = newname
hasEdited = true
}
for(let screen of screens) { for (let propName in props) {
const prop = props[propName]
let hasEdited = false; if (isPlainObject(prop) && prop._component) {
hasEdited = traverseProps(prop) || hasEdited
if(screen.name === oldname) { }
screen.name = newname; if (isArray(prop)) {
hasEdited = true; for (let element of prop) {
hasEdited = traverseProps(element) || hasEdited
} }
}
}
return hasEdited
}
if(screen.props._component === oldname) { for (let screen of screens) {
screen.props._component = newname; let hasEdited = false
hasEdited = true;
}
hasEdited = traverseProps(screen.props) || hasEdited;
if(hasEdited && screen.name !== newname) if (screen.name === oldname) {
changedScreens.push(screen.name); screen.name = newname
hasEdited = true
} }
for(let pageName in pages) { if (screen.props._component === oldname) {
const page = pages[pageName]; screen.props._component = newname
if(page.appBody === oldname) { hasEdited = true
page.appBody = newname;
}
} }
return {screens, pages, changedScreens}; hasEdited = traverseProps(screen.props) || hasEdited
if (hasEdited && screen.name !== newname) changedScreens.push(screen.name)
}
for (let pageName in pages) {
const page = pages[pageName]
if (page.appBody === oldname) {
page.appBody = newname
}
}
} return { screens, pages, changedScreens }
}

74
packages/builder/src/userInterface/pagesParsing/searchComponents.js

@ -1,67 +1,47 @@
import {pipe} from "../../common/core"; import { pipe } from "../../common/core"
import { import { find, isUndefined, filter, some, includes, has } from "lodash/fp"
find,
isUndefined,
filter,
some,
includes,
has
} from "lodash/fp";
const normalString = s => (s||"").trim().toLowerCase(); const normalString = s => (s || "").trim().toLowerCase()
export const isRootComponent = c => export const isRootComponent = c =>
isComponent(c) && isUndefined(c.props._component); isComponent(c) && isUndefined(c.props._component)
export const isComponent = c => { export const isComponent = c => {
const hasProp = (n) => !isUndefined(c[n]); const hasProp = n => !isUndefined(c[n])
return hasProp("name") && hasProp("props"); return hasProp("name") && hasProp("props")
} }
export const searchAllComponents = (components, phrase) => { export const searchAllComponents = (components, phrase) => {
const hasPhrase = (...vals) =>
pipe(vals, [some(v => includes(normalString(phrase))(normalString(v)))])
const hasPhrase = (...vals) => const componentMatches = c => {
pipe(vals, [ if (hasPhrase(c.name, ...(c.tags || []))) return true
some(v => includes(normalString(phrase))(normalString(v)))
]);
const componentMatches = c => { if (isRootComponent(c)) return false
if(hasPhrase(c.name, ...(c.tags || []))) return true;
if(isRootComponent(c)) return false; const parent = getExactComponent(components, c.props._component)
const parent = getExactComponent(
components,
c.props._component);
return componentMatches(parent); return componentMatches(parent)
} }
return filter(componentMatches)(components); return filter(componentMatches)(components)
} }
export const getExactComponent = (components, name) => { export const getExactComponent = (components, name) => {
const stringEquals = (s1, s2) => normalString(s1) === normalString(s2)
const stringEquals = (s1, s2) =>
normalString(s1) === normalString(s2);
return pipe(components,[
find(c => stringEquals(c.name, name))
]);
}
export const getAncestorProps = (components, name, found=[]) => { return pipe(components, [find(c => stringEquals(c.name, name))])
const thisComponent = getExactComponent( }
components, name);
if(isRootComponent(thisComponent)) export const getAncestorProps = (components, name, found = []) => {
return [thisComponent.props, ...found]; const thisComponent = getExactComponent(components, name)
return getAncestorProps( if (isRootComponent(thisComponent)) return [thisComponent.props, ...found]
components,
thisComponent.props._component,
[{...thisComponent.props},
...found]);
return getAncestorProps(components, thisComponent.props._component, [
{ ...thisComponent.props },
...found,
])
} }

22
packages/builder/src/userInterface/pagesParsing/splitRootComponentName.js

@ -1,18 +1,14 @@
import { import { split, last } from "lodash/fp"
split,
last
} from "lodash/fp";
import { pipe } from "../../common/core"; import { pipe } from "../../common/core"
export const splitName = fullname => { export const splitName = fullname => {
const componentName = pipe(fullname, [ const componentName = pipe(fullname, [split("/"), last])
split("/"),
last
]);
const libName =fullname.substring( const libName = fullname.substring(
0, fullname.length - componentName.length - 1); 0,
fullname.length - componentName.length - 1
)
return {libName, componentName}; return { libName, componentName }
} }

121
packages/builder/src/userInterface/pagesParsing/types.js

@ -1,89 +1,86 @@
import { import {
isString, isString,
isBoolean, isBoolean,
isNumber, isNumber,
isArray, isArray,
isObjectLike, isObjectLike,
isPlainObject, isPlainObject,
every, every,
isUndefined isUndefined,
} from "lodash/fp"; } from "lodash/fp"
import { import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers"
EVENT_TYPE_MEMBER_NAME
} from "../../common/eventHandlers";
import { import {
isBound, BB_STATE_BINDINGPATH isBound,
} from "@budibase/client/src/state/isState"; BB_STATE_BINDINGPATH,
} from "@budibase/client/src/state/isState"
const defaultDef = typeName => () => ({ const defaultDef = typeName => () => ({
type: typeName, type: typeName,
required:false, required: false,
default:types[typeName].default(), default: types[typeName].default(),
options: typeName === "options" ? [] : undefined options: typeName === "options" ? [] : undefined,
}); })
const propType = (defaultValue, isOfType, defaultDefinition) => ({ const propType = (defaultValue, isOfType, defaultDefinition) => ({
isOfType, default:defaultValue, defaultDefinition isOfType,
}); default: defaultValue,
defaultDefinition,
})
const expandSingleProp = propDef => { const expandSingleProp = propDef => {
const p = isString(propDef) const p = isString(propDef) ? types[propDef].defaultDefinition() : propDef
? types[propDef].defaultDefinition()
: propDef; if (!isString(propDef)) {
const def = types[propDef.type].defaultDefinition()
if(!isString(propDef)) { for (let p in def) {
const def = types[propDef.type].defaultDefinition(); if (propDef[p] === undefined) {
for(let p in def) { propDef[p] = def[p]
if(propDef[p] === undefined) { }
propDef[p] = def[p];
}
}
} }
}
return p; return p
} }
export const expandComponentDefinition = componentDefinition => { export const expandComponentDefinition = componentDefinition => {
const expandedProps = {}; const expandedProps = {}
const expandedComponent = {...componentDefinition}; const expandedComponent = { ...componentDefinition }
for(let p in componentDefinition.props) { for (let p in componentDefinition.props) {
expandedProps[p] = expandSingleProp( expandedProps[p] = expandSingleProp(componentDefinition.props[p])
componentDefinition.props[p]); }
}
expandedComponent.props = expandedProps; expandedComponent.props = expandedProps
if(expandedComponent.children !== false) { if (expandedComponent.children !== false) {
expandedComponent.children = true; expandedComponent.children = true
} }
return expandedComponent; return expandedComponent
} }
const isComponent = isObjectLike; const isComponent = isObjectLike
const isEvent = e => const isEvent = e =>
isPlainObject(e) isPlainObject(e) &&
&& isString(e[EVENT_TYPE_MEMBER_NAME]) isString(e[EVENT_TYPE_MEMBER_NAME]) &&
&& isPlainObject(e.parameters); isPlainObject(e.parameters)
const isEventList = e => const isEventList = e => isArray(e) && every(isEvent)(e)
isArray(e) && every(isEvent)(e);
const emptyState = () => { const emptyState = () => {
const s = {}; const s = {}
s[BB_STATE_BINDINGPATH] = ""; s[BB_STATE_BINDINGPATH] = ""
return s; return s
} }
export const types = { export const types = {
string: propType(() => "", isString, defaultDef("string")), string: propType(() => "", isString, defaultDef("string")),
bool: propType(() => false, isBoolean, defaultDef("bool")), bool: propType(() => false, isBoolean, defaultDef("bool")),
number: propType(() => 0, isNumber, defaultDef("number")), number: propType(() => 0, isNumber, defaultDef("number")),
options: propType(() => "", isString, defaultDef("options")), options: propType(() => "", isString, defaultDef("options")),
asset: propType(() => "", isString, defaultDef("asset")), asset: propType(() => "", isString, defaultDef("asset")),
event: propType(() => [], isEventList, defaultDef("event")), event: propType(() => [], isEventList, defaultDef("event")),
state: propType(() => emptyState(), isBound, defaultDef("state")) state: propType(() => emptyState(), isBound, defaultDef("state")),
}; }

85
packages/builder/src/userInterface/pagesParsing/validatePages.js

@ -1,30 +1,25 @@
import { import { isString, keys, flatten, isArray, map, filter } from "lodash/fp"
isString, import { common } from "../../../../core/src"
keys, const pipe = common.$
flatten,
isArray,
map,
filter
} from "lodash/fp";
import { common } from "../../../../core/src";
const pipe = common.$;
export const validatePage = (page, getComponent) => { export const validatePage = (page, getComponent) => {
const errors = []; const errors = []
const error = message => errors.push(message); const error = message => errors.push(message)
const noIndex = !page.index; const noIndex = !page.index
if (noIndex) { if (noIndex) {
error("Page does not define an index member"); error("Page does not define an index member")
} }
if (!page.appBody if (
|| !isString(page.appBody) !page.appBody ||
|| !page.appBody.endsWith(".json")) { !isString(page.appBody) ||
error("App body must be set toa valid JSON file"); !page.appBody.endsWith(".json")
} ) {
error("App body must be set toa valid JSON file")
}
/* Commenting this for now /* Commenting this for now
* index is a load of static members just now, but maybe useful * index is a load of static members just now, but maybe useful
for pageLayout props (which is just a pipe dream at time of writing) for pageLayout props (which is just a pipe dream at time of writing)
const indexHtmlErrors = noIndex const indexHtmlErrors = noIndex
@ -35,35 +30,35 @@ export const validatePage = (page, getComponent) => {
]); ]);
*/ */
return errors; return errors
} }
export const validatePages = (pages, getComponent) => { export const validatePages = (pages, getComponent) => {
let errors = []
const error = message => errors.push(message)
let errors = []; if (!pages.main) {
const error = message => errors.push(message); error("must have a 'main' page")
}
if (!pages.main) {
error("must have a 'main' page");
}
if (!pages.unauthenticated) { if (!pages.unauthenticated) {
error("must have a 'unauthenticated' (login) page"); error("must have a 'unauthenticated' (login) page")
} }
if (!pages.componentLibraries if (
|| !isArray(pages.componentLibraries) !pages.componentLibraries ||
|| pages.componentLibraries.length === 0) { !isArray(pages.componentLibraries) ||
pages.componentLibraries.length === 0
) {
error("componentLibraries must be set to a non-empty array of strings")
}
error("componentLibraries must be set to a non-empty array of strings"); const pageErrors = pipe(pages, [
} keys,
filter(k => k !== "componentLibraries"),
const pageErrors = pipe(pages, [ map(k => validatePage(pages[k], getComponent)),
keys, flatten,
filter(k => k !== "componentLibraries"), ])
map(k => validatePage(pages[k], getComponent)),
flatten
]);
return [...errors, ...pageErrors]; return [...errors, ...pageErrors]
} }

71
packages/builder/tests/buildCodeForScreen.spec.js

@ -1,29 +1,27 @@
import { buildCodeForScreens } from "../src/builderStore/buildCodeForScreens"; import { buildCodeForScreens } from "../src/builderStore/buildCodeForScreens"
describe("buildCodeForScreen",() => {
describe("buildCodeForScreen", () => {
it("should package _code into runnable function, for simple screen props", () => { it("should package _code into runnable function, for simple screen props", () => {
const screen = { const screen = {
props: { props: {
_id: "1234", _id: "1234",
_code: "render('render argument');" _code: "render('render argument');",
} },
} }
let renderArg; let renderArg
const render = (arg) => { const render = arg => {
renderArg = arg; renderArg = arg
} }
const uiFunctions = getFunctions(screen); const uiFunctions = getFunctions(screen)
const targetfunction = uiFunctions[screen.props._id];
expect(targetfunction).toBeDefined();
targetfunction(render); const targetfunction = uiFunctions[screen.props._id]
expect(targetfunction).toBeDefined()
expect(renderArg).toBe("render argument"); targetfunction(render)
}); expect(renderArg).toBe("render argument")
})
it("should package _code into runnable function, for _children ", () => { it("should package _code into runnable function, for _children ", () => {
const screen = { const screen = {
@ -31,34 +29,31 @@ describe("buildCodeForScreen",() => {
_id: "parent", _id: "parent",
_code: "render('parent argument');", _code: "render('parent argument');",
_children: [ _children: [
{ {
_id: "child1", _id: "child1",
_code: "render('child 1 argument');" _code: "render('child 1 argument');",
}, },
{ {
_id: "child2", _id: "child2",
_code: "render('child 2 argument');" _code: "render('child 2 argument');",
} },
] ],
} },
} }
let renderArg; let renderArg
const render = (arg) => { const render = arg => {
renderArg = arg; renderArg = arg
} }
const uiFunctions = getFunctions(screen); const uiFunctions = getFunctions(screen)
const targetfunction = uiFunctions["child2"];
expect(targetfunction).toBeDefined();
targetfunction(render); const targetfunction = uiFunctions["child2"]
expect(targetfunction).toBeDefined()
expect(renderArg).toBe("child 2 argument"); targetfunction(render)
expect(renderArg).toBe("child 2 argument")
}) })
})
}); const getFunctions = screen => new Function(buildCodeForScreens([screen]))()
const getFunctions = (screen) =>
new Function(buildCodeForScreens([screen]))();

42
packages/builder/tests/buildPropsHierarchy.spec.js

@ -1,31 +1,25 @@
import { componentsAndScreens } from "./testData"; import { componentsAndScreens } from "./testData"
import { import { find } from "lodash/fp"
find import { buildPropsHierarchy } from "../src/userInterface/pagesParsing/buildPropsHierarchy"
} from "lodash/fp";
import { buildPropsHierarchy } from "../src/userInterface/pagesParsing/buildPropsHierarchy";
describe("buildPropsHierarchy", () => { describe("buildPropsHierarchy", () => {
it("should build a complex component children", () => {
const { components, screens } = componentsAndScreens()
const allprops = buildPropsHierarchy(components, screens, "ButtonGroup")
it("should build a complex component children", () => { expect(allprops._component).toEqual("budibase-components/div")
const {components, screens} = componentsAndScreens(); const primaryButtonProps = () => ({
_component: "budibase-components/Button",
})
const allprops = buildPropsHierarchy( const button1 = primaryButtonProps()
components, screens, "ButtonGroup"); button1.contentText = "Button 1"
expect(allprops._children[0]).toEqual(button1)
expect(allprops._component).toEqual("budibase-components/div"); const button2 = primaryButtonProps()
button2.contentText = "Button 2"
const primaryButtonProps = () => ({ expect(allprops._children[1]).toEqual(button2)
_component: "budibase-components/Button" })
}); })
const button1 = primaryButtonProps();
button1.contentText = "Button 1";
expect(allprops._children[0]).toEqual(button1);
const button2 = primaryButtonProps();
button2.contentText = "Button 2";
expect(allprops._children[1]).toEqual(button2)
});
});

101
packages/builder/tests/componentDependencies.spec.js

@ -1,52 +1,55 @@
import { import { componentDependencies } from "../src/userInterface/pagesParsing/findDependencies"
componentDependencies import { componentsAndScreens } from "./testData"
} from "../src/userInterface/pagesParsing/findDependencies";
import { componentsAndScreens } from "./testData";
import { some, find } from "lodash/fp" import { some, find } from "lodash/fp"
describe("component dependencies", () => { describe("component dependencies", () => {
const contains = (result, name) => some(c => c.name === name)(result)
const contains = (result, name) =>
some(c => c.name === name)(result); const get = (all, name) => find(c => c.name === name)(all)
const get = (all, name) => it("should include component that inheirts", () => {
find(c => c.name === name)(all); const { components, screens } = componentsAndScreens()
it("should include component that inheirts", () => { const result = componentDependencies(
{},
const {components, screens} = componentsAndScreens(); screens,
components,
const result = componentDependencies( get([...components, ...screens], "budibase-components/TextBox")
{}, screens, components, )
get([...components, ...screens], "budibase-components/TextBox"));
expect(contains(result.dependantComponents, "common/SmallTextbox")).toBe(
expect(contains(result.dependantComponents, "common/SmallTextbox")).toBe(true); true
)
}); })
it("should include component that nests", () => { it("should include component that nests", () => {
const {components, screens} = componentsAndScreens(); const { components, screens } = componentsAndScreens()
const result = componentDependencies( const result = componentDependencies(
{}, screens, components, {},
get([...components, ...screens], "budibase-components/Button")); screens,
components,
expect(contains(result.dependantComponents, "ButtonGroup")).toBe(true); get([...components, ...screens], "budibase-components/Button")
)
});
expect(contains(result.dependantComponents, "ButtonGroup")).toBe(true)
it("should include components n page apbody", () => { })
const {components, screens} = componentsAndScreens();
const pages = { it("should include components n page apbody", () => {
main: { const { components, screens } = componentsAndScreens()
appBody: "PrimaryButton" const pages = {
} main: {
}; appBody: "PrimaryButton",
},
const result = componentDependencies( }
pages, screens, components,
get([...components, ...screens], "PrimaryButton")); const result = componentDependencies(
pages,
expect(result.dependantPages).toEqual(["main"]); screens,
}); components,
}) get([...components, ...screens], "PrimaryButton")
)
expect(result.dependantPages).toEqual(["main"])
})
})

330
packages/builder/tests/createDefaultProps.spec.js

@ -1,232 +1,212 @@
import { import { createProps } from "../src/userInterface/pagesParsing/createProps"
createProps, import { keys, some } from "lodash/fp"
} from "../src/userInterface/pagesParsing/createProps"; import { BB_STATE_BINDINGPATH } from "@budibase/client/src/state/isState"
import {
keys, some
} from "lodash/fp";
import {
BB_STATE_BINDINGPATH
} from "@budibase/client/src/state/isState";
describe("createDefaultProps", () => { describe("createDefaultProps", () => {
const getcomponent = () => ({
name: "some_component",
props: {
fieldName: { type: "string", default: "something" },
},
})
const getcomponent = () => ({ it("should create a object with single string value, when default string field set", () => {
name:"some_component", const { props, errors } = createProps(getcomponent())
props: {
fieldName: {type:"string", default:"something"}
}
});
it("should create a object with single string value, when default string field set", () => { expect(errors).toEqual([])
expect(props.fieldName).toBeDefined()
const { props, errors } = createProps(getcomponent()); expect(props.fieldName).toBe("something")
expect(keys(props).length).toBe(3)
})
expect(errors).toEqual([]); it("should set component name", () => {
expect(props.fieldName).toBeDefined(); const { props, errors } = createProps(getcomponent())
expect(props.fieldName).toBe("something");
expect(keys(props).length).toBe(3);
});
it("should set component name", () => { expect(errors).toEqual([])
expect(props._component).toBe("some_component")
})
const { props, errors } = createProps(getcomponent()); it("should return error when component name not supplied", () => {
const comp = getcomponent()
comp.name = ""
expect(errors).toEqual([]); const { errors } = createProps(comp)
expect(props._component).toBe("some_component");
});
it("should return error when component name not supplied", () => { expect(errors.length).toEqual(1)
const comp = getcomponent(); })
comp.name = "";
const { errors } = createProps(comp); it("should create a object with single blank string value, when no default", () => {
const comp = getcomponent()
comp.props.fieldName = { type: "string" }
expect(errors.length).toEqual(1); const { props, errors } = createProps(comp)
});
it("should create a object with single blank string value, when no default", () => { expect(errors).toEqual([])
const comp = getcomponent(); expect(props.fieldName).toBeDefined()
comp.props.fieldName = {type:"string"}; expect(props.fieldName).toBe("")
})
const { props, errors } = createProps(comp); it("should create a object with single blank string value, when prop definition is 'string' ", () => {
const comp = getcomponent()
comp.props.fieldName = "string"
expect(errors).toEqual([]); const { props, errors } = createProps(comp)
expect(props.fieldName).toBeDefined();
expect(props.fieldName).toBe("");
});
it("should create a object with single blank string value, when prop definition is 'string' ", () => { expect(errors).toEqual([])
const comp = getcomponent(); expect(props.fieldName).toBeDefined()
comp.props.fieldName = "string"; expect(props.fieldName).toBe("")
})
const { props, errors } = createProps(comp); it("should create a object with single fals value, when prop definition is 'bool' ", () => {
const comp = getcomponent()
comp.props.isVisible = "bool"
expect(errors).toEqual([]); const { props, errors } = createProps(comp)
expect(props.fieldName).toBeDefined();
expect(props.fieldName).toBe("");
});
it("should create a object with single fals value, when prop definition is 'bool' ", () => { expect(errors).toEqual([])
const comp = getcomponent(); expect(props.isVisible).toBeDefined()
comp.props.isVisible = "bool"; expect(props.isVisible).toBe(false)
})
const { props, errors } = createProps(comp); it("should create a object with single 0 value, when prop definition is 'number' ", () => {
const comp = getcomponent()
comp.props.width = "number"
expect(errors).toEqual([]); const { props, errors } = createProps(comp)
expect(props.isVisible).toBeDefined();
expect(props.isVisible).toBe(false);
});
it("should create a object with single 0 value, when prop definition is 'number' ", () => { expect(errors).toEqual([])
expect(props.width).toBeDefined()
const comp = getcomponent(); expect(props.width).toBe(0)
comp.props.width = "number"; })
const { props, errors } = createProps(comp); it("should create a object with empty _children array, when children===true ", () => {
const comp = getcomponent()
comp.children = true
expect(errors).toEqual([]); const { props, errors } = createProps(comp)
expect(props.width).toBeDefined();
expect(props.width).toBe(0);
});
it("should create a object with empty _children array, when children===true ", () => { expect(errors).toEqual([])
const comp = getcomponent(); expect(props._children).toBeDefined()
comp.children = true; expect(props._children).toEqual([])
})
const { props, errors } = createProps(comp); it("should create a object without _children array, when children===false ", () => {
const comp = getcomponent()
comp.children = false
expect(errors).toEqual([]); const { props, errors } = createProps(comp)
expect(props._children).toBeDefined();
expect(props._children).toEqual([]);
});
it("should create a object without _children array, when children===false ", () => { expect(errors).toEqual([])
const comp = getcomponent(); expect(props._children).not.toBeDefined()
comp.children = false; })
const { props, errors } = createProps(comp); it("should create a object with single empty array, when prop definition is 'event' ", () => {
const comp = getcomponent()
comp.props.onClick = "event"
expect(errors).toEqual([]); const { props, errors } = createProps(comp)
expect(props._children).not.toBeDefined();
});
it("should create a object with single empty array, when prop definition is 'event' ", () => { expect(errors).toEqual([])
expect(props.onClick).toBeDefined()
expect(props.onClick).toEqual([])
})
const comp = getcomponent(); it("should create a object with empty state when prop def is 'state' ", () => {
comp.props.onClick = "event"; const comp = getcomponent()
comp.props.data = "state"
const { props, errors } = createProps(comp); const { props, errors } = createProps(comp)
expect(errors).toEqual([]); expect(errors).toEqual([])
expect(props.onClick).toBeDefined(); expect(props.data[BB_STATE_BINDINGPATH]).toBeDefined()
expect(props.onClick).toEqual([]); expect(props.data[BB_STATE_BINDINGPATH]).toBe("")
}); })
it("should create a object with empty state when prop def is 'state' ", () => { it("should create a object children array when children == true ", () => {
const comp = getcomponent()
comp.children = true
const comp = getcomponent(); const { props, errors } = createProps(comp)
comp.props.data = "state";
const { props, errors } = createProps(comp); expect(errors).toEqual([])
expect(props._children).toBeDefined()
expect(props._children).toEqual([])
})
expect(errors).toEqual([]); it("should create a _children array when children not defined ", () => {
expect(props.data[BB_STATE_BINDINGPATH]).toBeDefined(); const comp = getcomponent()
expect(props.data[BB_STATE_BINDINGPATH]).toBe("");
});
it("should create a object children array when children == true ", () => { const { props, errors } = createProps(comp)
const comp = getcomponent(); expect(errors).toEqual([])
comp.children = true; expect(props._children).toBeDefined()
expect(props._children).toEqual([])
})
const { props, errors } = createProps(comp); it("should not create _children array when children=false ", () => {
const comp = getcomponent()
comp.children = false
expect(errors).toEqual([]); const { props, errors } = createProps(comp)
expect(props._children).toBeDefined();
expect(props._children).toEqual([]);
});
it("should create a _children array when children not defined ", () => { expect(errors).toEqual([])
expect(props._children).not.toBeDefined()
})
const comp = getcomponent(); it("should create an object with multiple prop names", () => {
const comp = getcomponent()
comp.props.fieldName = "string"
comp.props.fieldLength = { type: "number", default: 500 }
const { props, errors } = createProps(comp); const { props, errors } = createProps(comp)
expect(errors).toEqual([]); expect(errors).toEqual([])
expect(props._children).toBeDefined(); expect(props.fieldName).toBeDefined()
expect(props._children).toEqual([]); expect(props.fieldName).toBe("")
}); expect(props.fieldLength).toBeDefined()
expect(props.fieldLength).toBe(500)
})
it("should not create _children array when children=false ", () => { it("should return error when invalid type", () => {
const comp = getcomponent()
comp.props.fieldName = "invalid type name"
comp.props.fieldLength = { type: "invalid type name " }
const comp = getcomponent(); const { errors } = createProps(comp)
comp.children = false;
const { props, errors } = createProps(comp); expect(errors.length).toBe(2)
expect(some(e => e.propName === "fieldName")(errors)).toBeTruthy()
expect(some(e => e.propName === "fieldLength")(errors)).toBeTruthy()
})
expect(errors).toEqual([]); it("should return error default value is not of declared type", () => {
expect(props._children).not.toBeDefined(); const comp = getcomponent()
}); comp.props.fieldName = { type: "string", default: 1 }
it("should create an object with multiple prop names", () => { const { errors } = createProps(comp)
const comp = getcomponent();
comp.props.fieldName = "string";
comp.props.fieldLength = { type: "number", default: 500 };
const { props, errors } = createProps(comp); expect(errors.length).toBe(1)
expect(some(e => e.propName === "fieldName")(errors)).toBeTruthy()
})
expect(errors).toEqual([]); it("should merge in derived props", () => {
expect(props.fieldName).toBeDefined(); const propDef = {
expect(props.fieldName).toBe(""); fieldName: "string",
expect(props.fieldLength).toBeDefined(); fieldLength: { type: "number", default: 500 },
expect(props.fieldLength).toBe(500); }
})
it("should return error when invalid type", () => { const comp = getcomponent()
const comp = getcomponent(); comp.props.fieldName = "string"
comp.props.fieldName = "invalid type name"; comp.props.fieldLength = { type: "number", default: 500 }
comp.props.fieldLength = { type: "invalid type name "};
const { errors } = createProps(comp); const derivedFrom = {
fieldName: "surname",
}
expect(errors.length).toBe(2); const { props, errors } = createProps(comp, derivedFrom)
expect(some(e => e.propName === "fieldName")(errors)).toBeTruthy();
expect(some(e => e.propName === "fieldLength")(errors)).toBeTruthy();
});
it("should return error default value is not of declared type", () => {
const comp = getcomponent();
comp.props.fieldName = {type:"string", default: 1}
const { errors } = createProps(comp);
expect(errors.length).toBe(1);
expect(some(e => e.propName === "fieldName")(errors)).toBeTruthy();
});
it("should merge in derived props", () => {
const propDef = {
fieldName: "string",
fieldLength: { type: "number", default: 500}
};
const comp = getcomponent();
comp.props.fieldName = "string";
comp.props.fieldLength = { type: "number", default: 500};
const derivedFrom = {
fieldName: "surname"
};
const { props, errors } = createProps(comp, derivedFrom);
expect(errors.length).toBe(0);
expect(props.fieldName).toBe("surname");
expect(props.fieldLength).toBe(500);
});
expect(errors.length).toBe(0)
expect(props.fieldName).toBe("surname")
expect(props.fieldLength).toBe(500)
})
}) })

85
packages/builder/tests/expandPropDef.spec.js

@ -1,50 +1,45 @@
import { expandComponentDefinition } from "../src/userInterface/pagesParsing/types"; import { expandComponentDefinition } from "../src/userInterface/pagesParsing/types"
const componentDef = () => ({ const componentDef = () => ({
name: "comp", name: "comp",
props: { props: {
label: "string", label: "string",
width: {type:"number"}, width: { type: "number" },
color: {type:"string", required:true}, color: { type: "string", required: true },
} },
}) })
describe("expandPropDefintion", () => { describe("expandPropDefintion", () => {
it("should expand property defined as string, into default for that type", () => {
it("should expand property defined as string, into default for that type", () => { const result = expandComponentDefinition(componentDef())
const result = expandComponentDefinition(componentDef()); expect(result.props.label.type).toBe("string")
expect(result.props.label.required).toBe(false)
expect(result.props.label.type).toBe("string"); })
expect(result.props.label.required).toBe(false);
}); it("should add members to property defined as object, when members do not exist", () => {
const result = expandComponentDefinition(componentDef())
it("should add members to property defined as object, when members do not exist", () => { expect(result.props.width.required).toBe(false)
})
const result = expandComponentDefinition(componentDef());
expect(result.props.width.required).toBe(false); it("should not override existing memebers", () => {
}); const result = expandComponentDefinition(componentDef())
expect(result.props.color.required).toBe(true)
it("should not override existing memebers", () => { })
const result = expandComponentDefinition(componentDef()); it("should set children=true when not included", () => {
expect(result.props.color.required).toBe(true); const result = expandComponentDefinition(componentDef())
}); expect(result.children).toBe(true)
})
it("should set children=true when not included", () => {
const result = expandComponentDefinition(componentDef()); it("should not change children when specified", () => {
expect(result.children).toBe(true); const c = componentDef()
}); c.children = false
const result = expandComponentDefinition(c)
it("should not change children when specified", () => { expect(result.children).toBe(false)
const c = componentDef();
c.children = false; c.children = true
const result = expandComponentDefinition(c); const result2 = expandComponentDefinition(c)
expect(result.children).toBe(false); expect(result2.children).toBe(true)
})
c.children = true; })
const result2 = expandComponentDefinition(c);
expect(result2.children).toBe(true);
});
})

503
packages/builder/tests/generate_css.spec.js

@ -1,191 +1,175 @@
import { generate_css, make_margin, generate_screen_css } from '../src/builderStore/generate_css.js'; import {
generate_css,
describe('make_margin', () => { make_margin,
test('it should generate a valid rule', () => { generate_screen_css,
expect(make_margin(["1", "1", "1", "1"])).toEqual('1px 1px 1px 1px') } from "../src/builderStore/generate_css.js"
})
describe("make_margin", () => {
test('empty values should output 0', () => { test("it should generate a valid rule", () => {
expect(make_margin(["1", "1", "", ""])).toEqual('1px 1px 0px 0px') expect(make_margin(["1", "1", "1", "1"])).toEqual("1px 1px 1px 1px")
expect(make_margin(["1", "", "", "1"])).toEqual('1px 0px 0px 1px') })
expect(make_margin(["", "", "", ""])).toEqual('0px 0px 0px 0px')
}) test("empty values should output 0", () => {
expect(make_margin(["1", "1", "", ""])).toEqual("1px 1px 0px 0px")
expect(make_margin(["1", "", "", "1"])).toEqual("1px 0px 0px 1px")
expect(make_margin(["", "", "", ""])).toEqual("0px 0px 0px 0px")
})
}) })
describe('generate_css', () => { describe("generate_css", () => {
test('it should generate a valid css rule: grid-area', () => { test("it should generate a valid css rule: grid-area", () => {
expect( expect(generate_css({ layout: { gridarea: ["", "", "", ""] } })).toEqual({
generate_css({ layout: { gridarea: ["", "", "", ""] } }) layout: "",
).toEqual({ position: "",
layout: '', })
position: '' })
});
}) test("it should generate a valid css rule: grid-gap", () => {
expect(generate_css({ layout: { gap: "10" } })).toEqual({
test('it should generate a valid css rule: grid-gap', () => { layout: "grid-gap: 10px;\ndisplay: grid;",
expect( position: "",
generate_css({ layout: { gap: "10" } }) })
).toEqual({ })
layout: 'grid-gap: 10px;\ndisplay: grid;',
position: '' test("it should generate a valid css rule: column 1", () => {
}); expect(generate_css({ position: { column: ["", ""] } })).toEqual({
}) layout: "",
position: "",
test('it should generate a valid css rule: column 1', () => { })
expect( })
generate_css({ position: { column: ["", ""] } }
)).toEqual({ layout: '', position: '' }); test("it should generate a valid css rule: column 2", () => {
}) expect(generate_css({ position: { column: ["1", ""] } })).toEqual({
position: "grid-column-start: 1;",
test('it should generate a valid css rule: column 2', () => { layout: "",
expect( })
generate_css({ position: { column: ["1", ""] } }) })
).toEqual({
position: 'grid-column-start: 1;', test("it should generate a valid css rule: column 3", () => {
layout: '' expect(generate_css({ position: { column: ["", "1"] } })).toEqual({
}); position: "grid-column-end: 1;",
}) layout: "",
})
test('it should generate a valid css rule: column 3', () => { })
expect(
generate_css({ position: { column: ["", "1"] } }) test("it should generate a valid css rule: column 4", () => {
).toEqual({ expect(generate_css({ position: { column: ["1", "1"] } })).toEqual({
position: 'grid-column-end: 1;', position: "grid-column-start: 1;\ngrid-column-end: 1;",
layout: '' layout: "",
}); })
}) })
test('it should generate a valid css rule: column 4', () => { test("it should generate a valid css rule: row 1", () => {
expect( expect(generate_css({ position: { row: ["", ""] } })).toEqual({
generate_css({ position: { column: ["1", "1"] } }) layout: "",
).toEqual({ position: "",
position: 'grid-column-start: 1;\ngrid-column-end: 1;', })
layout: '' })
});
}) test("it should generate a valid css rule: row 2", () => {
expect(generate_css({ position: { row: ["1", ""] } })).toEqual({
test('it should generate a valid css rule: row 1', () => { position: "grid-row-start: 1;",
expect( layout: "",
generate_css({ position: { row: ["", ""] } }) })
).toEqual({ layout: '', position: '' }); })
})
test("it should generate a valid css rule: row 3", () => {
test('it should generate a valid css rule: row 2', () => { expect(generate_css({ position: { row: ["", "1"] } })).toEqual({
expect( position: "grid-row-end: 1;",
generate_css({ position: { row: ["1", ""] } }) layout: "",
).toEqual({ })
position: 'grid-row-start: 1;', })
layout: ''
}); test("it should generate a valid css rule: row 4", () => {
}) expect(generate_css({ position: { row: ["1", "1"] } })).toEqual({
position: "grid-row-start: 1;\ngrid-row-end: 1;",
test('it should generate a valid css rule: row 3', () => { layout: "",
expect( })
generate_css({ position: { row: ["", "1"] } }) })
).toEqual({
position: 'grid-row-end: 1;', test("it should generate a valid css rule: padding 1", () => {
layout: '' expect(
}); generate_css({ position: { padding: ["1", "1", "1", "1"] } })
}) ).toEqual({
position: "padding: 1px 1px 1px 1px;",
test('it should generate a valid css rule: row 4', () => { layout: "",
expect( })
generate_css({ position: { row: ["1", "1"] } }) })
).toEqual({
position: 'grid-row-start: 1;\ngrid-row-end: 1;', test("it should generate a valid css rule: padding 2", () => {
layout: '' expect(generate_css({ position: { padding: ["1", "", "", "1"] } })).toEqual(
}); {
}) position: "padding: 1px 0px 0px 1px;",
layout: "",
test('it should generate a valid css rule: padding 1', () => { }
expect( )
generate_css({ position: { padding: ["1", "1", "1", "1"] } }) })
).toEqual({
position: 'padding: 1px 1px 1px 1px;', test("it should generate a valid css rule: margin 1", () => {
layout: '' expect(
}); generate_css({ position: { margin: ["1", "1", "1", "1"] } })
}) ).toEqual({
position: "margin: 1px 1px 1px 1px;",
test('it should generate a valid css rule: padding 2', () => { layout: "",
expect( })
generate_css({ position: { padding: ["1", "", "", "1"] } }) })
).toEqual({
position: 'padding: 1px 0px 0px 1px;', test("it should generate a valid css rule: margin 2", () => {
layout: '' expect(generate_css({ position: { margin: ["1", "", "", "1"] } })).toEqual({
}); position: "margin: 1px 0px 0px 1px;",
}) layout: "",
})
test('it should generate a valid css rule: margin 1', () => { })
expect(
generate_css({ position: { margin: ["1", "1", "1", "1"] } }) test("it should generate a valid css rule: z-index 1", () => {
).toEqual({ expect(generate_css({ position: { zindex: "" } })).toEqual({
position: 'margin: 1px 1px 1px 1px;', position: "",
layout: '' layout: "",
}); })
}) })
test('it should generate a valid css rule: margin 2', () => { test("it should generate a valid css rule: z-index 2", () => {
expect( expect(generate_css({ position: { zindex: "1" } })).toEqual({
generate_css({ position: { margin: ["1", "", "", "1"] } }) position: "z-index: 1;",
).toEqual({ layout: "",
position: 'margin: 1px 0px 0px 1px;', })
layout: '' })
});
})
test('it should generate a valid css rule: z-index 1', () => {
expect(
generate_css({ position: { zindex: "" } })
).toEqual({
position: '',
layout: ''
});
})
test('it should generate a valid css rule: z-index 2', () => {
expect(
generate_css({ position: { zindex: "1" } })
).toEqual({
position: 'z-index: 1;',
layout: ''
});
})
}) })
describe('generate_screen_css', () => { describe("generate_screen_css", () => {
test('it should compile the css for a list of components', () => { test("it should compile the css for a list of components", () => {
const components = [ const components = [
{ {
_styles: { _styles: {
layout: { gridarea: ["", "", "", ""] }, layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] } position: { margin: ["1", "1", "1", "1"] },
}, },
_id: 1 _id: 1,
}, },
{ {
_styles: { _styles: {
layout: { gridarea: ["", "", "", ""] }, layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] } position: { margin: ["1", "1", "1", "1"] },
}, },
_id: 2 _id: 2,
}, { },
_styles: { {
layout: { gridarea: ["", "", "", ""] }, _styles: {
position: { margin: ["1", "1", "1", "1"] } layout: { gridarea: ["", "", "", ""] },
}, position: { margin: ["1", "1", "1", "1"] },
_id: 3 },
} _id: 3,
, { },
_styles: { {
layout: { gridarea: ["", "", "", ""] }, _styles: {
position: { margin: ["1", "1", "1", "1"] } layout: { gridarea: ["", "", "", ""] },
}, position: { margin: ["1", "1", "1", "1"] },
_id: 4 },
} _id: 4,
] },
]
const compiled = `.pos-1 {
const compiled = `.pos-1 {
margin: 1px 1px 1px 1px; margin: 1px 1px 1px 1px;
} }
.lay-1 { .lay-1 {
@ -210,80 +194,79 @@ margin: 1px 1px 1px 1px;
}` }`
expect(generate_screen_css(components)).toEqual(compiled) expect(generate_screen_css(components)).toEqual(compiled)
}) })
test('it should compile the css for a list of components', () => { test("it should compile the css for a list of components", () => {
const components = [ const components = [
{ {
_styles: { _styles: {
layout: { gridarea: ["", "", "", ""] }, layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] } position: { margin: ["1", "1", "1", "1"] },
}, },
_id: 1, _id: 1,
_children: [ _children: [
{ {
_styles: { _styles: {
layout: { gridarea: ["", "", "", ""] }, layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] } position: { margin: ["1", "1", "1", "1"] },
}, },
_id: 2, _id: 2,
_children: [ _children: [
{ {
_styles: { _styles: {
layout: { gridarea: ["", "", "", ""] }, layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] } position: { margin: ["1", "1", "1", "1"] },
}, },
_id: 3, _id: 3,
_children: [ _children: [
{ {
_styles: { _styles: {
layout: { gridarea: ["", "", "", ""] }, layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] } position: { margin: ["1", "1", "1", "1"] },
}, },
_id: 4, _id: 4,
_children: [ _children: [
{ {
_styles: { _styles: {
layout: { gridarea: ["", "", "", ""] }, layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] } position: { margin: ["1", "1", "1", "1"] },
}, },
_id: 5, _id: 5,
_children: [ _children: [],
},
] ],
}, },
] ],
}, },
] ],
}, },
] ],
}, },
] {
}, _styles: {
{ layout: { gridarea: ["", "", "", ""] },
_styles: { position: { margin: ["1", "1", "1", "1"] },
layout: { gridarea: ["", "", "", ""] }, },
position: { margin: ["1", "1", "1", "1"] } _id: 6,
}, },
_id: 6 {
}, { _styles: {
_styles: { layout: { gridarea: ["", "", "", ""] },
layout: { gridarea: ["", "", "", ""] }, position: { margin: ["1", "1", "1", "1"] },
position: { margin: ["1", "1", "1", "1"] } },
}, _id: 7,
_id: 7 },
} {
, { _styles: {
_styles: { layout: { gridarea: ["", "", "", ""] },
layout: { gridarea: ["", "", "", ""] }, position: { margin: ["1", "1", "1", "1"] },
position: { margin: ["1", "1", "1", "1"] } },
}, _id: 8,
_id: 8 },
} ]
]
const compiled = `.pos-1 {
const compiled = `.pos-1 {
margin: 1px 1px 1px 1px; margin: 1px 1px 1px 1px;
} }
.lay-1 { .lay-1 {
@ -332,6 +315,6 @@ margin: 1px 1px 1px 1px;
}` }`
expect(generate_screen_css(components)).toEqual(compiled) expect(generate_screen_css(components)).toEqual(compiled)
}) })
}) })

177
packages/builder/tests/getComponentInfo.spec.js

@ -1,96 +1,91 @@
import {
getInstanceProps,
getScreenInfo ,
getComponentInfo
} from "../src/userInterface/pagesParsing/createProps";
import { import {
keys, some, find getInstanceProps,
} from "lodash/fp"; getScreenInfo,
import { componentsAndScreens } from "./testData"; getComponentInfo,
} from "../src/userInterface/pagesParsing/createProps"
import { keys, some, find } from "lodash/fp"
import { componentsAndScreens } from "./testData"
describe("getComponentInfo", () => { describe("getComponentInfo", () => {
it("should return default props for root component", () => {
it("should return default props for root component", () => { const result = getComponentInfo(
const result = getComponentInfo( componentsAndScreens().components,
componentsAndScreens().components, "budibase-components/TextBox"
"budibase-components/TextBox"); )
expect(result.errors).toEqual([]); expect(result.errors).toEqual([])
expect(result.fullProps).toEqual({ expect(result.fullProps).toEqual({
_component: "budibase-components/TextBox", _component: "budibase-components/TextBox",
size: "", size: "",
isPassword: false, isPassword: false,
placeholder: "", placeholder: "",
label:"" label: "",
}); })
}); })
it("getInstanceProps should set supplied props on top of default props", () => { it("getInstanceProps should set supplied props on top of default props", () => {
const result = getInstanceProps( const result = getInstanceProps(
getComponentInfo( getComponentInfo(
componentsAndScreens().components, componentsAndScreens().components,
"budibase-components/TextBox"), "budibase-components/TextBox"
{size:"small"}); ),
{ size: "small" }
expect(result).toEqual({ )
_component: "budibase-components/TextBox",
size: "small", expect(result).toEqual({
isPassword: false, _component: "budibase-components/TextBox",
placeholder: "", size: "small",
label:"" isPassword: false,
}); placeholder: "",
label: "",
}); })
}); })
})
describe("getScreenInfo", () => { describe("getScreenInfo", () => {
const getScreen = (screens, name) => find(s => s.name === name)(screens)
const getScreen = (screens, name) =>
find(s => s.name === name)(screens); it("should return correct props for screen", () => {
const { components, screens } = componentsAndScreens()
it("should return correct props for screen", () => { const result = getScreenInfo(
const {components, screens} = componentsAndScreens(); components,
const result = getScreenInfo( getScreen(screens, "common/SmallTextbox")
components, )
getScreen(screens, "common/SmallTextbox"));
expect(result.errors).toEqual([])
expect(result.errors).toEqual([]); expect(result.fullProps).toEqual({
expect(result.fullProps).toEqual({ _component: "common/SmallTextbox",
_component: "common/SmallTextbox", size: "small",
size: "small", isPassword: false,
isPassword: false, placeholder: "",
placeholder: "", label: "",
label:"" })
}); })
});
it("should return correct props for twice derived component", () => {
it("should return correct props for twice derived component", () => { const { components, screens } = componentsAndScreens()
const {components, screens} = componentsAndScreens(); const result = getScreenInfo(
const result = getScreenInfo( components,
components, getScreen(screens, "common/PasswordBox")
getScreen(screens, "common/PasswordBox")); )
expect(result.errors).toEqual([]); expect(result.errors).toEqual([])
expect(result.fullProps).toEqual({ expect(result.fullProps).toEqual({
_component: "common/PasswordBox", _component: "common/PasswordBox",
size: "small", size: "small",
isPassword: true, isPassword: true,
placeholder: "", placeholder: "",
label:"" label: "",
}); })
}); })
it("should list unset props as those that are only defined in root", () => {
it("should list unset props as those that are only defined in root", () => { const { components, screens } = componentsAndScreens()
const {components, screens} = componentsAndScreens(); const result = getScreenInfo(
const result = getScreenInfo( components,
components, getScreen(screens, "common/PasswordBox")
getScreen(screens, "common/PasswordBox")); )
expect(result.unsetProps).toEqual([ expect(result.unsetProps).toEqual(["placeholder", "label"])
"placeholder", "label"]); })
}); })
})

58
packages/builder/tests/renameScreen.spec.js

@ -1,27 +1,21 @@
import { import { getExactComponent } from "../src/userInterface/pagesParsing/searchComponents"
getExactComponent import { rename } from "../src/userInterface/pagesParsing/renameScreen"
} from "../src/userInterface/pagesParsing/searchComponents"; import { componentsAndScreens } from "./testData"
import {
rename
} from "../src/userInterface/pagesParsing/renameScreen";
import { componentsAndScreens } from "./testData";
describe("rename component", () => { describe("rename component", () => {
it("should change the name of the component, duh", () => { it("should change the name of the component, duh", () => {
const { screens } = componentsAndScreens()
const {screens} = componentsAndScreens(); const result = rename({}, screens, "PrimaryButton", "MainButton")
const result = rename({}, screens, "PrimaryButton", "MainButton");
const newComponent = getExactComponent(result.screens, "MainButton");
const oldComponent = getExactComponent(result.screens, "Primary");
expect(oldComponent).toBeUndefined();
expect(newComponent).toBeDefined();
expect(newComponent.name).toBe("MainButton");
}); const newComponent = getExactComponent(result.screens, "MainButton")
const oldComponent = getExactComponent(result.screens, "Primary")
expect(oldComponent).toBeUndefined()
expect(newComponent).toBeDefined()
expect(newComponent.name).toBe("MainButton")
})
/* this may be usefull if we have user defined components /* this may be usefull if we have user defined components
it("should change name of nested _components", () => { it("should change name of nested _components", () => {
const {screens} = componentsAndScreens(); const {screens} = componentsAndScreens();
const result = rename({}, screens, "PrimaryButton", "MainButton"); const result = rename({}, screens, "PrimaryButton", "MainButton");
@ -32,21 +26,19 @@ describe("rename component", () => {
}); });
*/ */
it("should change name of page appBody", () => {
const { screens } = componentsAndScreens()
const pages = {
main: {
appBody: "PrimaryButton",
},
}
it("should change name of page appBody", () => { const result = rename(pages, screens, "PrimaryButton", "MainButton")
const {screens} = componentsAndScreens(); expect(result.pages.main.appBody).toBe("MainButton")
const pages = { })
main: {
appBody: "PrimaryButton"
}
};
const result = rename(pages, screens, "PrimaryButton", "MainButton");
expect(result.pages.main.appBody).toBe("MainButton");
});
/* this may be usefull if we have user defined components /* this may be usefull if we have user defined components
it("should return a list of changed components", () => { it("should return a list of changed components", () => {
const {screens} = componentsAndScreens(); const {screens} = componentsAndScreens();
const result = rename({}, screens, "PrimaryButton", "MainButton"); const result = rename({}, screens, "PrimaryButton", "MainButton");
@ -58,4 +50,4 @@ describe("rename component", () => {
}); });
*/ */
}) })

152
packages/builder/tests/searchComponentsProps.spec.js

@ -1,94 +1,74 @@
import { import {
searchAllComponents, searchAllComponents,
getExactComponent, getExactComponent,
getAncestorProps getAncestorProps,
} from "../src/userInterface/pagesParsing/searchComponents"; } from "../src/userInterface/pagesParsing/searchComponents"
import { componentsAndScreens } from "./testData"; import { componentsAndScreens } from "./testData"
describe("searchAllComponents", () => { describe("searchAllComponents", () => {
it("should match component by name", () => {
it("should match component by name", () => { const results = searchAllComponents(
componentsAndScreens().components,
const results = searchAllComponents( "Textbox"
componentsAndScreens().components, )
"Textbox"
); expect(results.length).toBe(1)
expect(results[0].name).toBe("budibase-components/TextBox")
expect(results.length).toBe(1); })
expect(results[0].name).toBe("budibase-components/TextBox");
it("should match component by tag", () => {
}); const results = searchAllComponents(
componentsAndScreens().components,
it("should match component by tag", () => { "record"
)
const results = searchAllComponents(
componentsAndScreens().components, expect(results.length).toBe(1)
"record" expect(results[0].name).toBe("budibase-components/RecordView")
); })
})
expect(results.length).toBe(1);
expect(results[0].name).toBe("budibase-components/RecordView");
});
});
describe("getExactComponent", () => { describe("getExactComponent", () => {
it("should get component by name", () => { it("should get component by name", () => {
const {components, screens} = componentsAndScreens(); const { components, screens } = componentsAndScreens()
const result = getExactComponent( const result = getExactComponent(
[...components, ...screens], [...components, ...screens],
"common/SmallTextbox" "common/SmallTextbox"
) )
expect(result).toBeDefined(); expect(result).toBeDefined()
expect(result.name).toBe("common/SmallTextbox"); expect(result.name).toBe("common/SmallTextbox")
}); })
it("should return nothing when no result (should not fail)", () => { it("should return nothing when no result (should not fail)", () => {
const {components, screens} = componentsAndScreens(); const { components, screens } = componentsAndScreens()
const result = getExactComponent( const result = getExactComponent([...components, ...screens], "bla/bla/bla")
[...components, ...screens],
"bla/bla/bla" expect(result).not.toBeDefined()
) })
})
expect(result).not.toBeDefined();
});
});
describe("getAncestorProps", () => { describe("getAncestorProps", () => {
it("should return props of root component", () => {
it("should return props of root component", () => { const result = getAncestorProps(
componentsAndScreens().components,
const result = getAncestorProps( "budibase-components/TextBox"
componentsAndScreens().components, )
"budibase-components/TextBox"
); expect(result).toEqual([componentsAndScreens().components[0].props])
})
expect(result).toEqual([
componentsAndScreens().components[0].props it("should return props of inherited and current component, in order", () => {
]); const { components, screens } = componentsAndScreens()
const allComponentsAndScreens = [...components, ...screens]
});
const result = getAncestorProps(
it("should return props of inherited and current component, in order", () => { allComponentsAndScreens,
"common/PasswordBox"
const {components, screens} = componentsAndScreens(); )
const allComponentsAndScreens = [...components, ...screens];
expect(result).toEqual([
const result = getAncestorProps( allComponentsAndScreens[0].props,
allComponentsAndScreens, { ...allComponentsAndScreens[5].props },
"common/PasswordBox" ])
); })
expect(result).toEqual([
allComponentsAndScreens[0].props,
{...allComponentsAndScreens[5].props}
]);
});
}) })

197
packages/builder/tests/testData.js

@ -1,102 +1,101 @@
export const componentsAndScreens = () => ({ export const componentsAndScreens = () => ({
components: [ components: [
{ {
name: "budibase-components/TextBox", name: "budibase-components/TextBox",
tags: ["Text", "input"], tags: ["Text", "input"],
children: false, children: false,
props: { props: {
size: {type:"options", options:["small", "medium", "large"]}, size: { type: "options", options: ["small", "medium", "large"] },
isPassword: "bool", isPassword: "bool",
placeholder: "string", placeholder: "string",
label:"string" label: "string",
} },
}, },
{ {
name: "budibase-components/Button", name: "budibase-components/Button",
tags: ["input"], tags: ["input"],
children: true, children: true,
props: { props: {
size: {type:"options", options:["small", "medium", "large"]}, size: { type: "options", options: ["small", "medium", "large"] },
css: "string", css: "string",
contentText: "string" contentText: "string",
} },
}, },
{ {
name: "budibase-components/div", name: "budibase-components/div",
tags: ["input"], tags: ["input"],
props: { props: {
width: "number", width: "number",
} },
}, },
{ {
name:"budibase-components/RecordView", name: "budibase-components/RecordView",
tags: ["record"], tags: ["record"],
props: { props: {
data: "state" data: "state",
} },
} },
], ],
screens: [ screens: [
{ {
name: "common/SmallTextbox", name: "common/SmallTextbox",
props: { props: {
_component: "budibase-components/TextBox", _component: "budibase-components/TextBox",
size: "small" size: "small",
} },
}, },
{ {
name: "common/PasswordBox", name: "common/PasswordBox",
tags: ["mask"], tags: ["mask"],
props: { props: {
_component: "budibase-components/TextBox", _component: "budibase-components/TextBox",
isPassword: true, isPassword: true,
size: "small" size: "small",
} },
}, },
{
name: "PrimaryButton",
props: {
_component: "budibase-components/Button",
css: "btn-primary",
},
},
{ {
name:"PrimaryButton", name: "ButtonGroup",
props: { props: {
_component:"budibase-components/Button", _component: "budibase-components/div",
css:"btn-primary" width: 100,
} _children: [
}, {
_component: "budibase-components/Button",
contentText: "Button 1",
},
{
_component: "budibase-components/Button",
contentText: "Button 2",
},
{
_component: "budibase-components/TextBox",
isPassword: true,
size: "small",
},
],
},
},
{ {
name:"ButtonGroup", name: "Field",
props: { props: {
_component:"budibase-components/div", _component: "budibase-components/div",
width: 100, _children: [
_children: [ {
{ _component: "common/SmallTextbox",
_component: "budibase-components/Button", },
contentText: "Button 1" ],
}, },
{ },
_component: "budibase-components/Button", ],
contentText: "Button 2" })
},
{
_component: "budibase-components/TextBox",
isPassword: true,
size: "small"
}
]
}
},
{
name:"Field",
props: {
_component:"budibase-components/div",
_children:[
{
_component: "common/SmallTextbox"
}
]
}
},
]
});

174
packages/builder/tests/validatePages.spec.js

@ -1,105 +1,95 @@
import { import {
validatePages, validatePages,
validatePage validatePage,
} from "../src/userInterface/pagesParsing/validatePages"; } from "../src/userInterface/pagesParsing/validatePages"
const validPages = () => ({ const validPages = () => ({
"main" : { main: {
"index" : { index: {
"title": "My Cool App" title: "My Cool App",
},
"appBody" : "./main.app.json"
}, },
"unauthenticated" : { appBody: "./main.app.json",
"index" : { },
"title": "My Cool App - Login" unauthenticated: {
}, index: {
"appBody" : "./unauthenticated.app.json" title: "My Cool App - Login",
}, },
"componentLibraries": ["./myComponents"] appBody: "./unauthenticated.app.json",
}); },
componentLibraries: ["./myComponents"],
const getComponent = name => ({ })
testIndexHtml : {
name: "testIndexHtml", const getComponent = name =>
props: { ({
title: "string", testIndexHtml: {
} name: "testIndexHtml",
} props: {
}[name]) title: "string",
},
},
}[name])
describe("validate single page", () => { describe("validate single page", () => {
it("should return no errors when page is valid", () => {
const errors = validatePage(validPages().main, getComponent)
it("should return no errors when page is valid", () => { expect(errors).toEqual([])
const errors = validatePage( })
validPages().main,
getComponent);
expect(errors).toEqual([]);
});
it("should return error when index is not set, or set incorrectly", () => {
let page = validPages().main;
delete page.index;
expect(validatePage(page, getComponent).length).toEqual(1);
});
it("should return error when appBody is not set, or set incorrectly", () => { it("should return error when index is not set, or set incorrectly", () => {
let page = validPages().main; let page = validPages().main
delete page.appBody; delete page.index
expect(validatePage(page, getComponent).length).toEqual(1); expect(validatePage(page, getComponent).length).toEqual(1)
})
page.appBody = true; // not a string it("should return error when appBody is not set, or set incorrectly", () => {
expect(validatePage(page, getComponent).length).toEqual(1); let page = validPages().main
delete page.appBody
expect(validatePage(page, getComponent).length).toEqual(1)
page.appBody = "something.js"; // not a json page.appBody = true // not a string
expect(validatePage(page, getComponent).length).toEqual(1); expect(validatePage(page, getComponent).length).toEqual(1)
}); page.appBody = "something.js" // not a json
expect(validatePage(page, getComponent).length).toEqual(1)
}); })
})
describe("validate pages", () => { describe("validate pages", () => {
it("should return no errors when pages are valid", () => {
it("should return no errors when pages are valid", () => { const errors = validatePages(validPages(), getComponent)
const errors = validatePages(
validPages(), expect(errors).toEqual([])
getComponent); })
expect(errors).toEqual([]); it("should return error when component libraries not set", () => {
}); const pages = validPages()
it("should return error when component libraries not set", () => { delete pages.componentLibraries
const pages = validPages(); let errors = validatePages(pages, getComponent)
expect(errors.length).toBe(1)
delete pages.componentLibraries;
let errors = validatePages(pages, getComponent); pages.componentLibraries = []
expect(errors.length).toBe(1); errors = validatePages(pages, getComponent)
expect(errors.length).toBe(1)
pages.componentLibraries = []; })
errors = validatePages(pages, getComponent);
expect(errors.length).toBe(1); it("should return error when no main or unauthenticated page", () => {
let pages = validPages()
}); delete pages.main
let errors = validatePages(pages, getComponent)
it("should return error when no main or unauthenticated page", () => { expect(errors.length).toBe(1)
let pages = validPages(); pages = validPages()
delete pages.main; delete pages.unauthenticated
let errors = validatePages(pages, getComponent); errors = validatePages(pages, getComponent)
expect(errors.length).toBe(1); expect(errors.length).toBe(1)
})
pages = validPages();
delete pages.unauthenticated; it("should return error when page is invalid", () => {
errors = validatePages(pages, getComponent); const pages = validPages()
expect(errors.length).toBe(1); delete pages.main.index
const errors = validatePages(pages, getComponent)
}); expect(errors.length).toBe(1)
})
it("should return error when page is invalid", () => { })
const pages = validPages();
delete pages.main.index;
const errors = validatePages(pages, getComponent);
expect(errors.length).toBe(1);
});
});

18
packages/cli/src/cli.js

@ -1,21 +1,21 @@
const yargs = require("yargs"); const yargs = require("yargs")
const chalk = require("chalk"); const chalk = require("chalk")
module.exports = () => { module.exports = () => {
yargs yargs
.scriptName("budi") .scriptName("budi")
.usage('$0 <cmd> [args]') .usage("$0 <cmd> [args]")
.command(require("./commands/init")) .command(require("./commands/init"))
.command(require("./commands/new")) .command(require("./commands/new"))
.command(require("./commands/run")) .command(require("./commands/run"))
.command(require("./commands/instance")) .command(require("./commands/instance"))
.fail((msg, err) => { .fail((msg, err) => {
if(err) { if (err) {
console.log(chalk.red(err.message)); console.log(chalk.red(err.message))
console.log(chalk.gray(err.toString())); console.log(chalk.gray(err.toString()))
} else { } else {
console.log(chalk.red(msg)); console.log(chalk.red(msg))
} }
}) })
.help().argv; .help().argv
} }

64
packages/cli/src/commands/init/index.js

@ -1,35 +1,35 @@
const handler = require("./initHandler"); const handler = require("./initHandler")
module.exports = { module.exports = {
command: "init [dir] [config] [username] [password]", command: "init [dir] [config] [username] [password]",
desc: "Initialise Budibase. Run this first to setup your local Budibase", desc: "Initialise Budibase. Run this first to setup your local Budibase",
builder: yargs => { builder: yargs => {
yargs.positional("dir", { yargs.positional("dir", {
type: "string", type: "string",
describe: "your apps directory - directory will be created if it does not exist", describe:
default: ".", "your apps directory - directory will be created if it does not exist",
alias: "d" default: ".",
}); alias: "d",
yargs.positional("config", { })
type: "string", yargs.positional("config", {
describe: "config template file to use - optional, defaults to config.js", type: "string",
alias: "c", describe: "config template file to use - optional, defaults to config.js",
default: "config.dev.js", alias: "c",
choices: ["dev", "contributors"] default: "config.dev.js",
}); choices: ["dev", "contributors"],
yargs.positional("username", { })
type: "string", yargs.positional("username", {
describe: "username for admin interface", type: "string",
alias: "u", describe: "username for admin interface",
default: "" alias: "u",
}) default: "",
yargs.positional("password", { })
type: "string", yargs.positional("password", {
describe: "passord for admin interface", type: "string",
alias: "p", describe: "passord for admin interface",
default: "" alias: "p",
}) default: "",
}, })
handler },
handler,
} }

173
packages/cli/src/commands/init/initHandler.js

@ -1,104 +1,99 @@
const inquirer = require("inquirer"); const inquirer = require("inquirer")
const { mkdir, exists, copy } = require("fs-extra"); const { mkdir, exists, copy } = require("fs-extra")
const chalk = require("chalk"); const chalk = require("chalk")
const { serverFileName, getAppContext } = require("../../common"); const { serverFileName, getAppContext } = require("../../common")
const passwordQuestion = require("@inquirer/password"); const passwordQuestion = require("@inquirer/password")
const createMasterDb = require("@budibase/server/initialise/createMasterDb"); const createMasterDb = require("@budibase/server/initialise/createMasterDb")
const { resolve } = require("path"); const { resolve } = require("path")
const localDatastore = require("@budibase/datastores/datastores/local"); const localDatastore = require("@budibase/datastores/datastores/local")
module.exports = (opts) => { module.exports = opts => {
run(opts); run(opts)
} }
const run = async (opts) => { const run = async opts => {
try { try {
await prompts(opts); await prompts(opts)
await createDevConfig(opts); await createDevConfig(opts)
await createAppsDir(opts); await createAppsDir(opts)
await createDataFolder(opts); await createDataFolder(opts)
await initialiseDatabase(opts); await initialiseDatabase(opts)
console.log(chalk.green("Budibase successfully initialised.")); console.log(chalk.green("Budibase successfully initialised."))
} catch (error) { } catch (error) {
console.error(`Error initialising Budibase: ${error.message}`) console.error(`Error initialising Budibase: ${error.message}`)
} }
} }
const prompts = async (opts) => { const prompts = async opts => {
const questions = [
const questions = [ {
{ type: "input",
type: "input", name: "username",
name: "username", message: "Username for Admin: ",
message: "Username for Admin: ", validate: function(value) {
validate: function(value) { return !!value || "Please enter a username"
return !!value || "Please enter a username" },
} },
} ]
]
if (!opts.username) {
if(!opts.username) { const answers = await inquirer.prompt(questions)
const answers = await inquirer.prompt(questions); opts.username = answers.username
opts.username = answers.username; }
}
if (!opts.password) {
if(!opts.password) { const password = await passwordQuestion({
message: "Password for Admin: ",
const password = await passwordQuestion({ mask: "*",
message: "Password for Admin: ", mask: "*" })
}); const passwordConfirm = await passwordQuestion({
const passwordConfirm = await passwordQuestion({ message: "Confirm Password: ",
message: "Confirm Password: ", mask: "*" mask: "*",
}); })
if(password !== passwordConfirm) if (password !== passwordConfirm)
throw new Exception("Passwords do not match!"); throw new Exception("Passwords do not match!")
opts.password = password
opts.password = password; }
}
} }
const createAppsDir = async (opts) => { const createAppsDir = async opts => {
if(!await exists(opts.configJson.latestPackagesFolder)) { if (!(await exists(opts.configJson.latestPackagesFolder))) {
await mkdir(opts.configJson.latestPackagesFolder); await mkdir(opts.configJson.latestPackagesFolder)
} }
} }
const createDataFolder = async (opts) => { const createDataFolder = async opts => {
const dataPath = opts.configJson.datastoreConfig.rootPath
const dataPath = opts.configJson.datastoreConfig.rootPath;
if(await exists(dataPath)) { if (await exists(dataPath)) {
const err = `The path ${opts.datapath} already exists - has budibase already been initialised? Remove the directory to try again.`; const err = `The path ${opts.datapath} already exists - has budibase already been initialised? Remove the directory to try again.`
throw new Error(err); throw new Error(err)
} }
await mkdir(dataPath); await mkdir(dataPath)
} }
const createDevConfig = async (opts) => { const createDevConfig = async opts => {
const configTemplateFile = `config.${opts.config}.js`
const configTemplateFile = `config.${opts.config}.js`; const destConfigFile = "./config.js"
const destConfigFile = "./config.js";
if (await exists(destConfigFile)) {
if(await exists(destConfigFile)) { console.log(
console.log(chalk.yellow("Config file already exists (config.js) - keeping your existing config")); chalk.yellow(
} else { "Config file already exists (config.js) - keeping your existing config"
const srcConfig = serverFileName(configTemplateFile); )
await copy(srcConfig, destConfigFile); )
} } else {
opts.configJson = require(resolve("./config.js"))(); const srcConfig = serverFileName(configTemplateFile)
await copy(srcConfig, destConfigFile)
}
opts.configJson = require(resolve("./config.js"))()
} }
const initialiseDatabase = async (opts) => { const initialiseDatabase = async opts => {
const appContext = await getAppContext({ masterIsCreated: false })
const appContext = await getAppContext({masterIsCreated:false});
await createMasterDb( await createMasterDb(appContext, localDatastore, opts.username, opts.password)
appContext, }
localDatastore,
opts.username,
opts.password);
}

36
packages/cli/src/commands/instance/index.js

@ -1,22 +1,22 @@
const handler = require("./instanceHandler"); const handler = require("./instanceHandler")
module.exports = { module.exports = {
command: "instance <appname> [config]", command: "instance <appname> [config]",
desc: "Create a new instance for an app", desc: "Create a new instance for an app",
builder: yargs => { builder: yargs => {
yargs.positional("appname", { yargs.positional("appname", {
type: "string", type: "string",
describe: "the name of the app to create an instance", describe: "the name of the app to create an instance",
alias: "a" alias: "a",
}); })
yargs.positional("config", { yargs.positional("config", {
type: "string", type: "string",
describe: "config file to use. optional, defaults to config.js. Use 'dev' as shorthand for 'config.dev.js' ", describe:
alias: "c", "config file to use. optional, defaults to config.js. Use 'dev' as shorthand for 'config.dev.js' ",
default: "config.js" alias: "c",
}) default: "config.js",
}, })
handler },
handler,
} }

200
packages/cli/src/commands/instance/instanceHandler.js

@ -1,104 +1,116 @@
const inquirer = require("inquirer"); const inquirer = require("inquirer")
const { readJSON } = require("fs-extra"); const { readJSON } = require("fs-extra")
const { join } = require("path"); const { join } = require("path")
const chalk = require("chalk"); const chalk = require("chalk")
const fp = require("lodash/fp"); const fp = require("lodash/fp")
const { getAppContext } = require("../../common"); const { getAppContext } = require("../../common")
const passwordQuestion = require("@inquirer/password"); const passwordQuestion = require("@inquirer/password")
module.exports = (opts) => { module.exports = opts => {
run(opts); run(opts)
} }
const run = async (opts) => { const run = async opts => {
try { try {
const appContext = await getAppContext({configName: opts.config, masterIsCreated:true}); const appContext = await getAppContext({
opts.appContext = appContext; configName: opts.config,
opts.datapath = "./.data"; masterIsCreated: true,
await fetchUserLevels(opts); })
await prompts(opts); opts.appContext = appContext
await createInstance(opts); opts.datapath = "./.data"
console.log(chalk.green(`Budibase instance created for app ${opts.appname}.`)) await fetchUserLevels(opts)
} catch (error) { await prompts(opts)
console.error(chalk.red(`Error creating instance of app ${opts.appname}: ${error.message}`)) await createInstance(opts)
} console.log(
chalk.green(`Budibase instance created for app ${opts.appname}.`)
)
} catch (error) {
console.error(
chalk.red(
`Error creating instance of app ${opts.appname}: ${error.message}`
)
)
}
} }
const fetchUserLevels = async (opts) => { const fetchUserLevels = async opts => {
const accessLevels = await readJSON( const accessLevels = await readJSON(
join( join(
opts.appContext.config.latestPackagesFolder || ".", opts.appContext.config.latestPackagesFolder || ".",
opts.appname, opts.appname,
"access_levels.json") "access_levels.json"
); )
)
if(accessLevels.levels.length === 0) if (accessLevels.levels.length === 0)
throw new Error("No access levels. Use the builder to create one"); throw new Error("No access levels. Use the builder to create one")
opts.accessLevels = accessLevels.levels; opts.accessLevels = accessLevels.levels
} }
const prompts = async (opts) => { const prompts = async opts => {
const questions = [
const questions = [ {
{ type: "input",
type: "input", name: "username",
name: "username", message: "Username: ",
message: "Username: ", validate: function(value) {
validate: function(value) { return !!value || "Please enter a username"
return !!value || "Please enter a username" },
} },
} ]
]
if (opts.accessLevels.length === 1) {
if(opts.accessLevels.length === 1) { opts.userAccessLevel = opts.accessLevels[0].name
opts.userAccessLevel = opts.accessLevels[0].name; } else {
} else { questions.push({
questions.push({ type: "input",
type: "input", name: "userAccessLevel",
name: "userAccessLevel", message: `Access Level [${fp.join(", ")(
message: `Access Level [${fp.join(", ")(opts.accessLevels.map(l => l.name))}]: `, opts.accessLevels.map(l => l.name)
choices: opts.accessLevels.map(l => l.name) )}]: `,
}); choices: opts.accessLevels.map(l => l.name),
} })
}
const answers = await inquirer.prompt(questions);
const password = await passwordQuestion({ const answers = await inquirer.prompt(questions)
message: "Password for Admin: ", mask: "*" const password = await passwordQuestion({
}); message: "Password for Admin: ",
const passwordConfirm = await passwordQuestion({ mask: "*",
message: "Confirm Password: ", mask: "*" })
}); const passwordConfirm = await passwordQuestion({
message: "Confirm Password: ",
if(password !== passwordConfirm) mask: "*",
throw new Error("Passwords do not match!"); })
opts.username = answers.username; if (password !== passwordConfirm) throw new Error("Passwords do not match!")
opts.password = password;
if(opts.accessLevels.length > 1) { opts.username = answers.username
opts.userAccessLevel = answers.userAccessLevel; opts.password = password
} if (opts.accessLevels.length > 1) {
opts.userAccessLevel = answers.userAccessLevel
}
} }
const createInstance = async opts => {
const createInstance = async (opts) => { const bb = opts.appContext.master.bbMaster
const bb = opts.appContext.master.bbMaster; const app = await opts.appContext.master.getApplication(opts.appname)
const instance = bb.recordApi.getNew(`${app.key}/instances`, "instance")
const app = await opts.appContext.master.getApplication(opts.appname); instance.name = "dev instance"
const instance = bb.recordApi.getNew(`${app.key}/instances`, "instance"); instance.active = true
instance.name = "dev instance"; instance.version = { key: "" }
instance.active = true;
instance.version = {key:""}; const user = bb.authApi.getNewUser()
user.accessLevels.push(opts.userAccessLevel)
const user = bb.authApi.getNewUser(); user.name = opts.username
user.accessLevels.push(opts.userAccessLevel);
user.name = opts.username; await bb.recordApi.save(instance)
const savedInstance = await bb.recordApi.load(instance.key)
await bb.recordApi.save(instance); await opts.appContext.master.createAppUser(
const savedInstance = await bb.recordApi.load(instance.key); opts.appname,
await opts.appContext.master.createAppUser( savedInstance,
opts.appname, savedInstance, user, opts.password); user,
opts.password
} )
}

3
packages/cli/src/commands/new/appPackageTemplate/plugins.js

@ -1,2 +1 @@
module.exports = config => ({})
module.exports = (config) => ({})

35
packages/cli/src/commands/new/index.js

@ -1,21 +1,20 @@
const handler = require("./newHandler"); const handler = require("./newHandler")
module.exports = { module.exports = {
command: "new <name> [config]", command: "new <name> [config]",
desc: "Create a new Budibase app", desc: "Create a new Budibase app",
builder: yargs => { builder: yargs => {
yargs.positional("name", { yargs.positional("name", {
type: "string", type: "string",
describe: "the name of your app", describe: "the name of your app",
alias: "n" alias: "n",
}); })
yargs.positional("config", { yargs.positional("config", {
type: "string", type: "string",
describe: "config file to use - optional, defaults to config.js", describe: "config file to use - optional, defaults to config.js",
alias: "c", alias: "c",
default: "config.js" default: "config.js",
}) })
}, },
handler handler,
} }

90
packages/cli/src/commands/new/newHandler.js

@ -1,59 +1,61 @@
const { getAppContext } = require("../../common"); const { getAppContext } = require("../../common")
const { const {
getMasterApisWithFullAccess getMasterApisWithFullAccess,
} = require("@budibase/server/utilities/budibaseApi"); } = require("@budibase/server/utilities/budibaseApi")
const { copy, readJSON, writeJSON, remove, exists } = require("fs-extra"); const { copy, readJSON, writeJSON, remove, exists } = require("fs-extra")
const { resolve, join } = require("path"); const { resolve, join } = require("path")
const thisPackageJson = require("../../../package.json"); const thisPackageJson = require("../../../package.json")
const chalk = require("chalk"); const chalk = require("chalk")
const {exec} = require('child_process'); const { exec } = require("child_process")
module.exports = (opts) => { module.exports = opts => {
run(opts); run(opts)
console.log(chalk.green(`Budibase app ${opts.name} created!`)); console.log(chalk.green(`Budibase app ${opts.name} created!`))
} }
const run = async (opts) => { const run = async opts => {
const context = await getAppContext({configName:opts.config, masterIsCreated:true}); const context = await getAppContext({
opts.config = context.config; configName: opts.config,
const bb = await getMasterApisWithFullAccess(context); masterIsCreated: true,
})
opts.config = context.config
const bb = await getMasterApisWithFullAccess(context)
const app = bb.recordApi.getNew("/applications", "application"); const app = bb.recordApi.getNew("/applications", "application")
app.name = opts.name; app.name = opts.name
await bb.recordApi.save(app); await bb.recordApi.save(app)
await createEmptyAppPackage(opts); await createEmptyAppPackage(opts)
exec(`cd ${join(opts.config.latestPackagesFolder, opts.name)} && npm install`); exec(`cd ${join(opts.config.latestPackagesFolder, opts.name)} && npm install`)
} }
const createEmptyAppPackage = async (opts) => { const createEmptyAppPackage = async opts => {
const templateFolder = resolve( const templateFolder = resolve(__dirname, "appPackageTemplate")
__dirname, "appPackageTemplate");
const appsFolder = opts.config.latestPackagesFolder || "."; const appsFolder = opts.config.latestPackagesFolder || "."
const destinationFolder = resolve(appsFolder, opts.name); const destinationFolder = resolve(appsFolder, opts.name)
if(await exists(destinationFolder)) return; if (await exists(destinationFolder)) return
await copy(templateFolder, destinationFolder); await copy(templateFolder, destinationFolder)
const packageJsonPath = join(appsFolder, opts.name, "package.json"); const packageJsonPath = join(appsFolder, opts.name, "package.json")
const packageJson = await readJSON(packageJsonPath); const packageJson = await readJSON(packageJsonPath)
packageJson.name = opts.name; packageJson.name = opts.name
packageJson.dependencies["@budibase/standard-components"] = `^${thisPackageJson.version}`; packageJson.dependencies[
"@budibase/standard-components"
] = `^${thisPackageJson.version}`
await writeJSON(packageJsonPath, packageJson); await writeJSON(packageJsonPath, packageJson)
const removePlaceholder = async (...args) => { const removePlaceholder = async (...args) => {
await remove(join(destinationFolder, ...args, "placeholder")); await remove(join(destinationFolder, ...args, "placeholder"))
} }
await removePlaceholder("components"); await removePlaceholder("components")
await removePlaceholder("public", "shared"); await removePlaceholder("public", "shared")
await removePlaceholder("public", "main"); await removePlaceholder("public", "main")
await removePlaceholder("public", "unauthenticated"); await removePlaceholder("public", "unauthenticated")
}
}

29
packages/cli/src/commands/run/index.js

@ -1,17 +1,18 @@
const handler = require("./runHandler"); const handler = require("./runHandler")
module.exports = { module.exports = {
command: "run [config]", command: "run [config]",
aliases: ["$0"], aliases: ["$0"],
desc: "Start budibase Server. You can access your apps and the builder from here if you have dev=true in your config", desc:
builder: yargs => { "Start budibase Server. You can access your apps and the builder from here if you have dev=true in your config",
yargs.positional("config", { builder: yargs => {
type: "string", yargs.positional("config", {
describe: "config file to use. optional, defaults to config.js. Use 'dev' as shorthand for 'config.dev.js' ", type: "string",
alias: "c", describe:
default: "config.js" "config file to use. optional, defaults to config.js. Use 'dev' as shorthand for 'config.dev.js' ",
}) alias: "c",
}, default: "config.js",
handler })
},
handler,
} }

16
packages/cli/src/commands/run/runHandler.js

@ -1,11 +1,9 @@
const { getAppContext } = require("../../common"); const { getAppContext } = require("../../common")
const app = require("@budibase/server/app"); const app = require("@budibase/server/app")
module.exports = ({config}) => { module.exports = ({ config }) => {
getAppContext({configName:config, masterIsCreated:true}) getAppContext({ configName: config, masterIsCreated: true }).then(context => {
.then(context => { app(context)
app(context); console.log(`Budibase Builder running on port ${context.config.port}..`)
console.log(`Budibase Builder running on port ${context.config.port}..`); })
});
} }

36
packages/cli/src/common.js

@ -1,25 +1,19 @@
const { resolve, join } = require("path"); const { resolve, join } = require("path")
const { cwd } = require("process"); const { cwd } = require("process")
const buildAppContext = require("@budibase/server/initialise/buildAppContext"); const buildAppContext = require("@budibase/server/initialise/buildAppContext")
module.exports.serverFileName = relativePath => module.exports.serverFileName = relativePath =>
resolve(__dirname, resolve(__dirname, "..", "node_modules", "@budibase", "server", relativePath)
"..",
"node_modules",
"@budibase",
"server",
relativePath);
module.exports.getAppContext = async ({configName, masterIsCreated}) => { module.exports.getAppContext = async ({ configName, masterIsCreated }) => {
if (configName) {
if(configName) { if (!configName.endsWith(".js")) {
if(!configName.endsWith(".js")) { configName = `config.${configName}.js`
configName = `config.${configName}.js`;
}
} else {
configName = "config.js";
} }
} else {
configName = "config.js"
}
const config = require(resolve(cwd(), configName))(); const config = require(resolve(cwd(), configName))()
return await buildAppContext(config, masterIsCreated); return await buildAppContext(config, masterIsCreated)
} }

25
packages/client/babel.config.js

@ -1,12 +1,13 @@
module.exports = ({ module.exports = {
"presets": ["@babel/preset-env"], presets: ["@babel/preset-env"],
"sourceMaps": "inline", sourceMaps: "inline",
"retainLines": true, retainLines: true,
"plugins": [ plugins: [
["@babel/plugin-transform-runtime", [
{ "@babel/plugin-transform-runtime",
"regenerator": true {
} regenerator: true,
] },
] ],
}); ],
}

165
packages/client/rollup.config.js

@ -1,70 +1,111 @@
import resolve from 'rollup-plugin-node-resolve'; import resolve from "rollup-plugin-node-resolve"
import commonjs from 'rollup-plugin-commonjs'; import commonjs from "rollup-plugin-commonjs"
import builtins from 'rollup-plugin-node-builtins'; import builtins from "rollup-plugin-node-builtins"
import nodeglobals from 'rollup-plugin-node-globals'; import nodeglobals from "rollup-plugin-node-globals"
import { terser } from 'rollup-plugin-terser'; import { terser } from "rollup-plugin-terser"
const lodash_fp_exports = [ const lodash_fp_exports = [
"find", "isUndefined", "split", "max", "find",
"last", "union", "reduce", "isObject", "isUndefined",
"cloneDeep", "some", "isArray", "map", "split",
"filter", "keys", "isFunction", "isEmpty", "max",
"countBy", "join", "includes", "flatten", "last",
"constant", "first", "intersection", "take", "union",
"has", "mapValues", "isString", "isBoolean", "reduce",
"isNull", "isNumber", "isObjectLike", "isDate", "isObject",
"clone", "values", "keyBy", "isNaN", "cloneDeep",
"isInteger", "toNumber"]; "some",
"isArray",
"map",
"filter",
"keys",
"isFunction",
"isEmpty",
"countBy",
"join",
"includes",
"flatten",
"constant",
"first",
"intersection",
"take",
"has",
"mapValues",
"isString",
"isBoolean",
"isNull",
"isNumber",
"isObjectLike",
"isDate",
"clone",
"values",
"keyBy",
"isNaN",
"isInteger",
"toNumber",
]
const lodash_exports = [ const lodash_exports = [
"flow", "head", "find", "each", "flow",
"tail", "findIndex", "startsWith", "head",
"dropRight", "takeRight", "find",
"trim", "split", "replace", "each",
"merge", "assign"]; "tail",
"findIndex",
"startsWith",
"dropRight",
"takeRight",
"trim",
"split",
"replace",
"merge",
"assign",
]
const coreExternal = [ const coreExternal = [
"lodash", "lodash/fp", "lodash",
"lunr", "safe-buffer", "shortid", "lodash/fp",
"@nx-js/compiler-util" "lunr",
]; "safe-buffer",
"shortid",
"@nx-js/compiler-util",
]
export default { export default {
input: 'src/index.js', input: "src/index.js",
output: [ output: [
{ {
sourcemap: true, sourcemap: true,
format: 'iife', format: "iife",
name: 'app', name: "app",
file: `./dist/budibase-client.js` file: `./dist/budibase-client.js`,
}, },
{ {
file: 'dist/budibase-client.esm.mjs', file: "dist/budibase-client.esm.mjs",
format: 'esm', format: "esm",
sourcemap: "inline" sourcemap: "inline",
} },
], ],
plugins: [ plugins: [
resolve({
resolve({ preferBuiltins: true,
preferBuiltins:true, browser: true,
browser:true, dedupe: importee => {
dedupe: importee => { return coreExternal.includes(importee)
return coreExternal.includes(importee); },
} }),
}), commonjs({
commonjs({ namedExports: {
namedExports: { "lodash/fp": lodash_fp_exports,
"lodash/fp": lodash_fp_exports, lodash: lodash_exports,
"lodash":lodash_exports, shortid: ["generate"],
"shortid": ["generate"] },
} }),
}), builtins(),
builtins(), nodeglobals(),
nodeglobals(), //terser()
//terser() ],
], watch: {
watch: { clearScreen: false,
clearScreen: false },
} }
};

91
packages/client/scripts/publishDev.js

@ -1,53 +1,52 @@
const { readdir, stat, copyFile } = require("fs-extra"); const { readdir, stat, copyFile } = require("fs-extra")
const { constants } = require("fs"); const { constants } = require("fs")
const { join, basename } = require("path"); const { join, basename } = require("path")
const packagesFolder = ".."; const packagesFolder = ".."
const jsFile = dir => join(dir, "budibase-client.js"); const jsFile = dir => join(dir, "budibase-client.js")
const jsMapFile = dir => join(dir, "budibase-client.js.map"); const jsMapFile = dir => join(dir, "budibase-client.js.map")
const sourceJs = jsFile("dist"); const sourceJs = jsFile("dist")
const sourceJsMap = jsMapFile("dist"); const sourceJsMap = jsMapFile("dist")
const appPackages = join(packagesFolder, "server", "appPackages"); const appPackages = join(packagesFolder, "server", "appPackages")
const publicMain = appName => join(appPackages, appName, "public", "main"); const publicMain = appName => join(appPackages, appName, "public", "main")
const publicUnauth = appName => join(appPackages, appName, "public", "unauthenticated"); const publicUnauth = appName =>
const nodeModules = appName => join(appPackages, appName, "node_modules", "@budibase", "client", "dist"); join(appPackages, appName, "public", "unauthenticated")
const nodeModules = appName =>
(async () => { join(appPackages, appName, "node_modules", "@budibase", "client", "dist")
const apps = await readdir(appPackages); ;(async () => {
const apps = await readdir(appPackages)
const copySource = file => async toDir => {
const dest = join(toDir, basename(file)); const copySource = file => async toDir => {
try { const dest = join(toDir, basename(file))
await copyFile(file, dest, constants.COPYFILE_FICLONE); try {
console.log(`COPIED ${file} to ${dest}`); await copyFile(file, dest, constants.COPYFILE_FICLONE)
} catch(e) { console.log(`COPIED ${file} to ${dest}`)
console.log(`COPY FAILED ${file} to ${dest}: ${e}`); } catch (e) {
} console.log(`COPY FAILED ${file} to ${dest}: ${e}`)
} }
}
const copySourceJs = copySource(sourceJs); const copySourceJs = copySource(sourceJs)
const copySourceJsMap = copySource(sourceJsMap); const copySourceJsMap = copySource(sourceJsMap)
for(let app of apps) { for (let app of apps) {
if(!(await stat(join(appPackages, app))).isDirectory()) continue; if (!(await stat(join(appPackages, app))).isDirectory()) continue
await copySourceJs(nodeModules(app)); await copySourceJs(nodeModules(app))
await copySourceJsMap(nodeModules(app)); await copySourceJsMap(nodeModules(app))
await copySourceJs(publicMain(app)); await copySourceJs(publicMain(app))
await copySourceJsMap(publicMain(app)); await copySourceJsMap(publicMain(app))
await copySourceJs(publicUnauth(app)); await copySourceJs(publicUnauth(app))
await copySourceJsMap(publicUnauth(app)); await copySourceJsMap(publicUnauth(app))
await copySource(
join("dist", "budibase-client.esm.mjs"))(
join(packagesFolder, "server", "builder" ));
}
})(); await copySource(join("dist", "budibase-client.esm.mjs"))(
join(packagesFolder, "server", "builder")
)
}
})()

38
packages/client/src/api/authenticate.js

@ -1,24 +1,22 @@
export const USER_STATE_PATH = "_bbuser"
export const USER_STATE_PATH = "_bbuser"; export const authenticate = api => async ({ username, password }) => {
if (!username) {
api.error("Authenticate: username not set")
return
}
export const authenticate = (api) => async ({username, password}) => { if (!password) {
api.error("Authenticate: password not set")
return
}
if(!username) { const user = await api.post({
api.error("Authenticate: username not set"); url: "/api/authenticate",
return; body: { username, password },
} })
if(!password) {
api.error("Authenticate: password not set");
return;
}
const user = await api.post({ // set user even if error - so it is defined at least
url:"/api/authenticate", api.setState(USER_STATE_PATH, user)
body : {username, password} localStorage.setItem("budibase:user", JSON.stringify(user))
}); }
// set user even if error - so it is defined at least
api.setState(USER_STATE_PATH, user);
localStorage.setItem("budibase:user", JSON.stringify(user));
}

127
packages/client/src/api/index.js

@ -1,65 +1,76 @@
import { ERROR } from "../state/standardState"; import { ERROR } from "../state/standardState"
import {loadRecord} from "./loadRecord"; import { loadRecord } from "./loadRecord"
import {listRecords} from "./listRecords"; import { listRecords } from "./listRecords"
import {authenticate} from "./authenticate"; import { authenticate } from "./authenticate"
import {saveRecord} from "./saveRecord"; import { saveRecord } from "./saveRecord"
export const createApi = ({rootPath, setState, getState}) => { export const createApi = ({ rootPath, setState, getState }) => {
const apiCall = method => ({
url,
body,
notFound,
badRequest,
forbidden,
}) => {
return fetch(`${rootPath}${url}`, {
method: method,
headers: {
"Content-Type": "application/json",
},
body: body && JSON.stringify(body),
credentials: "same-origin",
}).then(r => {
switch (r.status) {
case 200:
return r.json()
case 404:
return error(notFound || `${url} Not found`)
case 400:
return error(badRequest || `${url} Bad Request`)
case 403:
return error(forbidden || `${url} Forbidden`)
default:
if (
r.status.toString().startsWith("2") ||
r.status.toString().startsWith("3")
)
return r.json()
else return error(`${url} - ${r.statusText}`)
}
})
}
const apiCall = (method) => ({url, body, notFound, badRequest, forbidden}) => { const post = apiCall("POST")
return fetch(`${rootPath}${url}`, { const get = apiCall("GET")
method: method, const patch = apiCall("PATCH")
headers: { const del = apiCall("DELETE")
'Content-Type': 'application/json',
},
body: body && JSON.stringify(body),
credentials: "same-origin"
}).then(r => {
switch (r.status) {
case 200:
return r.json();
case 404:
return error(notFound || `${url} Not found`);
case 400:
return error(badRequest || `${url} Bad Request`);
case 403:
return error(forbidden || `${url} Forbidden`);
default:
if(r.status.toString().startsWith("2")
|| r.status.toString().startsWith("3"))
return r.json()
else
return error(`${url} - ${r.statusText}`);
}
});
}
const post = apiCall("POST"); const ERROR_MEMBER = "##error"
const get = apiCall("GET"); const error = message => {
const patch = apiCall("PATCH"); const e = {}
const del = apiCall("DELETE"); e[ERROR_MEMBER] = message
setState(ERROR, message)
return e
}
const ERROR_MEMBER = "##error"; const isSuccess = obj => !obj || !obj[ERROR_MEMBER]
const error = message => {
const e = {};
e[ERROR_MEMBER] = message;
setState(ERROR, message);
return e;
}
const isSuccess = obj => !obj || !obj[ERROR_MEMBER]; const apiOpts = {
rootPath,
setState,
getState,
isSuccess,
error,
post,
get,
patch,
delete: del,
}
const apiOpts = { return {
rootPath, setState, getState, isSuccess, error, loadRecord: loadRecord(apiOpts),
post, get, patch, delete:del listRecords: listRecords(apiOpts),
}; authenticate: authenticate(apiOpts),
saveRecord: saveRecord(apiOpts),
return { }
loadRecord:loadRecord(apiOpts),
listRecords: listRecords(apiOpts),
authenticate: authenticate(apiOpts),
saveRecord: saveRecord(apiOpts)
}
} }

33
packages/client/src/api/listRecords.js

@ -1,20 +1,19 @@
import {trimSlash} from "../common/trimSlash"; import { trimSlash } from "../common/trimSlash"
export const listRecords = api => async ({indexKey, statePath}) => { export const listRecords = api => async ({ indexKey, statePath }) => {
if(!indexKey) { if (!indexKey) {
api.error("Load Record: record key not set"); api.error("Load Record: record key not set")
return; return
} }
if(!statePath) {
api.error("Load Record: state path not set");
return;
}
const records = await api.get({ if (!statePath) {
url:`/api/listRecords/${trimSlash(indexKey)}` api.error("Load Record: state path not set")
}); return
}
if(api.isSuccess(records)) const records = await api.get({
api.setState(statePath, records); url: `/api/listRecords/${trimSlash(indexKey)}`,
} })
if (api.isSuccess(records)) api.setState(statePath, records)
}

32
packages/client/src/api/loadRecord.js

@ -1,21 +1,19 @@
import {trimSlash} from "../common/trimSlash"; import { trimSlash } from "../common/trimSlash"
export const loadRecord = (api) => async ({recordKey, statePath}) => { export const loadRecord = api => async ({ recordKey, statePath }) => {
if (!recordKey) {
api.error("Load Record: record key not set")
return
}
if(!recordKey) { if (!statePath) {
api.error("Load Record: record key not set"); api.error("Load Record: state path not set")
return; return
} }
if(!statePath) {
api.error("Load Record: state path not set");
return;
}
const record = await api.get({ const record = await api.get({
url:`/api/record/${trimSlash(recordKey)}` url: `/api/record/${trimSlash(recordKey)}`,
}); })
if(api.isSuccess(record)) if (api.isSuccess(record)) api.setState(statePath, record)
api.setState(statePath, record); }
}

46
packages/client/src/api/saveRecord.js

@ -1,29 +1,29 @@
import {trimSlash} from "../common/trimSlash"; import { trimSlash } from "../common/trimSlash"
export const saveRecord = (api) => async ({statePath}) => { export const saveRecord = api => async ({ statePath }) => {
if (!statePath) {
if(!statePath) { api.error("Load Record: state path not set")
api.error("Load Record: state path not set"); return
return; }
}
const recordtoSave = api.getState(statePath); const recordtoSave = api.getState(statePath)
if(!recordtoSave) { if (!recordtoSave) {
api.error(`there is no record in state: ${statePath}`); api.error(`there is no record in state: ${statePath}`)
return; return
} }
if(!recordtoSave.key) { if (!recordtoSave.key) {
api.error(`item in state does not appear to be a record - it has no key (${statePath})`); api.error(
return; `item in state does not appear to be a record - it has no key (${statePath})`
} )
return
}
const savedRecord = await api.post({ const savedRecord = await api.post({
url:`/api/record/${trimSlash(recordtoSave.key)}`, url: `/api/record/${trimSlash(recordtoSave.key)}`,
body: recordtoSave body: recordtoSave,
}); })
if(api.isSuccess(savedRecord)) if (api.isSuccess(savedRecord)) api.setState(statePath, savedRecord)
api.setState(statePath, savedRecord); }
}

2
packages/client/src/common/trimSlash.js

@ -1 +1 @@
export const trimSlash = (str) => str.replace(/^\/+|\/+$/g, ''); export const trimSlash = str => str.replace(/^\/+|\/+$/g, "")

6
packages/client/src/core/common.js

@ -1,5 +1 @@
export { $ } from "../../../core/src/common"
export {
$
} from "../../../core/src/common";

22
packages/client/src/core/createCoreApp.js

@ -1,14 +1,12 @@
export const createCoreApp = (appDefinition, user) => { export const createCoreApp = (appDefinition, user) => {
const app = { const app = {
datastore: null, datastore: null,
crypto:null, crypto: null,
publish: () => {}, publish: () => {},
hierarchy: appDefinition.hierarchy, hierarchy: appDefinition.hierarchy,
actions: appDefinition.actions, actions: appDefinition.actions,
user user,
}; }
return app; return app
} }

32
packages/client/src/core/index.js

@ -1,24 +1,18 @@
import { createCoreApp } from "./createCoreApp" import { createCoreApp } from "./createCoreApp"
import { import { getNew, getNewChild } from "../../../core/src/recordApi/getNew"
getNew, getNewChild import { constructHierarchy } from "../../../core/src/templateApi/createNodes"
} from "../../../core/src/recordApi/getNew";
import {
constructHierarchy
} from "../../../core/src/templateApi/createNodes";
export const createCoreApi = (appDefinition, user) => { export const createCoreApi = (appDefinition, user) => {
const app = createCoreApp(appDefinition, user)
const app = createCoreApp(appDefinition, user);
return { return {
recordApi: { recordApi: {
getNew: getNew(app), getNew: getNew(app),
getNewChild: getNewChild(app) getNewChild: getNewChild(app),
}, },
templateApi: { templateApi: {
constructHierarchy constructHierarchy,
} },
} }
}
}

159
packages/client/src/createApp.js

@ -1,82 +1,99 @@
import {writable} from "svelte/store"; import { writable } from "svelte/store"
import { createCoreApi } from "./core"; import { createCoreApi } from "./core"
import { getStateOrValue } from "./state/getState"; import { getStateOrValue } from "./state/getState"
import { setState, setStateFromBinding } from "./state/setState"; import { setState, setStateFromBinding } from "./state/setState"
import { trimSlash } from "./common/trimSlash"; import { trimSlash } from "./common/trimSlash"
import { isBound } from "./state/isState"; import { isBound } from "./state/isState"
import { _initialiseChildren } from "./render/initialiseChildren"; import { _initialiseChildren } from "./render/initialiseChildren"
import { createTreeNode } from "./render/renderComponent"; import { createTreeNode } from "./render/renderComponent"
export const createApp = (document, componentLibraries, appDefinition, user, uiFunctions) => { export const createApp = (
document,
const coreApi = createCoreApi(appDefinition, user); componentLibraries,
appDefinition.hierarchy = coreApi.templateApi.constructHierarchy(appDefinition.hierarchy); appDefinition,
const store = writable({ user,
_bbuser: user uiFunctions
}); ) => {
const coreApi = createCoreApi(appDefinition, user)
appDefinition.hierarchy = coreApi.templateApi.constructHierarchy(
appDefinition.hierarchy
)
const store = writable({
_bbuser: user,
})
let globalState = null
store.subscribe(s => {
globalState = s
})
let globalState = null; const relativeUrl = url =>
store.subscribe(s => { appDefinition.appRootPath
globalState = s; ? appDefinition.appRootPath + "/" + trimSlash(url)
}); : url
const relativeUrl = (url) => const apiCall = method => (url, body) =>
appDefinition.appRootPath fetch(relativeUrl(url), {
? appDefinition.appRootPath + "/" + trimSlash(url) method: method,
: url; headers: {
"Content-Type": "application/json",
},
body: body && JSON.stringify(body),
})
const api = {
post: apiCall("POST"),
get: apiCall("GET"),
patch: apiCall("PATCH"),
delete: apiCall("DELETE"),
}
const apiCall = (method) => (url, body) => const safeCallEvent = (event, context) => {
fetch(relativeUrl(url), { const isFunction = obj =>
method: method, !!(obj && obj.constructor && obj.call && obj.apply)
headers: {
'Content-Type': 'application/json',
},
body: body && JSON.stringify(body),
});
const api = { if (isFunction(event)) event(context)
post: apiCall("POST"), }
get: apiCall("GET"),
patch: apiCall("PATCH"),
delete: apiCall("DELETE")
};
const safeCallEvent = (event, context) => { const initialiseChildrenParams = (hydrate, treeNode) => ({
bb,
const isFunction = (obj) => coreApi,
!!(obj && obj.constructor && obj.call && obj.apply); store,
document,
componentLibraries,
appDefinition,
hydrate,
uiFunctions,
treeNode,
})
if(isFunction(event)) event(context); const bb = (treeNode, componentProps) => ({
} hydrateChildren: _initialiseChildren(
initialiseChildrenParams(true, treeNode)
const initialiseChildrenParams = (hydrate, treeNode) => ({ ),
bb, coreApi, store, document, appendChildren: _initialiseChildren(
componentLibraries, appDefinition, initialiseChildrenParams(false, treeNode)
hydrate, uiFunctions, treeNode ),
}); insertChildren: (props, htmlElement, anchor) =>
_initialiseChildren(initialiseChildrenParams(false, treeNode))(
const bb = (treeNode, componentProps) => ({ props,
hydrateChildren: _initialiseChildren(initialiseChildrenParams(true, treeNode)), htmlElement,
appendChildren: _initialiseChildren(initialiseChildrenParams(false, treeNode)), anchor
insertChildren: (props, htmlElement, anchor) => ),
_initialiseChildren(initialiseChildrenParams(false, treeNode)) context: treeNode.context,
(props, htmlElement, anchor), props: componentProps,
context: treeNode.context, call: safeCallEvent,
props: componentProps, setStateFromBinding: (binding, value) =>
call:safeCallEvent, setStateFromBinding(store, binding, value),
setStateFromBinding: (binding, value) => setStateFromBinding(store, binding, value), setState: (path, value) => setState(store, path, value),
setState: (path, value) => setState(store, path, value), getStateOrValue: (prop, currentContext) =>
getStateOrValue: (prop, currentContext) => getStateOrValue(globalState, prop, currentContext),
getStateOrValue(globalState, prop, currentContext), store,
store, relativeUrl,
relativeUrl, api,
api, isBound,
isBound, parent,
parent })
});
return bb(createTreeNode());
return bb(createTreeNode())
} }

106
packages/client/src/index.js

@ -1,55 +1,59 @@
import { createApp } from "./createApp"; import { createApp } from "./createApp"
import { trimSlash } from "./common/trimSlash"; import { trimSlash } from "./common/trimSlash"
export const loadBudibase = async ({ export const loadBudibase = async ({
componentLibraries, props, componentLibraries,
window, localStorage, uiFunctions }) => { props,
window,
const appDefinition = window["##BUDIBASE_APPDEFINITION##"]; localStorage,
const uiFunctionsFromWindow = window["##BUDIBASE_APPDEFINITION##"]; uiFunctions,
uiFunctions = uiFunctionsFromWindow || uiFunctions; }) => {
const appDefinition = window["##BUDIBASE_APPDEFINITION##"]
const userFromStorage = localStorage.getItem("budibase:user") const uiFunctionsFromWindow = window["##BUDIBASE_APPDEFINITION##"]
uiFunctions = uiFunctionsFromWindow || uiFunctions
const user = userFromStorage ? JSON.parse(userFromStorage) : {
const userFromStorage = localStorage.getItem("budibase:user")
const user = userFromStorage
? JSON.parse(userFromStorage)
: {
name: "annonymous", name: "annonymous",
permissions : [], permissions: [],
isUser:false, isUser: false,
temp:false temp: false,
}; }
if(!componentLibraries) { if (!componentLibraries) {
const rootPath =
const rootPath = appDefinition.appRootPath === "" appDefinition.appRootPath === ""
? "" ? ""
: "/" + trimSlash(appDefinition.appRootPath); : "/" + trimSlash(appDefinition.appRootPath)
const componentLibraryUrl = (lib) => rootPath + "/" + trimSlash(lib) const componentLibraryUrl = lib => rootPath + "/" + trimSlash(lib)
componentLibraries = {}; componentLibraries = {}
for(let lib of appDefinition.componentLibraries) { for (let lib of appDefinition.componentLibraries) {
componentLibraries[lib.libName] = await import( componentLibraries[lib.libName] = await import(
componentLibraryUrl(lib.importPath)); componentLibraryUrl(lib.importPath)
} )
} }
}
if(!props) {
props = appDefinition.props; if (!props) {
} props = appDefinition.props
}
const app = createApp(
window.document, const app = createApp(
componentLibraries, window.document,
appDefinition, componentLibraries,
user, appDefinition,
uiFunctions || {}); user,
app.hydrateChildren( uiFunctions || {}
[props], )
window.document.body); app.hydrateChildren([props], window.document.body)
return app; return app
}; }
if(window) { if (window) {
window.loadBudibase = loadBudibase; window.loadBudibase = loadBudibase
} }

120
packages/client/src/render/initialiseChildren.js

@ -1,72 +1,80 @@
import { import { setupBinding } from "../state/stateBinding"
setupBinding import { split, last } from "lodash/fp"
} from "../state/stateBinding"; import { $ } from "../core/common"
import { import { renderComponent } from "./renderComponent"
split,
last
} from "lodash/fp";
import { $ } from "../core/common";
import { renderComponent } from "./renderComponent";
export const _initialiseChildren = (initialiseOpts) => export const _initialiseChildren = initialiseOpts => (
(childrenProps, htmlElement, anchor=null) => { childrenProps,
htmlElement,
anchor = null
) => {
const {
uiFunctions,
bb,
coreApi,
store,
componentLibraries,
treeNode,
appDefinition,
document,
hydrate,
} = initialiseOpts
const { uiFunctions, bb, coreApi, for (let childNode of treeNode.children) {
store, componentLibraries, treeNode, if (childNode.unsubscribe) childNode.unsubscribe()
appDefinition, document, hydrate } = initialiseOpts; if (childNode.component) childNode.component.$destroy()
}
for(let childNode of treeNode.children) { if (hydrate) {
if(childNode.unsubscribe) while (htmlElement.firstChild) {
childNode.unsubscribe(); htmlElement.removeChild(htmlElement.firstChild)
if(childNode.component)
childNode.component.$destroy();
} }
}
if(hydrate) { htmlElement.classList.add(`lay-${treeNode.props._id}`)
while (htmlElement.firstChild) {
htmlElement.removeChild(htmlElement.firstChild); const renderedComponents = []
} for (let childProps of childrenProps) {
} const { componentName, libName } = splitName(childProps._component)
htmlElement.classList.add(`lay-${treeNode.props._id}`) if (!componentName || !libName) return
const { initialProps, bind } = setupBinding(
store,
childProps,
coreApi,
appDefinition.appRootPath
)
const renderedComponents = []; const componentConstructor = componentLibraries[libName][componentName]
for(let childProps of childrenProps) {
const {componentName, libName} = splitName(childProps._component);
if(!componentName || !libName) return; const renderedComponentsThisIteration = renderComponent({
props: childProps,
const {initialProps, bind} = setupBinding( parentNode: treeNode,
store, childProps, coreApi, componentConstructor,
appDefinition.appRootPath); uiFunctions,
htmlElement,
const componentConstructor = componentLibraries[libName][componentName]; anchor,
initialProps,
bb,
})
const renderedComponentsThisIteration = renderComponent({ for (let comp of renderedComponentsThisIteration) {
props: childProps, comp.unsubscribe = bind(comp.component)
parentNode: treeNode, renderedComponents.push(comp)
componentConstructor,uiFunctions,
htmlElement, anchor, initialProps,
bb});
for(let comp of renderedComponentsThisIteration) {
comp.unsubscribe = bind(comp.component);
renderedComponents.push(comp);
}
} }
}
return renderedComponents; return renderedComponents
} }
const splitName = fullname => { const splitName = fullname => {
const componentName = $(fullname, [ const componentName = $(fullname, [split("/"), last])
split("/"),
last
]);
const libName =fullname.substring( const libName = fullname.substring(
0, fullname.length - componentName.length - 1); 0,
fullname.length - componentName.length - 1
)
return {libName, componentName}; return { libName, componentName }
} }

102
packages/client/src/render/renderComponent.js

@ -1,66 +1,64 @@
export const renderComponent = ({ export const renderComponent = ({
componentConstructor, uiFunctions, componentConstructor,
htmlElement, anchor, props, uiFunctions,
initialProps, bb, htmlElement,
parentNode}) => { anchor,
props,
initialProps,
bb,
parentNode,
}) => {
const func = initialProps._id ? uiFunctions[initialProps._id] : undefined
const func = initialProps._id const parentContext = (parentNode && parentNode.context) || {}
? uiFunctions[initialProps._id]
: undefined;
const parentContext = (parentNode && parentNode.context) || {}; let renderedNodes = []
const render = context => {
let renderedNodes = []; let componentContext = parentContext
const render = (context) => { if (context) {
componentContext = { ...componentContext }
let componentContext = parentContext; componentContext.$parent = parentContext
if(context) { }
componentContext = {...componentContext};
componentContext.$parent = parentContext;
}
const thisNode = createTreeNode(); const thisNode = createTreeNode()
thisNode.context = componentContext; thisNode.context = componentContext
thisNode.parentNode = parentNode; thisNode.parentNode = parentNode
thisNode.props = props; thisNode.props = props
parentNode.children.push(thisNode); parentNode.children.push(thisNode)
renderedNodes.push(thisNode); renderedNodes.push(thisNode)
initialProps._bb = bb(thisNode, props); initialProps._bb = bb(thisNode, props)
thisNode.component = new componentConstructor({ thisNode.component = new componentConstructor({
target: htmlElement, target: htmlElement,
props: initialProps, props: initialProps,
hydrate:false, hydrate: false,
anchor anchor,
}); })
thisNode.rootElement = htmlElement.children[ thisNode.rootElement = htmlElement.children[htmlElement.children.length - 1]
htmlElement.children.length - 1];
if (initialProps._id) {
thisNode.rootElement.classList.add(`pos-${initialProps._id}`)
}
}
if(func) { if (initialProps._id) {
func(render, parentContext); thisNode.rootElement.classList.add(`pos-${initialProps._id}`)
} else {
render();
} }
}
return renderedNodes; if (func) {
func(render, parentContext)
} else {
render()
}
return renderedNodes
} }
export const createTreeNode = () => ({ export const createTreeNode = () => ({
context: {}, context: {},
props: {}, props: {},
rootElement: null, rootElement: null,
parentNode: null, parentNode: null,
children: [], children: [],
component: null, component: null,
unsubscribe: () => {} unsubscribe: () => {},
}); })

112
packages/client/src/state/coreHandlers.js

@ -1,63 +1,71 @@
import { ERROR } from "./standardState"; import { ERROR } from "./standardState"
export const getNewChildRecordToState = (coreApi, setState) => export const getNewChildRecordToState = (coreApi, setState) => ({
({recordKey, collectionName,childRecordType,statePath}) => { recordKey,
const error = errorHandler(setState); collectionName,
try { childRecordType,
if(!recordKey) { statePath,
error("getNewChild > recordKey not set"); }) => {
return; const error = errorHandler(setState)
} try {
if (!recordKey) {
if(!collectionName) { error("getNewChild > recordKey not set")
error("getNewChild > collectionName not set"); return
return;
}
if(!childRecordType) {
error("getNewChild > childRecordType not set");
return;
}
if(!statePath) {
error("getNewChild > statePath not set");
return;
}
const rec = coreApi.recordApi.getNewChild(recordKey, collectionName, childRecordType);
setState(statePath, rec);
} }
catch(e) {
error(e.message); if (!collectionName) {
error("getNewChild > collectionName not set")
return
} }
}
if (!childRecordType) {
error("getNewChild > childRecordType not set")
return
}
export const getNewRecordToState = (coreApi, setState) => if (!statePath) {
({collectionKey,childRecordType,statePath}) => { error("getNewChild > statePath not set")
const error = errorHandler(setState); return
try { }
if(!collectionKey) {
error("getNewChild > collectionKey not set");
return;
}
if(!childRecordType) { const rec = coreApi.recordApi.getNewChild(
error("getNewChild > childRecordType not set"); recordKey,
return; collectionName,
} childRecordType
)
setState(statePath, rec)
} catch (e) {
error(e.message)
}
}
if(!statePath) { export const getNewRecordToState = (coreApi, setState) => ({
error("getNewChild > statePath not set"); collectionKey,
return; childRecordType,
} statePath,
}) => {
const error = errorHandler(setState)
try {
if (!collectionKey) {
error("getNewChild > collectionKey not set")
return
}
const rec = coreApi.recordApi.getNew(collectionKey, childRecordType); if (!childRecordType) {
setState(statePath, rec); error("getNewChild > childRecordType not set")
return
} }
catch(e) {
error(e.message); if (!statePath) {
error("getNewChild > statePath not set")
return
} }
const rec = coreApi.recordApi.getNew(collectionKey, childRecordType)
setState(statePath, rec)
} catch (e) {
error(e.message)
}
} }
const errorHandler = setState => message => setState(ERROR, message); const errorHandler = setState => message => setState(ERROR, message)

110
packages/client/src/state/eventHandlers.js

@ -1,56 +1,54 @@
import { setState } from "./setState"; import { setState } from "./setState"
import { getState } from "./getState"; import { getState } from "./getState"
import { import { isArray, isUndefined } from "lodash/fp"
isArray, isUndefined
} from "lodash/fp"; import { createApi } from "../api"
import { getNewChildRecordToState, getNewRecordToState } from "./coreHandlers"
import { createApi } from "../api";
import { export const EVENT_TYPE_MEMBER_NAME = "##eventHandlerType"
getNewChildRecordToState, getNewRecordToState
} from "./coreHandlers"; export const eventHandlers = (store, coreApi, rootPath) => {
const handler = (parameters, execute) => ({
export const EVENT_TYPE_MEMBER_NAME = "##eventHandlerType"; execute,
parameters,
export const eventHandlers = (store,coreApi,rootPath) => { })
const handler = (parameters, execute) => ({ const setStateWithStore = (path, value) => setState(store, path, value)
execute, parameters
}); let currentState
store.subscribe(s => {
const setStateWithStore = (path, value) => setState(store, path, value); currentState = s
})
let currentState;
store.subscribe(s => { const api = createApi({
currentState = s; rootPath: rootPath,
}); setState: setStateWithStore,
getState: (path, fallback) => getState(currentState, path, fallback),
const api = createApi({ })
rootPath:rootPath,
setState: setStateWithStore, const setStateHandler = ({ path, value }) => setState(store, path, value)
getState: (path, fallback) => getState(currentState, path, fallback)
}); return {
"Set State": handler(["path", "value"], setStateHandler),
const setStateHandler = ({path, value}) => setState(store, path, value); "Load Record": handler(["recordKey", "statePath"], api.loadRecord),
"List Records": handler(["indexKey", "statePath"], api.listRecords),
return { "Save Record": handler(["statePath"], api.saveRecord),
"Set State": handler(["path", "value"], setStateHandler),
"Load Record": handler(["recordKey", "statePath"], api.loadRecord), "Get New Child Record": handler(
"List Records": handler(["indexKey", "statePath"], api.listRecords), ["recordKey", "collectionName", "childRecordType", "statePath"],
"Save Record": handler(["statePath"], api.saveRecord), getNewChildRecordToState(coreApi, setStateWithStore)
),
"Get New Child Record": handler(
["recordKey", "collectionName", "childRecordType", "statePath"], "Get New Record": handler(
getNewChildRecordToState(coreApi, setStateWithStore)), ["collectionKey", "childRecordType", "statePath"],
getNewRecordToState(coreApi, setStateWithStore)
"Get New Record": handler( ),
["collectionKey", "childRecordType", "statePath"],
getNewRecordToState(coreApi, setStateWithStore)), Authenticate: handler(["username", "password"], api.authenticate),
}
"Authenticate": handler(["username", "password"], api.authenticate) }
};
}; export const isEventType = prop =>
isArray(prop) &&
export const isEventType = prop => prop.length > 0 &&
isArray(prop) !isUndefined(prop[0][EVENT_TYPE_MEMBER_NAME])
&& prop.length > 0
&& !isUndefined(prop[0][EVENT_TYPE_MEMBER_NAME]);

90
packages/client/src/state/getState.js

@ -1,67 +1,59 @@
import { isUndefined, isObject } from "lodash/fp"
import { import {
isUndefined, isBound,
isObject BB_STATE_BINDINGPATH,
} from "lodash/fp"; BB_STATE_FALLBACK,
import { takeStateFromStore,
isBound,BB_STATE_BINDINGPATH, BB_STATE_FALLBACK, takeStateFromStore } from "./isState"
} from "./isState";
export const getState = (s, path, fallback) => { export const getState = (s, path, fallback) => {
if (!s) return fallback
if (!path || path.length === 0) return fallback
if(!s) return fallback; if (path === "$") return s
if(!path || path.length === 0) return fallback;
if(path === "$") return s; const pathParts = path.split(".")
const safeGetPath = (obj, currentPartIndex = 0) => {
const currentKey = pathParts[currentPartIndex]
const pathParts = path.split("."); if (pathParts.length - 1 == currentPartIndex) {
const safeGetPath = (obj, currentPartIndex=0) => { const value = obj[currentKey]
if (isUndefined(value)) return fallback
const currentKey = pathParts[currentPartIndex]; else return value
}
if(pathParts.length - 1 == currentPartIndex) {
const value = obj[currentKey];
if(isUndefined(value))
return fallback;
else
return value;
}
if(obj[currentKey] === null
|| obj[currentKey] === undefined
|| !isObject(obj[currentKey])) {
return fallback;
}
return safeGetPath(obj[currentKey], currentPartIndex + 1);
if (
obj[currentKey] === null ||
obj[currentKey] === undefined ||
!isObject(obj[currentKey])
) {
return fallback
} }
return safeGetPath(obj[currentKey], currentPartIndex + 1)
}
return safeGetPath(s); return safeGetPath(s)
} }
export const getStateOrValue = (globalState, prop, currentContext) => { export const getStateOrValue = (globalState, prop, currentContext) => {
if (!prop) return prop
if(!prop) return prop;
if(isBound(prop)) {
const stateToUse = takeStateFromStore(prop) if (isBound(prop)) {
? globalState const stateToUse = takeStateFromStore(prop) ? globalState : currentContext
: currentContext;
return getState(stateToUse, prop[BB_STATE_BINDINGPATH], prop[BB_STATE_FALLBACK]); return getState(
} stateToUse,
prop[BB_STATE_BINDINGPATH],
prop[BB_STATE_FALLBACK]
)
}
if(prop.path && prop.source) { if (prop.path && prop.source) {
const stateToUse = prop.source === "store" const stateToUse = prop.source === "store" ? globalState : currentContext
? globalState
: currentContext;
return getState(stateToUse, prop.path, prop.fallback); return getState(stateToUse, prop.path, prop.fallback)
} }
return prop; return prop
}
}

27
packages/client/src/state/isState.js

@ -1,17 +1,16 @@
export const BB_STATE_BINDINGPATH = "##bbstate"; export const BB_STATE_BINDINGPATH = "##bbstate"
export const BB_STATE_BINDINGSOURCE = "##bbsource"; export const BB_STATE_BINDINGSOURCE = "##bbsource"
export const BB_STATE_FALLBACK = "##bbstatefallback"; export const BB_STATE_FALLBACK = "##bbstatefallback"
export const isBound = (prop) => export const isBound = prop =>
prop !== undefined prop !== undefined && prop[BB_STATE_BINDINGPATH] !== undefined
&& prop[BB_STATE_BINDINGPATH] !== undefined;
export const takeStateFromStore = (prop) =>
prop[BB_STATE_BINDINGSOURCE] === undefined
|| prop[BB_STATE_BINDINGSOURCE] === "store";
export const takeStateFromContext = (prop) => export const takeStateFromStore = prop =>
prop[BB_STATE_BINDINGSOURCE] === "context"; prop[BB_STATE_BINDINGSOURCE] === undefined ||
prop[BB_STATE_BINDINGSOURCE] === "store"
export const takeStateFromEventParameters = (prop) => export const takeStateFromContext = prop =>
prop[BB_STATE_BINDINGSOURCE] === "event"; prop[BB_STATE_BINDINGSOURCE] === "context"
export const takeStateFromEventParameters = prop =>
prop[BB_STATE_BINDINGSOURCE] === "event"

56
packages/client/src/state/setState.js

@ -1,38 +1,34 @@
import { import { isObject } from "lodash/fp"
isObject import { BB_STATE_BINDINGPATH } from "./isState"
} from "lodash/fp";
import { BB_STATE_BINDINGPATH } from "./isState";
export const setState = (store, path, value) => { export const setState = (store, path, value) => {
if (!path || path.length === 0) return
if(!path || path.length === 0) return; const pathParts = path.split(".")
const safeSetPath = (obj, currentPartIndex = 0) => {
const currentKey = pathParts[currentPartIndex]
const pathParts = path.split("."); if (pathParts.length - 1 == currentPartIndex) {
const safeSetPath = (obj, currentPartIndex=0) => { obj[currentKey] = value
return
const currentKey = pathParts[currentPartIndex]; }
if(pathParts.length - 1 == currentPartIndex) {
obj[currentKey] = value;
return;
}
if(obj[currentKey] === null
|| obj[currentKey] === undefined
|| !isObject(obj[currentKey])) {
obj[currentKey] = {};
}
safeSetPath(obj[currentKey], currentPartIndex + 1);
if (
obj[currentKey] === null ||
obj[currentKey] === undefined ||
!isObject(obj[currentKey])
) {
obj[currentKey] = {}
} }
store.update(s => { safeSetPath(obj[currentKey], currentPartIndex + 1)
safeSetPath(s); }
return s;
}); store.update(s => {
}; safeSetPath(s)
return s
})
}
export const setStateFromBinding = (store, binding, value) => export const setStateFromBinding = (store, binding, value) =>
setState(store, binding[BB_STATE_BINDINGPATH], value); setState(store, binding[BB_STATE_BINDINGPATH], value)

3
packages/client/src/state/standardState.js

@ -1,2 +1 @@
export const ERROR = "##error_message"
export const ERROR = "##error_message";

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save