Browse Source

running prettier over codebase, removing merge files

pull/4023/head
Martin McKeaveney 6 years ago
parent
commit
0808178dfa
  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": {
"browser": true,
"es6": true,
"jest": true
"jest": true,
"node": true
},
"parserOptions": {
"ecmaVersion": 2019,

1
package.json

@ -18,6 +18,7 @@
"dev": "lerna run --parallel --stream dev:builder",
"test": "lerna run test",
"lint": "eslint packages",
"lint:fix": "eslint --fix packages",
"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 resolve from 'rollup-plugin-node-resolve';
import svelte from "rollup-plugin-svelte"
import resolve from "rollup-plugin-node-resolve"
export default {
input: 'src/index.js',
output: [
{
file: "dist/index.js",
format: 'esm',
name:"budibaseStandardComponents",
sourcemap: "inline"
}
],
plugins: [
svelte({
hydratable:true
}),
resolve()
]
};
input: "src/index.js",
output: [
{
file: "dist/index.js",
format: "esm",
name: "budibaseStandardComponents",
sourcemap: "inline",
},
],
plugins: [
svelte({
hydratable: true,
}),
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 {
input: 'src/generators.js',
output: [
{
file: "dist/generators.js",
format: 'esm',
name:"budibaseStandardComponents",
sourcemap: "inline"
}
],
plugins: [
resolve()
]
};
input: "src/generators.js",
output: [
{
file: "dist/generators.js",
format: "esm",
name: "budibaseStandardComponents",
sourcemap: "inline",
},
],
plugins: [resolve()],
}

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

@ -1,89 +1,131 @@
import svelte from 'rollup-plugin-svelte';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import json from 'rollup-plugin-json';
import svelte from "rollup-plugin-svelte"
import resolve from "rollup-plugin-node-resolve"
import commonjs from "rollup-plugin-commonjs"
import livereload from "rollup-plugin-livereload"
import { terser } from "rollup-plugin-terser"
import json from "rollup-plugin-json"
const production = !process.env.ROLLUP_WATCH;
const production = !process.env.ROLLUP_WATCH
const lodash_fp_exports = [
"find", "isUndefined", "split",
"last", "union", "reduce", "isObject",
"cloneDeep", "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"];
"find",
"isUndefined",
"split",
"last",
"union",
"reduce",
"isObject",
"cloneDeep",
"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 = [
"flow", "head",
"tail", "findIndex", "startsWith",
"dropRight", "takeRight",
"trim", "split", "replace",
"merge", "assign"];
"flow",
"head",
"tail",
"findIndex",
"startsWith",
"dropRight",
"takeRight",
"trim",
"split",
"replace",
"merge",
"assign",
]
const coreExternal = [
"lodash", "lodash/fp", "date-fns",
"lunr", "safe-buffer", "shortid",
"@nx-js/compiler-util"
];
"lodash",
"lodash/fp",
"date-fns",
"lunr",
"safe-buffer",
"shortid",
"@nx-js/compiler-util",
]
export default {
input: 'src/Test/testMain.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/bundle.js'
},
plugins: [
svelte({
// enable run-time checks when not in production
dev: !production,
// we'll extract any component CSS out into
// a separate file — better for performance
css: css => {
css.write('public/bundle.css');
},
hydratable:true
}),
input: "src/Test/testMain.js",
output: {
sourcemap: true,
format: "iife",
name: "app",
file: "public/bundle.js",
},
plugins: [
svelte({
// enable run-time checks when not in production
dev: !production,
// we'll extract any component CSS out into
// a separate file — better for performance
css: css => {
css.write("public/bundle.css")
},
// If you have external dependencies installed from
// 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(),
hydratable: true,
}),
// Watch the `public` directory and refresh the
// browser on changes when not in production
!production && livereload('public'),
// If you have external dependencies installed from
// 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(),
// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser()
],
watch: {
clearScreen: false
}
};
// Watch the `public` directory and refresh the
// browser on changes when not in production
!production && livereload("public"),
// If we're building for production (npm run build
// 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 { constants } = require("fs");
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);
const { readdir, stat, copyFile } = require("fs-extra")
const { constants } = require("fs")
const { join, basename } = require("path")
for(let app of apps) {
if(!(await stat(join(appPackages, app))).isDirectory()) continue;
const packagesFolder = ".."
await copySourceJs(nodeModulesDist(app));
await copySourceJsMap(nodeModulesDist(app));
await copyGenerators(nodeModulesDist(app));
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")
await copyComponentsJson(nodeModules(app));
const appPackages = join(packagesFolder, "server", "appPackages")
await copySourceJs(join(publicMain(app), "dist"));
await copySourceJsMap(join(publicMain(app), "dist"));
await copyGenerators(join(publicMain(app), "dist"));
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)
await copySourceJs(join(publicUnauth(app), "dist"));
await copySourceJsMap(join(publicUnauth(app), "dist"));
await copyGenerators(join(publicUnauth(app), "dist"));
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) {
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 Grid from "../Grid.svelte";
import Form from "../Form.svelte";
import Textbox from "../Textbox.svelte";
import Text from "../Text.svelte";
import Nav from "../Nav.svelte";
import Panel from "../Panel.svelte";
import StackPanel from "../StackPanel.svelte";
import Table from "../Table.svelte";
import Button from "../Button.svelte";
import { createApp } from "@budibase/client/src/createApp";
import Login from "../Login.svelte"
import Grid from "../Grid.svelte"
import Form from "../Form.svelte"
import Textbox from "../Textbox.svelte"
import Text from "../Text.svelte"
import Nav from "../Nav.svelte"
import Panel from "../Panel.svelte"
import StackPanel from "../StackPanel.svelte"
import Table from "../Table.svelte"
import Button from "../Button.svelte"
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 = {
components : {
login : Login,
grid : Grid,
form : Form,
textbox : Textbox,
text: Text,
nav: Nav,
panel: Panel,
table: Table,
stackpanel: StackPanel,
button: Button
}
}
const appDef = { hierarchy: {}, actions: {} }
const user = { name: "yeo", permissions: [] }
const appDef = {hierarchy:{}, actions:{}};
const user = {name:"yeo", permissions:[]};
var app = createApp(componentLibraries, appDef, user);
app.store.update(s => {
s.people = [
{name:"bob", address: "123 Main Street", status: "Open"},
{name:"poppy", address: "456 Side Road", status: "Closed"},
{name:"Oscar", address: "678 Dodgy Alley", status: "Open"},
];
return s;
})
var app = createApp(componentLibraries, appDef, user)
app.store.update(s => {
s.people = [
{ name: "bob", address: "123 Main Street", status: "Open" },
{ name: "poppy", address: "456 Side Road", status: "Closed" },
{ 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 = {
login: { _component: "components/login" },
login: { _component:"components/login" },
form: {
_component: "components/form",
formControls: [
{
control: {
_component: "components/textbox"
},
label:"First Name"
},
{
control: {
_component: "components/textbox"
},
label:"Last Name"
}
]
},
form: {
_component: "components/form",
formControls: [
{
control: {
_component: "components/textbox",
},
label: "First Name",
},
{
control: {
_component: "components/textbox",
},
label: "Last Name",
},
],
},
nav: {
_component: "components/nav",
navBarBackground: "red",
navBarBorder: "1px solid maroon",
navBarColor: "black",
selectedItemBackground: "maroon",
selectedItemColor: "white",
selectedItemBorder: "green",
itemHoverBackground: "yellow",
itemHoverColor: "pink",
items: [
{
title: "People",
component: {
_component: "components/panel",
text:"People Panel",
padding: "40px",
border: "2px solid pink",
background: "mistyrose"
nav: {
_component: "components/nav",
navBarBackground: "red",
navBarBorder: "1px solid maroon",
navBarColor: "black",
selectedItemBackground: "maroon",
selectedItemColor: "white",
selectedItemBorder: "green",
itemHoverBackground: "yellow",
itemHoverColor: "pink",
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",
},
},
],
},
}
},
{
title: "Animals",
component: {
_component: "components/panel",
text:"Animals Panel",
padding: "40px",
border: "2px solid green",
background: "azure"
}
}
]
table: {
_component: "components/table",
columns: [
{
title: {
"##bbstate": "NameColumnName",
"##bbsource": "store",
"##bbstatefallback": "Name",
},
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: {
_component:"components/table",
columns: [
{
title: {
"##bbstate":"NameColumnName",
"##bbsource":"store",
"##bbstatefallback": "Name"
},
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"
grid: {
_component: "components/grid",
gridTemplateColumns: "[left] auto [center] auto [right] auto",
gridTemplateRows: "[top] auto [middle] auto [bottom] auto",
children: [
{
control: {
_component: "components/text",
value: "1",
background: "blue",
textAlign: "center",
color: "white",
},
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",
direction: "horizontal",
children: [
{
control: {
_component: "components/text",
value: "STATIC",
},
},
],
data: {
"##bbstate": "people",
},
grid: {
_component: "components/grid",
gridTemplateColumns: "[left] auto [center] auto [right] auto",
gridTemplateRows: "[top] auto [middle] auto [bottom] auto",
children : [
{
control: {
_component: "components/text",
value: "1",
background: "blue",
textAlign:"center",
color: "white"
},
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"
}
]
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",
height: "200px",
weight: "200px",
},
boundStackPanel: {
_component: "components/stackpanel",
direction: "horizontal",
children: [
},
hiddenNav: {
_component: "components/stackpanel",
children: [
{
control: {
_component: "components/button",
contentText: "Peep",
onClick: [
{
control: {
_component: "components/text",
value: "STATIC"
}
}
],
data: {
"##bbstate":"people"
},
dataItemComponent: {
_component: "components/panel",
text: {
"##bbstate":"name",
"##bbsource":"context",
"##bbstatefallback": "balls to that"
"##eventHandlerType": "Set State",
parameters: {
path: "selected",
value: "People",
},
},
padding: "10px",
border: "5px solid black",
margin: "10px",
hoverColor: "white",
hoverBackground: "black",
height:"200px",
weight:"200px"
}
},
hiddenNav: {
_component: "components/stackpanel",
children: [
],
},
},
{
control: {
_component: "components/button",
contentText: "Ani",
onClick: [
{
control:{
_component: "components/button",
contentText: "Peep",
onClick: [
{
"##eventHandlerType": "Set State",
parameters: {
path: "selected",
value: "People"
}
}
]
}
"##eventHandlerType": "Set State",
parameters: {
path: "selected",
value: "Animals",
},
},
],
},
},
{
control: {
_component: "components/nav",
hideNavBar: true,
selectedItem: {
"##bbstate": "selected",
"##bbsource": "store",
"##bbstatefallback": "Animals",
},
items: [
{
control:{
_component: "components/button",
contentText: "Ani",
onClick: [
{
"##eventHandlerType": "Set State",
parameters: {
path: "selected",
value: "Animals"
}
}
]
}
title: "People",
component: {
_component: "components/panel",
text: "People Panel",
padding: "40px",
border: "2px solid pink",
background: "mistyrose",
},
},
{
control: {
_component: "components/nav",
hideNavBar: true,
selectedItem: {
"##bbstate":"selected",
"##bbsource":"store",
"##bbstatefallback": "Animals"
},
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"
}
}
]
}
}
]
}
}
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({
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) => {
let str = "";
for(let s in styles) {
if(styles[s]) {
str += `${s}: ${styles[s]}; `
}
export const buildStyle = styles => {
let str = ""
for (let s in styles) {
if (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
export default (node, props) => {
Object.entries(props).forEach(([key, value]) => {
node.style.setProperty(`--${key}`, value);
});
return {
update(new_props) {
Object.entries(new_props).forEach(([key, value]) => {
node.style.setProperty(`--${key}`, value);
delete props[key];
});
Object.keys(props).forEach(name =>
node.style.removeProperty(`--${name}`),
);
props = new_props;
},
};
};
Object.entries(props).forEach(([key, value]) => {
node.style.setProperty(`--${key}`, value)
})
return {
update(new_props) {
Object.entries(new_props).forEach(([key, value]) => {
node.style.setProperty(`--${key}`, value)
delete props[key]
})
Object.keys(props).forEach(name => node.style.removeProperty(`--${name}`))
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 { indexTables } from "./generators/indexTablesGenerator";
export { app } from "./generators/appGenerator";
export { recordHomePageComponents as recordHomepages } from "./generators/recordHomePageGenerator";
export { forms } from "./generators/formsGenerator"
export { indexTables } from "./generators/indexTablesGenerator"
export { app } from "./generators/appGenerator"
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 { recordHomepages } from "./recordHomePageGenerator";
export const app = ({records, indexes, helpers}) => [
{
name: "Application Root",
inherits: "@budibase/bootstrap-components/nav",
props: {
items: recordHomepages({indexes, records})
.map(navItem),
orientation: "horizontal",
alignment: "start",
fill: false,
pills: true,
selectedItem: {
"##bbstate":"selectedNav",
"##bbstatefallback":`${records[0].name}`,
"##bbsource": "store"
},
className: "p-3"
}
import {
navContentComponentName,
selectNavContent,
} from "./selectedNavContentGenerator"
import { recordHomepages } from "./recordHomePageGenerator"
export const app = ({ records, indexes, helpers }) => [
{
name: "Application Root",
inherits: "@budibase/bootstrap-components/nav",
props: {
items: recordHomepages({ indexes, records }).map(navItem),
orientation: "horizontal",
alignment: "start",
fill: false,
pills: true,
selectedItem: {
"##bbstate": "selectedNav",
"##bbstatefallback": `${records[0].name}`,
"##bbsource": "store",
},
className: "p-3",
},
{
name: "Login",
inherits: "@budibase/standard-components/login",
props: {}
},
...selectNavContent({records, indexes, helpers})
},
{
name: "Login",
inherits: "@budibase/standard-components/login",
props: {},
},
...selectNavContent({ records, indexes, helpers }),
]
export const navItem = ({record}) => ({
title: record.collectionName,
component : {
_component: navContentComponentName(record)
}
export const navItem = ({ record }) => ({
title: record.collectionName,
component: {
_component: navContentComponentName(record),
},
})

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

@ -1,18 +1,18 @@
export const buttons = () => [
{
name: "common/Primary Button",
description: "Bootstrap primary button ",
inherits: "@budibase/standard-components/button",
props: {
className: "btn btn-primary"
}
{
name: "common/Primary Button",
description: "Bootstrap primary button ",
inherits: "@budibase/standard-components/button",
props: {
className: "btn btn-primary",
},
{
name: "common/Default Button",
description: "Bootstrap default button",
inherits: "@budibase/standard-components/button",
props: {
className: "btn btn-secondary"
}
}
]
},
{
name: "common/Default Button",
description: "Bootstrap default button",
inherits: "@budibase/standard-components/button",
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}) =>
[
...records.map(root),
...buttons({records, indexes, helpers})
];
export const forms = ({ records, indexes, helpers }) => [
...records.map(root),
...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 => ({
name: formName(record),
description: `Control for creating/updating '${record.nodeKey()}' `,
inherits: "@budibase/standard-components/div",
props: {
className:"p-1",
children: [
{
component: {
_component: "@budibase/standard-components/h3",
text: `Edit ${record.name}`,
}
},
form(record),
saveCancelButtons(record)
]
}
})
name: formName(record),
description: `Control for creating/updating '${record.nodeKey()}' `,
inherits: "@budibase/standard-components/div",
props: {
className: "p-1",
children: [
{
component: {
_component: "@budibase/standard-components/h3",
text: `Edit ${record.name}`,
},
},
form(record),
saveCancelButtons(record),
],
},
})
const form = record => ({
component: {
_component: "@budibase/standard-components/form",
formControls:
record.fields.map(f => formControl(record, f))
}
component: {
_component: "@budibase/standard-components/form",
formControls: record.fields.map(f => formControl(record, f)),
},
})
const formControl = (record, field) => {
if(field.type === "string" && field.typeOptions.values && field.typeOptions.values.length > 0) {
return ({
control: {
_component: "@budibase/standard-components/select",
options: field.typeOptions.values.map(v => ({id:v, value:v})),
value: {
"##bbstate":`${record.name}.${field.name}`,
"##bbsource":"store"
},
className: "form-control"
},
label: field.label
});
} else {
return ({
control: {
_component: "@budibase/standard-components/input",
value: {
"##bbstate":`${record.name}.${field.name}`,
"##bbsource":"store"
},
className: "form-control",
type: field.type === "string" ? "text"
: field.type === "datetime" ? "date"
: field.type === "number" ? "number"
: "text"
},
label: field.label
});
if (
field.type === "string" &&
field.typeOptions.values &&
field.typeOptions.values.length > 0
) {
return {
control: {
_component: "@budibase/standard-components/select",
options: field.typeOptions.values.map(v => ({ id: v, value: v })),
value: {
"##bbstate": `${record.name}.${field.name}`,
"##bbsource": "store",
},
className: "form-control",
},
label: field.label,
}
} else {
return {
control: {
_component: "@budibase/standard-components/input",
value: {
"##bbstate": `${record.name}.${field.name}`,
"##bbsource": "store",
},
className: "form-control",
type:
field.type === "string"
? "text"
: field.type === "datetime"
? "date"
: field.type === "number"
? "number"
: "text",
},
label: field.label,
}
}
}
const saveCancelButtons = (record) => ({
component: {
_component: "@budibase/standard-components/stackpanel",
direction: "horizontal",
children: [
paddedPanelForButton({
_component: "common/Primary Button",
contentText: `Save ${record.name}`,
onClick: [
{
"##eventHandlerType": "Save Record",
parameters: {
statePath: `${record.name}`,
}
},
{
"##eventHandlerType": "Set State",
parameters: {
path: `isEditing${record.name}`,
value: ""
}
}
]
}),
paddedPanelForButton({
_component: "common/Default Button",
contentText: `Cancel`,
onClick: [
{
"##eventHandlerType": "Set State",
parameters: {
path: `isEditing${record.name}`,
value: ""
}
}
]
})
]
}
const saveCancelButtons = record => ({
component: {
_component: "@budibase/standard-components/stackpanel",
direction: "horizontal",
children: [
paddedPanelForButton({
_component: "common/Primary Button",
contentText: `Save ${record.name}`,
onClick: [
{
"##eventHandlerType": "Save Record",
parameters: {
statePath: `${record.name}`,
},
},
{
"##eventHandlerType": "Set State",
parameters: {
path: `isEditing${record.name}`,
value: "",
},
},
],
}),
paddedPanelForButton({
_component: "common/Default Button",
contentText: `Cancel`,
onClick: [
{
"##eventHandlerType": "Set State",
parameters: {
path: `isEditing${record.name}`,
value: "",
},
},
],
}),
],
},
})
const paddedPanelForButton = (button) => ({
control: {
_component: "@budibase/standard-components/div",
className: "btn-group",
children: [
{
component: button
}
]
}
});
const paddedPanelForButton = button => ({
control: {
_component: "@budibase/standard-components/div",
className: "btn-group",
children: [
{
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);
if(current.parent().type === "root") {
return;
}
add(current.parent());
const add = current => {
parts.push(current.name)
if (current.parent().type === "root") {
return
}
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}) =>
indexes.map(i => indexTable(i, helpers));
export const indexTables = ({ indexes, 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) => ({
data: {
"##bbstate":index.nodeKey(),
"##bbsource":"store"
data: {
"##bbstate": index.nodeKey(),
"##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) => {
record = record
|| index.parent().type === "record" ? index.parent() : null;
return (record
? `${getRecordPath(record)}/${index.name} Table`
: `${index.name} Table`);
record = record || index.parent().type === "record" ? index.parent() : null
return record
? `${getRecordPath(record)}/${index.name} Table`
: `${index.name} Table`
}
const indexTable = (index, helpers) => ({
name: getIndexTableName(index),
inherits: "@budibase/standard-components/table",
props: indexTableProps(index, helpers)
});
name: getIndexTableName(index),
inherits: "@budibase/standard-components/table",
props: indexTableProps(index, helpers),
})
const column = (col) => ({
title: col.name,
value: {
"##bbstate": col.name,
"##bbsource":"context"
}
})
const column = col => ({
title: col.name,
value: {
"##bbstate": col.name,
"##bbsource": "context",
},
})

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

@ -1,190 +1,183 @@
import {
getIndexTableName, indexTables
} from "./indexTablesGenerator";
import { getIndexTableName, indexTables } from "./indexTablesGenerator"
import {
buttons
} from "./buttonGenerators";
import { buttons } from "./buttonGenerators"
export const recordHomePageComponents = ({indexes, records, helpers}) =>
[
...recordHomepages({indexes, records})
.map(component),
export const recordHomePageComponents = ({ indexes, records, helpers }) => [
...recordHomepages({ indexes, records }).map(component),
...recordHomepages({indexes, records})
.map(homePageButtons),
...indexTables({indexes, records, helpers}),
...recordHomepages({ indexes, records }).map(homePageButtons),
...buttons({indexes, buttons, helpers})
]
...indexTables({ indexes, records, helpers }),
...buttons({ indexes, buttons, helpers }),
]
const findIndexForRecord = (indexes, record) => {
const forRecord = indexes.filter(i => i.allowedRecordNodeIds.includes(record.nodeId));
if(forRecord.length === 0) return;
if(forRecord.length === 1) return forRecord[0];
const noMap = forRecord.filter(i => !i.filter || !i.filter.trim());
if(noMap.length === 0) forRecord[0];
return noMap[0];
const forRecord = indexes.filter(i =>
i.allowedRecordNodeIds.includes(record.nodeId)
)
if (forRecord.length === 0) return
if (forRecord.length === 1) return forRecord[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}) =>
records.filter(r => r.parent().type === "root")
.map(r =>({
record:r,
index:findIndexForRecord(indexes, r)
}))
.filter(r => r.index);
export const recordHomepages = ({ indexes, records }) =>
records
.filter(r => r.parent().type === "root")
.map(r => ({
record: r,
index: findIndexForRecord(indexes, r),
}))
.filter(r => r.index)
export const homepageComponentName = record =>
`${record.name}/${record.name} homepage`
export const homepageComponentName = (record) =>
`${record.name}/${record.name} homepage`;
const component = ({ record, index }) => ({
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}) => ({
inherits: "@budibase/standard-components/div",
name: homepageComponentName(record),
props: {
className: "d-flex flex-column h-100",
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: "@budibase/standard-components/div",
className: "btn-group mr-3",
children: [
{
component: {
_component: `${record.name}/homepage buttons`,
}
component: {
_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: getIndexTableName(index)
},
className: "flex-gow-1 overflow-auto"
}
],
onLoad: [
{
"##eventHandlerType": "Set State",
parameters: {
path: `isEditing${record.name}`,
value: ""
}
component: {
_component: "common/Default Button",
contentText: `Refresh`,
onClick: [
{
"##eventHandlerType": "List Records",
parameters: {
statePath: index.nodeKey(),
indexKey: index.nodeKey(),
},
},
],
},
},
{
"##eventHandlerType": "List Records",
parameters: {
statePath: index.nodeKey(),
indexKey: index.nodeKey()
}
}
]
}
});
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: "@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: [
{
component: {
_component: "@budibase/standard-components/div",
className: "btn-group mr-3",
children: [
{
component: {
_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: "common/Default Button",
contentText: `Edit ${record.name}`,
onClick: [
{
"##eventHandlerType": "Load Record",
parameters: {
statePath: record.name,
recordKey: {
"##bbstate": `selectedrow_${index.name}`,
"##source": "store",
},
{
component: {
_component: "common/Default Button",
contentText: `Refresh`,
onClick: [
{
"##eventHandlerType": "List Records",
parameters: {
statePath: index.nodeKey(),
indexKey: index.nodeKey()
}
}
]
}
}
]
}
},
{
},
},
{
"##eventHandlerType": "Set State",
parameters: {
path: `isEditing${record.name}`,
value: "true",
},
},
],
},
},
{
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: [
{
component: {
_component: "common/Default Button",
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"
}
}
}
]
}
}
]
}
}
}
]
}
})
_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 {
recordHomepages,
homepageComponentName,
recordHomePageComponents
} from "./recordHomePageGenerator";
import { formName, forms } from "./formsGenerator";
import {
recordHomepages,
homepageComponentName,
recordHomePageComponents,
} from "./recordHomePageGenerator"
import { formName, forms } from "./formsGenerator"
export const selectNavContent = ({indexes, records, helpers}) =>
[
...recordHomepages({indexes, records})
.map(component),
export const selectNavContent = ({ indexes, records, helpers }) => [
...recordHomepages({ indexes, records }).map(component),
...recordHomePageComponents({indexes, records, helpers}),
...forms({indexes, records, helpers})
]
...recordHomePageComponents({ indexes, records, helpers }),
...forms({ indexes, records, helpers }),
]
export const navContentComponentName = record =>
`${record.name}/${record.name} Nav Content`;
`${record.name}/${record.name} Nav Content`
const component = ({record, index}) => ({
inherits: "@budibase/standard-components/if",
description: `the component that gets displayed when the ${record.collectionName} nav is selected`,
name: navContentComponentName(record),
props: {
condition: `$store.isEditing${record.name}`,
thenComponent: {
_component: formName(record)
},
elseComponent: {
_component: homepageComponentName(record)
}
}
});
const component = ({ record }) => ({
inherits: "@budibase/standard-components/if",
description: `the component that gets displayed when the ${record.collectionName} nav is selected`,
name: navContentComponentName(record),
props: {
condition: `$store.isEditing${record.name}`,
thenComponent: {
_component: formName(record),
},
elseComponent: {
_component: homepageComponentName(record),
},
},
})

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

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

25
packages/builder/babel.config.js

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

16
packages/builder/build/copy.js

@ -1,8 +1,8 @@
const ncp = require('ncp').ncp;
ncp("./dist", "../server/builder", function (err) {
if (err) {
return console.error(err);
}
console.log('Copied dist folder to ../server/builder');
})
const ncp = require("ncp").ncp
ncp("./dist", "../server/builder", function(err) {
if (err) {
return console.error(err)
}
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 resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import url from 'rollup-plugin-url';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import builtins from 'rollup-plugin-node-builtins';
import nodeglobals from 'rollup-plugin-node-globals';
import copy from 'rollup-plugin-copy';
import browsersync from "rollup-plugin-browsersync";
import proxy from "http-proxy-middleware";
import svelte from "rollup-plugin-svelte"
import resolve from "rollup-plugin-node-resolve"
import commonjs from "rollup-plugin-commonjs"
import url from "rollup-plugin-url"
import livereload from "rollup-plugin-livereload"
import { terser } from "rollup-plugin-terser"
import builtins from "rollup-plugin-node-builtins"
import nodeglobals from "rollup-plugin-node-globals"
import copy from "rollup-plugin-copy"
import browsersync from "rollup-plugin-browsersync"
import proxy from "http-proxy-middleware"
const target = 'http://localhost:4001';
const _builderProxy = proxy('/_builder', {
target: "http://localhost:3000",
pathRewrite: { '^/_builder': '' }
});
const target = "http://localhost:4001"
const _builderProxy = proxy("/_builder", {
target: "http://localhost:3000",
pathRewrite: { "^/_builder": "" },
})
const apiProxy = proxy(['/_builder/api/**', '/_builder/**/componentlibrary', '/_builder/**/componentlibraryGenerators'], {
target,
logLevel: "debug",
changeOrigin: true,
cookieDomainRewrite: true,
onProxyReq(proxyReq) {
if (proxyReq.getHeader("origin")) {
proxyReq.setHeader("origin", target)
}
}
});
const apiProxy = proxy(
[
"/_builder/api/**",
"/_builder/**/componentlibrary",
"/_builder/**/componentlibraryGenerators",
],
{
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",
"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_fp_exports = [
"union",
"reduce",
"isUndefined",
"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",
"tail", "startsWith", "findIndex", "merge",
"assign", "each", "find", "orderBy", "union"];
const lodash_exports = [
"flow",
"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 = [
"lodash", "lodash/fp", "date-fns",
"lunr", "safe-buffer", "shortid",
"@nx-js/compiler-util"
];
"lodash",
"lodash/fp",
"date-fns",
"lunr",
"safe-buffer",
"shortid",
"@nx-js/compiler-util",
]
export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: `${outputpath}/bundle.js`
},
plugins: [
copy({
targets: [
{ src: 'src/index.html', dest: outputpath },
{ src: 'src/favicon.png', dest: outputpath },
{ src: 'src/assets', dest: outputpath },
{ src: 'node_modules/@budibase/client/dist/budibase-client.esm.mjs', dest: outputpath },
]
}),
input: "src/main.js",
output: {
sourcemap: true,
format: "iife",
name: "app",
file: `${outputpath}/bundle.js`,
},
plugins: [
copy({
targets: [
{ src: "src/index.html", dest: outputpath },
{ src: "src/favicon.png", dest: outputpath },
{ src: "src/assets", dest: outputpath },
{
src: "node_modules/@budibase/client/dist/budibase-client.esm.mjs",
dest: outputpath,
},
],
}),
svelte({
// enable run-time checks when not in production
dev: !production,
include: 'src/**/*.svelte',
// we'll extract any component CSS out into
// a separate file — better for performance
css: css => {
css.write(`${outputpath}/bundle.css`);
}
}),
svelte({
// enable run-time checks when not in production
dev: !production,
include: "src/**/*.svelte",
// we'll extract any component CSS out into
// a separate file — better for performance
css: css => {
css.write(`${outputpath}/bundle.css`)
},
}),
resolve({
browser: true,
dedupe: importee => {
return importee === 'svelte'
|| importee.startsWith('svelte/')
|| coreExternal.includes(importee);
}
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"],
},
}),
url({
limit: 0,
include: ["**/*.woff2", "**/*.png"],
fileName: "[dirname][name][extname]",
emitFiles: true,
}),
url({
limit: 0,
include: ["**/*.css"],
fileName: "[name][extname]",
emitFiles: true,
}),
builtins(),
nodeglobals(),
}),
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
// browser on changes when not in production
!production && livereload(outputpath),
!production &&
browsersync({
server: outputpath,
middleware: [apiProxy, _builderProxy],
}),
// Watch the `dist` directory and refresh the
// browser on changes when not in production
!production && livereload(outputpath),
!production && browsersync({
server: outputpath,
middleware: [apiProxy, _builderProxy]
}),
// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser()
],
watch: {
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) =>
fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
},
body: body && JSON.stringify(body),
});
const apiCall = method => (url, body) =>
fetch(url, {
method: method,
headers: {
"Content-Type": "application/json",
},
body: body && JSON.stringify(body),
})
const post = apiCall("POST");
const get = apiCall("GET");
const patch = apiCall("PATCH");
const del = apiCall("DELETE");
const post = apiCall("POST")
const get = apiCall("GET")
const patch = apiCall("PATCH")
const del = apiCall("DELETE")
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) => {
let code = "";
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);
}
for (let child of props._children) {
walkProps(child)
}
}
walkProps(screen.props);
walkProps(screen.props)
return code;
return code
}
export const buildCodeForScreens = screens => {
let allfunctions = "";
for(let screen of screens) {
allfunctions += buildCodeForSingleScreen(screen);
}
return (`return ({ ${allfunctions} });`);
let allfunctions = ""
for (let screen of screens) {
allfunctions += buildCodeForSingleScreen(screen)
}
return `return ({ ${allfunctions} });`
}
const buildComponentCode = (componentProps) =>
`"${componentProps._id}" : (render, context) => {
const buildComponentCode = componentProps =>
`"${componentProps._id}" : (render, context) => {
${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) => {
packageInfo.createNewPackage("");
const root = createNewHierarchy();
store.importAppDefinition({
hierarchy:root,
actions:[],
triggers:[],
accessLevels: {version:0, levels:[]}
});
};
packageInfo.createNewPackage("")
const root = createNewHierarchy()
store.importAppDefinition({
hierarchy: root,
actions: [],
triggers: [],
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 { pipe } from "../common/core";
import { filter, map, reduce, toPairs } from "lodash/fp"
import { pipe } from "../common/core"
const self = n => n;
const join_with = delimiter => a => a.join(delimiter);
const empty_string_to_unset = s => s.length ? s : "0";
const add_suffix = suffix => s => s + suffix;
const self = n => n
const join_with = delimiter => a => a.join(delimiter)
const empty_string_to_unset = s => (s.length ? s : "0")
const add_suffix = suffix => s => s + suffix
export const make_margin = (values) => pipe(values, [
map(empty_string_to_unset),
map(add_suffix('px')),
join_with(' ')
]);
const tap = message => x => {
console.log(x);
return x;
}
export const make_margin = values =>
pipe(values, [
map(empty_string_to_unset),
map(add_suffix("px")),
join_with(" "),
])
const css_map = {
templaterows: {
name: 'grid-template-columns',
generate: self
},
templatecolumns: {
name: 'grid-template-rows',
generate: self
},
gridarea: {
name: 'grid-area',
generate: make_margin
},
gap: {
name: 'grid-gap',
generate: n => `${n}px`
},
columnstart: {
name: 'grid-column-start',
generate: self
},
columnend: {
name: 'grid-column-end',
generate: self
},
rowstart: {
name: 'grid-row-start',
generate: self
},
rowend: {
name: 'grid-row-end',
generate: self
},
padding: {
name: 'padding',
generate: make_margin
},
margin: {
name: 'margin',
generate: make_margin
},
zindex: {
name: 'z-index',
generate: self
}
templaterows: {
name: "grid-template-columns",
generate: self,
},
templatecolumns: {
name: "grid-template-rows",
generate: self,
},
gridarea: {
name: "grid-area",
generate: make_margin,
},
gap: {
name: "grid-gap",
generate: n => `${n}px`,
},
columnstart: {
name: "grid-column-start",
generate: self,
},
columnend: {
name: "grid-column-end",
generate: self,
},
rowstart: {
name: "grid-row-start",
generate: self,
},
rowend: {
name: "grid-row-end",
generate: self,
},
padding: {
name: "padding",
generate: make_margin,
},
margin: {
name: "margin",
generate: make_margin,
},
zindex: {
name: "z-index",
generate: self,
},
}
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]) => {
let tmp = [];
let tmp = []
if (name === 'row' || name === 'column') {
if (value[0]) tmp.push([`${name}start`, value[0]]);
if (value[1]) tmp.push([`${name}end`, value[1]]);
return acc.concat(tmp)
}
if (name === "row" || name === "column") {
if (value[0]) tmp.push([`${name}start`, value[0]])
if (value[1]) tmp.push([`${name}end`, value[1]])
return acc.concat(tmp)
}
return acc.concat([[name, value]]);
return acc.concat([[name, value]])
}
const object_to_css_string = [
toPairs,
reduce(handle_grid, []),
filter(v => Array.isArray(v[1]) ? v[1].some(s => s.length) : v[1].length),
map(generate_rule),
join_with('\n'),
];
toPairs,
reduce(handle_grid, []),
filter(v => (Array.isArray(v[1]) ? v[1].some(s => s.length) : v[1].length)),
map(generate_rule),
join_with("\n"),
]
export const generate_css = ({ layout, position }) => {
let _layout = pipe(layout, object_to_css_string);
_layout = _layout.length ? _layout + "\ndisplay: grid;" : _layout;
let _layout = pipe(layout, object_to_css_string)
_layout = _layout.length ? _layout + "\ndisplay: grid;" : _layout
return {
layout: _layout,
position: pipe(position, object_to_css_string)
}
return {
layout: _layout,
position: pipe(position, object_to_css_string),
}
}
const apply_class = (id, name, styles) => `.${name}-${id} {\n${styles}\n}`;
export const generate_screen_css = (component_array) => {
let styles = "";
const apply_class = (id, name, styles) => `.${name}-${id} {\n${styles}\n}`
for (let i = 0; i < component_array.length; i += 1) {
const { _styles, _id, _children } = component_array[i];
const { layout, position } = generate_css(_styles);
export const generate_screen_css = component_array => {
let styles = ""
styles += apply_class(_id, 'pos', position) + "\n";
styles += apply_class(_id, 'lay', layout) + "\n";
for (let i = 0; i < component_array.length; i += 1) {
const { _styles, _id, _children } = component_array[i]
const { layout, position } = generate_css(_styles)
if (_children && _children.length) {
styles += generate_screen_css(_children) + "\n";
}
styles += apply_class(_id, "pos", position) + "\n"
styles += apply_class(_id, "lay", layout) + "\n"
}
return styles.trim();
if (_children && _children.length) {
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 getStore from "./store";
import { createPackage } from "./createPackage"
import getStore from "./store"
export const store = getStore();
export const store = getStore()
export const createNewPackage = () =>
createPackage(packageInfo, store);
export const createNewPackage = () => createPackage(packageInfo, store)
export const initialise = async () => {
try {
setupRouter(store);
await store.initialise();
} catch(err) {
console.log(err);
}
}
try {
setupRouter(store)
await store.initialise()
} catch (err) {
console.log(err)
}
}
const setupRouter = (writable) => {
const pushState = history.pushState;
history.pushState = () => {
pushState.apply(history, arguments);
//fireEvents('pushState', arguments);
writable.initialise();
}
window.addEventListener('hashchange',()=>{
writable.initialise();
})
const setupRouter = writable => {
const pushState = history.pushState
history.pushState = () => {
pushState.apply(history, arguments)
//fireEvents('pushState', arguments);
writable.initialise()
}
window.addEventListener("hashchange", () => {
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) => {
const allLibraries = {}
for (let lib of appPackage.pages.componentLibraries) {
const libModule = await import(makeLibraryUrl(appName, lib))
allLibraries[lib] = libModule
}
const allLibraries = {};
for(let lib of appPackage.pages.componentLibraries) {
const libModule = await import(makeLibraryUrl(appName, lib));
allLibraries[lib] = libModule;
}
return allLibraries;
return allLibraries
}
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 = {};
for(let lib of appPackage.pages.componentLibraries) {
const generatorModule = await import(makeGeneratorLibraryUrl(appName, lib));
allGeneratorLibs[lib] = generatorModule;
}
return allGeneratorLibs;
return allGeneratorLibs
}
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 = [];
for(let lib of appPackage.pages.componentLibraries) {
const libUrl = makeLibraryUrl(appName, lib);
allLibraries.push({libName:lib, importPath:libUrl});
}
return allLibraries;
return allLibraries
}
export const loadLib = async (appName, lib, allLibs) => {
allLibs[lib] = await import(makeLibraryUrl(appName, lib));
return allLibs;
allLibs[lib] = await import(makeLibraryUrl(appName, lib))
return allLibs
}
export const loadGeneratorLib = async (appName, lib, allGeneratorLibs) => {
allGeneratorLibs[lib] = await import(makeGeneratorLibraryUrl(appName, lib));
return allGeneratorLibs;
allGeneratorLibs[lib] = await import(makeGeneratorLibraryUrl(appName, lib))
return allGeneratorLibs
}
export const makeLibraryUrl = (appName, lib) =>
`/_builder/${appName}/componentlibrary?lib=${encodeURI(lib)}`
export const makeLibraryUrl = (appName, lib) =>
`/_builder/${appName}/componentlibrary?lib=${encodeURI(lib)}`
export const makeGeneratorLibraryUrl = (appName, lib) =>
`/_builder/${appName}/componentlibraryGenerators?lib=${encodeURI(lib)}`
export const makeGeneratorLibraryUrl = (appName, 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() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0,
v = c == 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => {
const r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8
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 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';
export { default as CircleIndicator } from './CircleIndicator.svelte';
export { default as PencilIcon } from './Pencil.svelte';
export { default as EventsIcon } from './Events.svelte';
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"
export { default as CircleIndicator } from "./CircleIndicator.svelte"
export { default as PencilIcon } from "./Pencil.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 {
isString
} from "lodash/fp";
import { isString } from "lodash/fp"
import {
BB_STATE_BINDINGPATH, BB_STATE_FALLBACK,
BB_STATE_BINDINGSOURCE
} from "@budibase/client/src/state/isState";
BB_STATE_BINDINGPATH,
BB_STATE_FALLBACK,
BB_STATE_BINDINGSOURCE,
} from "@budibase/client/src/state/isState"
export const isBinding = value =>
!isString(value)
&& value
&& isString(value[BB_STATE_BINDINGPATH])
&& value[BB_STATE_BINDINGPATH].length > 0;
export const isBinding = value =>
!isString(value) &&
value &&
isString(value[BB_STATE_BINDINGPATH]) &&
value[BB_STATE_BINDINGPATH].length > 0
export const setBinding = ({path, fallback, source}, binding={} ) => {
if(isNonEmptyString(path)) binding[BB_STATE_BINDINGPATH] = path;
if(isNonEmptyString(fallback)) binding[BB_STATE_FALLBACK] = fallback;
binding[BB_STATE_BINDINGSOURCE] = source || "store";
return binding
export const setBinding = ({ path, fallback, source }, binding = {}) => {
if (isNonEmptyString(path)) binding[BB_STATE_BINDINGPATH] = path
if (isNonEmptyString(fallback)) binding[BB_STATE_FALLBACK] = fallback
binding[BB_STATE_BINDINGSOURCE] = source || "store"
return binding
}
export const getBinding = binding => ({
path: binding[BB_STATE_BINDINGPATH] || "",
fallback: binding[BB_STATE_FALLBACK] || "",
source: binding[BB_STATE_BINDINGSOURCE] || "store"
});
path: binding[BB_STATE_BINDINGPATH] || "",
fallback: binding[BB_STATE_FALLBACK] || "",
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 {
generateSchema
} from "../../../core/src/indexing/indexSchemaCreator";
hierarchy as hierarchyFunctions,
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) =>
pipe(hierarchy, [
hierarchyFunctions.getFlattenedHierarchy,
find(n => n.nodeId === nodeId || n.nodeKey() === nodeId)
]);
export const getNode = (hierarchy, nodeId) =>
pipe(hierarchy, [
hierarchyFunctions.getFlattenedHierarchy,
find(n => n.nodeId === nodeId || n.nodeKey() === nodeId),
])
export const constructHierarchy = node => {
if(!node) return node;
return templateApi(node).constructHierarchy(node);
if (!node) return node
return templateApi(node).constructHierarchy(node)
}
export const createNewHierarchy = () => {
return templateApi().getNewRootLevel();
return templateApi().getNewRootLevel()
}
export const templateApi = hierarchy => getTemplateApi({hierarchy})
export const authApi = (hierarchy, actions) => getAuthApi({
hierarchy, actions: keyBy("name")(actions), publish:()=>{}})
export const templateApi = hierarchy => getTemplateApi({ hierarchy })
export const authApi = (hierarchy, actions) =>
getAuthApi({
hierarchy,
actions: keyBy("name")(actions),
publish: () => {},
})
export const allTypes = templateApi({}).allTypes;
export const allTypes = templateApi({}).allTypes
export const validate = {
all: templateApi({}).validateAll,
node: templateApi({}).validateNode,
field: templateApi({}).validateField
};
all: templateApi({}).validateAll,
node: templateApi({}).validateNode,
field: templateApi({}).validateField,
}
export const getPotentialReverseReferenceIndexes = (hierarchy, refIndex) => {
const res = pipe(hierarchy, [
hierarchyFunctions.getFlattenedHierarchy,
filter(n => hierarchyFunctions.isAncestor(refIndex)(n)
|| hierarchyFunctions.isAncestor(refIndex)(n.parent())),
map(n => n.indexes),
flatten,
filter(hierarchyFunctions.isReferenceIndex)
]);
return res;
const res = pipe(hierarchy, [
hierarchyFunctions.getFlattenedHierarchy,
filter(
n =>
hierarchyFunctions.isAncestor(refIndex)(n) ||
hierarchyFunctions.isAncestor(refIndex)(n.parent())
),
map(n => n.indexes),
flatten,
filter(hierarchyFunctions.isReferenceIndex),
])
return res
}
export const getPotentialReferenceIndexes = (hierarchy, record) =>
pipe(hierarchy, [
hierarchyFunctions.getFlattenedHierarchy,
filter(hierarchyFunctions.isAncestorIndex),
filter(i => 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 getNewTrigger = () => templateApi({}).createTrigger();
export const validateActions = actions => templateApi({}).validateActions(actions);
export const validateTriggers = (triggers, actions) => templateApi({}).validateTriggers(triggers, actions);
export const generateFullPermissions = (hierarchy, actions) =>
authApi(hierarchy,actions).generateFullPermissions();
export const getNewAccessLevel = () =>
authApi().getNewAccessLevel();
export const validateAccessLevels = (hierarchy, actions, accessLevels) =>
authApi(hierarchy, actions).validateAccessLevels(accessLevels);
export const getIndexNodes = (hierarchy) =>
pipe(hierarchy, [
hierarchyFunctions.getFlattenedHierarchy,
filter(hierarchyFunctions.isIndex)
]);
export const getRecordNodes = (hierarchy) =>
pipe(hierarchy, [
hierarchyFunctions.getFlattenedHierarchy,
filter(hierarchyFunctions.isRecord)
]);
export const getIndexSchema = hierarchy => index =>
generateSchema(hierarchy, index);
pipe(hierarchy, [
hierarchyFunctions.getFlattenedHierarchy,
filter(hierarchyFunctions.isAncestorIndex),
filter(
i =>
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 getNewTrigger = () => templateApi({}).createTrigger()
export const validateActions = actions =>
templateApi({}).validateActions(actions)
export const validateTriggers = (triggers, actions) =>
templateApi({}).validateTriggers(triggers, actions)
export const generateFullPermissions = (hierarchy, actions) =>
authApi(hierarchy, actions).generateFullPermissions()
export const getNewAccessLevel = () => authApi().getNewAccessLevel()
export const validateAccessLevels = (hierarchy, actions, accessLevels) =>
authApi(hierarchy, actions).validateAccessLevels(accessLevels)
export const getIndexNodes = hierarchy =>
pipe(hierarchy, [
hierarchyFunctions.getFlattenedHierarchy,
filter(hierarchyFunctions.isIndex),
])
export const getRecordNodes = hierarchy =>
pipe(hierarchy, [
hierarchyFunctions.getFlattenedHierarchy,
filter(hierarchyFunctions.isRecord),
])
export const getIndexSchema = hierarchy => index =>
generateSchema(hierarchy, index)

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

@ -1,26 +1,21 @@
import {
eventHandlers
} from "../../../client/src/state/eventHandlers";
import {writable} from "svelte/store";
export {
EVENT_TYPE_MEMBER_NAME
} from "../../../client/src/state/eventHandlers";
import {
createCoreApi
} from "../../../client/src/core";
import { eventHandlers } from "../../../client/src/state/eventHandlers"
import { writable } from "svelte/store"
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);
appDefinition.hierarchy = coreApi.templateApi.constructHierarchy(appDefinition.hierarchy);
const store = writable({
_bbuser: user
});
const handlersObj = eventHandlers(store, coreApi);
const handlersArray = [];
for(let key in handlersObj) {
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";
const getIcon = (icon, size) => feather.icons[icon].toSvg({height:size||"16", width:size||"16"});
export default getIcon;
import feather from "feather-icons"
const getIcon = (icon, size) =>
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 "./global.css";
import "./fonts.css";
import "/assets/roboto-v20-latin-ext_latin-300";
import "/assets/roboto-v20-latin-ext_latin-400";
import "/assets/roboto-v20-latin-ext_latin-500";
import "/assets/roboto-v20-latin-ext_latin-700";
import "/assets/roboto-v20-latin-ext_latin-900";
import "/assets/budibase-logo.png";
import "/assets/budibase-logo-only.png";
import "uikit/dist/css/uikit.min.css";
import "uikit/dist/js/uikit.min.js";
import "codemirror/lib/codemirror.css";
import 'codemirror/theme/monokai.css';
import App from "./App.svelte"
import "./global.css"
import "./fonts.css"
import "/assets/roboto-v20-latin-ext_latin-300"
import "/assets/roboto-v20-latin-ext_latin-400"
import "/assets/roboto-v20-latin-ext_latin-500"
import "/assets/roboto-v20-latin-ext_latin-700"
import "/assets/roboto-v20-latin-ext_latin-900"
import "/assets/budibase-logo.png"
import "/assets/budibase-logo-only.png"
import "uikit/dist/css/uikit.min.css"
import "uikit/dist/js/uikit.min.js"
import "codemirror/lib/codemirror.css"
import "codemirror/theme/monokai.css"
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 {
getComponentInfo, createProps, getInstanceProps
} from "./createProps";
import { getComponentInfo, createProps, getInstanceProps } from "./createProps"
export const buildPropsHierarchy = (components, screens, baseComponent) => {
const allComponents = [...components, ...screens];
const buildProps = (componentDefinition, derivedFromProps) => {
const {props} = createProps(componentDefinition, derivedFromProps);
const propsDefinition = componentDefinition.props;
props._component = componentDefinition.name;
for(let propName in props) {
if(propName === "_component") continue;
const propDef = propsDefinition[propName];
if(!propDef) continue;
if(propName === "_children") {
const childrenProps = props[propName];
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));
}
}
const allComponents = [...components, ...screens]
const buildProps = (componentDefinition, derivedFromProps) => {
const { props } = createProps(componentDefinition, derivedFromProps)
const propsDefinition = componentDefinition.props
props._component = componentDefinition.name
for (let propName in props) {
if (propName === "_component") continue
const propDef = propsDefinition[propName]
if (!propDef) continue
if (propName === "_children") {
const childrenProps = props[propName]
if (!childrenProps || childrenProps.length === 0) {
continue
}
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(
baseComponentInfo.rootComponent,
baseComponentInfo.fullProps);
const baseComponentInfo = getComponentInfo(allComponents, baseComponent)
}
return buildProps(
baseComponentInfo.rootComponent,
baseComponentInfo.fullProps
)
}

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

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

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

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

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

@ -1,89 +1,80 @@
import { splitName } from "./splitRootComponentName";
import {
find, filter, cloneDeep, isPlainObject,
isArray
} from "lodash/fp";
import { isRootComponent } from "./searchComponents";
import { splitName } from "./splitRootComponentName"
import { find, filter, cloneDeep, isPlainObject, isArray } from "lodash/fp"
import { isRootComponent } from "./searchComponents"
export const libraryDependencies = (components, lib) => {
const componentDependsOnLibrary = comp => {
if(isRootComponent(comp)) {
const {libName} = splitName(component.name);
return (libName === lib);
}
return componentDependsOnLibrary(
find(c => c.name === comp.props._component)(
components)
);
const componentDependsOnLibrary = comp => {
if (isRootComponent(comp)) {
const { libName } = splitName(component.name)
return libName === lib
}
return filter(c => !isRootComponent(c)
&& componentDependsOnLibrary(c))(
components
);
return componentDependsOnLibrary(
find(c => c.name === comp.props._component)(components)
)
}
return filter(c => !isRootComponent(c) && componentDependsOnLibrary(c))(
components
)
}
export const componentDependencies = (pages, screens, components, dependsOn) => {
const allComponents = [
...cloneDeep(components),
...cloneDeep(screens)];
pages = cloneDeep(pages);
const dependantComponents = [];
const dependantPages = [];
const traverseProps = (props) => {
if(props._component && props._component === dependsOn.name) {
return true;
}
export const componentDependencies = (
pages,
screens,
components,
dependsOn
) => {
const allComponents = [...cloneDeep(components), ...cloneDeep(screens)]
pages = cloneDeep(pages)
const dependantComponents = []
const dependantPages = []
const traverseProps = props => {
if (props._component && props._component === dependsOn.name) {
return true
}
for(let propName in props) {
const prop = props[propName];
if(isPlainObject(prop) && prop._component) {
if(traverseProps(prop)) return true;
}
if(isArray(prop)) {
for(let element of prop) {
if(traverseProps(element)) return true;
}
}
for (let propName in props) {
const prop = props[propName]
if (isPlainObject(prop) && prop._component) {
if (traverseProps(prop)) return true
}
if (isArray(prop)) {
for (let element of prop) {
if (traverseProps(element)) return true
}
return false;
}
}
return false
}
for(let component of allComponents) {
if(isRootComponent(component)) {
continue;
}
if(component.name === dependsOn.name) {
continue;
}
for (let component of allComponents) {
if (isRootComponent(component)) {
continue
}
if(component.props._component === dependsOn.name) {
dependantComponents.push(component);
continue;
}
if(traverseProps(component.props)) {
dependantComponents.push(component);
}
if (component.name === dependsOn.name) {
continue
}
if (component.props._component === dependsOn.name) {
dependantComponents.push(component)
continue
}
for(let pageName in pages) {
const page = pages[pageName];
if(page.appBody === dependsOn.name) {
dependantPages.push(pageName);
}
if (traverseProps(component.props)) {
dependantComponents.push(component)
}
}
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 { find } from "lodash/fp";
import { isRootComponent } from "./searchComponents"
import { find } from "lodash/fp"
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 {
isPlainObject, isArray, cloneDeep
} from "lodash/fp";
import {
getExactComponent
} from "./searchComponents";
import { isPlainObject, isArray, cloneDeep } from "lodash/fp"
import { getExactComponent } from "./searchComponents"
export const rename = (pages, screens, oldname, newname) => {
pages = cloneDeep(pages)
screens = cloneDeep(screens)
const changedScreens = []
pages = cloneDeep(pages);
screens = cloneDeep(screens);
const changedScreens = [];
const existingWithNewName = getExactComponent(screens, newname);
if(existingWithNewName) return {
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 existingWithNewName = getExactComponent(screens, newname)
if (existingWithNewName)
return {
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 screen of screens) {
let hasEdited = false;
if(screen.name === oldname) {
screen.name = 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
}
if(screen.props._component === oldname) {
screen.props._component = newname;
hasEdited = true;
}
hasEdited = traverseProps(screen.props) || hasEdited;
for (let screen of screens) {
let hasEdited = false
if(hasEdited && screen.name !== newname)
changedScreens.push(screen.name);
if (screen.name === oldname) {
screen.name = newname
hasEdited = true
}
for(let pageName in pages) {
const page = pages[pageName];
if(page.appBody === oldname) {
page.appBody = newname;
}
if (screen.props._component === oldname) {
screen.props._component = newname
hasEdited = true
}
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 {
find,
isUndefined,
filter,
some,
includes,
has
} from "lodash/fp";
import { 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 =>
isComponent(c) && isUndefined(c.props._component);
export const isRootComponent = c =>
isComponent(c) && isUndefined(c.props._component)
export const isComponent = c => {
const hasProp = (n) => !isUndefined(c[n]);
return hasProp("name") && hasProp("props");
const hasProp = n => !isUndefined(c[n])
return hasProp("name") && hasProp("props")
}
export const searchAllComponents = (components, phrase) => {
const hasPhrase = (...vals) =>
pipe(vals, [some(v => includes(normalString(phrase))(normalString(v)))])
const hasPhrase = (...vals) =>
pipe(vals, [
some(v => includes(normalString(phrase))(normalString(v)))
]);
const componentMatches = c => {
if (hasPhrase(c.name, ...(c.tags || []))) return true
const componentMatches = c => {
if(hasPhrase(c.name, ...(c.tags || []))) return true;
if (isRootComponent(c)) return false
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) => {
const stringEquals = (s1, s2) =>
normalString(s1) === normalString(s2);
return pipe(components,[
find(c => stringEquals(c.name, name))
]);
}
const stringEquals = (s1, s2) => normalString(s1) === normalString(s2)
export const getAncestorProps = (components, name, found=[]) => {
const thisComponent = getExactComponent(
components, name);
return pipe(components, [find(c => stringEquals(c.name, name))])
}
if(isRootComponent(thisComponent))
return [thisComponent.props, ...found];
export const getAncestorProps = (components, name, found = []) => {
const thisComponent = getExactComponent(components, name)
return getAncestorProps(
components,
thisComponent.props._component,
[{...thisComponent.props},
...found]);
if (isRootComponent(thisComponent)) return [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 {
split,
last
} from "lodash/fp";
import { split, last } from "lodash/fp"
import { pipe } from "../../common/core";
import { pipe } from "../../common/core"
export const splitName = fullname => {
const componentName = pipe(fullname, [
split("/"),
last
]);
const componentName = pipe(fullname, [split("/"), last])
const libName =fullname.substring(
0, fullname.length - componentName.length - 1);
const libName = fullname.substring(
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 {
isString,
isBoolean,
isNumber,
isArray,
isObjectLike,
isPlainObject,
every,
isUndefined
} from "lodash/fp";
isString,
isBoolean,
isNumber,
isArray,
isObjectLike,
isPlainObject,
every,
isUndefined,
} from "lodash/fp"
import {
EVENT_TYPE_MEMBER_NAME
} from "../../common/eventHandlers";
import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers"
import {
isBound, BB_STATE_BINDINGPATH
} from "@budibase/client/src/state/isState";
isBound,
BB_STATE_BINDINGPATH,
} from "@budibase/client/src/state/isState"
const defaultDef = typeName => () => ({
type: typeName,
required:false,
default:types[typeName].default(),
options: typeName === "options" ? [] : undefined
});
type: typeName,
required: false,
default: types[typeName].default(),
options: typeName === "options" ? [] : undefined,
})
const propType = (defaultValue, isOfType, defaultDefinition) => ({
isOfType, default:defaultValue, defaultDefinition
});
isOfType,
default: defaultValue,
defaultDefinition,
})
const expandSingleProp = propDef => {
const p = isString(propDef)
? types[propDef].defaultDefinition()
: propDef;
if(!isString(propDef)) {
const def = types[propDef.type].defaultDefinition();
for(let p in def) {
if(propDef[p] === undefined) {
propDef[p] = def[p];
}
}
const p = isString(propDef) ? types[propDef].defaultDefinition() : propDef
if (!isString(propDef)) {
const def = types[propDef.type].defaultDefinition()
for (let p in def) {
if (propDef[p] === undefined) {
propDef[p] = def[p]
}
}
}
return p;
return p
}
export const expandComponentDefinition = componentDefinition => {
const expandedProps = {};
const expandedComponent = {...componentDefinition};
const expandedProps = {}
const expandedComponent = { ...componentDefinition }
for(let p in componentDefinition.props) {
expandedProps[p] = expandSingleProp(
componentDefinition.props[p]);
}
for (let p in componentDefinition.props) {
expandedProps[p] = expandSingleProp(componentDefinition.props[p])
}
expandedComponent.props = expandedProps;
expandedComponent.props = expandedProps
if(expandedComponent.children !== false) {
expandedComponent.children = true;
}
if (expandedComponent.children !== false) {
expandedComponent.children = true
}
return expandedComponent;
return expandedComponent
}
const isComponent = isObjectLike;
const isEvent = e =>
isPlainObject(e)
&& isString(e[EVENT_TYPE_MEMBER_NAME])
&& isPlainObject(e.parameters);
const isComponent = isObjectLike
const isEvent = e =>
isPlainObject(e) &&
isString(e[EVENT_TYPE_MEMBER_NAME]) &&
isPlainObject(e.parameters)
const isEventList = e =>
isArray(e) && every(isEvent)(e);
const isEventList = e => isArray(e) && every(isEvent)(e)
const emptyState = () => {
const s = {};
s[BB_STATE_BINDINGPATH] = "";
return s;
const s = {}
s[BB_STATE_BINDINGPATH] = ""
return s
}
export const types = {
string: propType(() => "", isString, defaultDef("string")),
bool: propType(() => false, isBoolean, defaultDef("bool")),
number: propType(() => 0, isNumber, defaultDef("number")),
options: propType(() => "", isString, defaultDef("options")),
asset: propType(() => "", isString, defaultDef("asset")),
event: propType(() => [], isEventList, defaultDef("event")),
state: propType(() => emptyState(), isBound, defaultDef("state"))
};
string: propType(() => "", isString, defaultDef("string")),
bool: propType(() => false, isBoolean, defaultDef("bool")),
number: propType(() => 0, isNumber, defaultDef("number")),
options: propType(() => "", isString, defaultDef("options")),
asset: propType(() => "", isString, defaultDef("asset")),
event: propType(() => [], isEventList, defaultDef("event")),
state: propType(() => emptyState(), isBound, defaultDef("state")),
}

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

@ -1,30 +1,25 @@
import {
isString,
keys,
flatten,
isArray,
map,
filter
} from "lodash/fp";
import { common } from "../../../../core/src";
const pipe = common.$;
import { isString, keys, flatten, isArray, map, filter } from "lodash/fp"
import { common } from "../../../../core/src"
const pipe = common.$
export const validatePage = (page, getComponent) => {
const errors = [];
const error = message => errors.push(message);
const errors = []
const error = message => errors.push(message)
const noIndex = !page.index;
if (noIndex) {
error("Page does not define an index member");
}
const noIndex = !page.index
if (noIndex) {
error("Page does not define an index member")
}
if (!page.appBody
|| !isString(page.appBody)
|| !page.appBody.endsWith(".json")) {
error("App body must be set toa valid JSON file");
}
if (
!page.appBody ||
!isString(page.appBody) ||
!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
for pageLayout props (which is just a pipe dream at time of writing)
const indexHtmlErrors = noIndex
@ -35,35 +30,35 @@ export const validatePage = (page, getComponent) => {
]);
*/
return errors;
return errors
}
export const validatePages = (pages, getComponent) => {
let errors = []
const error = message => errors.push(message)
let errors = [];
const error = message => errors.push(message);
if (!pages.main) {
error("must have a 'main' page");
}
if (!pages.main) {
error("must have a 'main' page")
}
if (!pages.unauthenticated) {
error("must have a 'unauthenticated' (login) page");
}
if (!pages.unauthenticated) {
error("must have a 'unauthenticated' (login) page")
}
if (!pages.componentLibraries
|| !isArray(pages.componentLibraries)
|| pages.componentLibraries.length === 0) {
if (
!pages.componentLibraries ||
!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"),
map(k => validatePage(pages[k], getComponent)),
flatten
]);
const pageErrors = pipe(pages, [
keys,
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";
describe("buildCodeForScreen",() => {
import { buildCodeForScreens } from "../src/builderStore/buildCodeForScreens"
describe("buildCodeForScreen", () => {
it("should package _code into runnable function, for simple screen props", () => {
const screen = {
props: {
_id: "1234",
_code: "render('render argument');"
}
_code: "render('render argument');",
},
}
let renderArg;
const render = (arg) => {
renderArg = arg;
let renderArg
const render = arg => {
renderArg = arg
}
const uiFunctions = getFunctions(screen);
const targetfunction = uiFunctions[screen.props._id];
expect(targetfunction).toBeDefined();
const uiFunctions = getFunctions(screen)
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 ", () => {
const screen = {
@ -31,34 +29,31 @@ describe("buildCodeForScreen",() => {
_id: "parent",
_code: "render('parent argument');",
_children: [
{
_id: "child1",
_code: "render('child 1 argument');"
},
{
_id: "child2",
_code: "render('child 2 argument');"
}
]
}
{
_id: "child1",
_code: "render('child 1 argument');",
},
{
_id: "child2",
_code: "render('child 2 argument');",
},
],
},
}
let renderArg;
const render = (arg) => {
renderArg = arg;
let renderArg
const render = arg => {
renderArg = arg
}
const uiFunctions = getFunctions(screen);
const targetfunction = uiFunctions["child2"];
expect(targetfunction).toBeDefined();
const uiFunctions = getFunctions(screen)
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 {
find
} from "lodash/fp";
import { buildPropsHierarchy } from "../src/userInterface/pagesParsing/buildPropsHierarchy";
import { componentsAndScreens } from "./testData"
import { find } from "lodash/fp"
import { buildPropsHierarchy } from "../src/userInterface/pagesParsing/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(
components, screens, "ButtonGroup");
const button1 = primaryButtonProps()
button1.contentText = "Button 1"
expect(allprops._children[0]).toEqual(button1)
expect(allprops._component).toEqual("budibase-components/div");
const primaryButtonProps = () => ({
_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)
});
});
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 {
componentDependencies
} from "../src/userInterface/pagesParsing/findDependencies";
import { componentsAndScreens } from "./testData";
import { componentDependencies } from "../src/userInterface/pagesParsing/findDependencies"
import { componentsAndScreens } from "./testData"
import { some, find } from "lodash/fp"
describe("component dependencies", () => {
const contains = (result, name) =>
some(c => c.name === name)(result);
const get = (all, name) =>
find(c => c.name === name)(all);
it("should include component that inheirts", () => {
const {components, screens} = componentsAndScreens();
const result = componentDependencies(
{}, screens, components,
get([...components, ...screens], "budibase-components/TextBox"));
expect(contains(result.dependantComponents, "common/SmallTextbox")).toBe(true);
});
it("should include component that nests", () => {
const {components, screens} = componentsAndScreens();
const result = componentDependencies(
{}, screens, components,
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 = {
main: {
appBody: "PrimaryButton"
}
};
const result = componentDependencies(
pages, screens, components,
get([...components, ...screens], "PrimaryButton"));
expect(result.dependantPages).toEqual(["main"]);
});
})
const contains = (result, name) => some(c => c.name === name)(result)
const get = (all, name) => find(c => c.name === name)(all)
it("should include component that inheirts", () => {
const { components, screens } = componentsAndScreens()
const result = componentDependencies(
{},
screens,
components,
get([...components, ...screens], "budibase-components/TextBox")
)
expect(contains(result.dependantComponents, "common/SmallTextbox")).toBe(
true
)
})
it("should include component that nests", () => {
const { components, screens } = componentsAndScreens()
const result = componentDependencies(
{},
screens,
components,
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 = {
main: {
appBody: "PrimaryButton",
},
}
const result = componentDependencies(
pages,
screens,
components,
get([...components, ...screens], "PrimaryButton")
)
expect(result.dependantPages).toEqual(["main"])
})
})

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

@ -1,232 +1,212 @@
import {
createProps,
} from "../src/userInterface/pagesParsing/createProps";
import {
keys, some
} from "lodash/fp";
import {
BB_STATE_BINDINGPATH
} from "@budibase/client/src/state/isState";
import { createProps } from "../src/userInterface/pagesParsing/createProps"
import { keys, some } from "lodash/fp"
import { BB_STATE_BINDINGPATH } from "@budibase/client/src/state/isState"
describe("createDefaultProps", () => {
const getcomponent = () => ({
name: "some_component",
props: {
fieldName: { type: "string", default: "something" },
},
})
const getcomponent = () => ({
name:"some_component",
props: {
fieldName: {type:"string", default:"something"}
}
});
it("should create a object with single string value, when default string field set", () => {
const { props, errors } = createProps(getcomponent())
it("should create a object with single string value, when default string field set", () => {
const { props, errors } = createProps(getcomponent());
expect(errors).toEqual([])
expect(props.fieldName).toBeDefined()
expect(props.fieldName).toBe("something")
expect(keys(props).length).toBe(3)
})
expect(errors).toEqual([]);
expect(props.fieldName).toBeDefined();
expect(props.fieldName).toBe("something");
expect(keys(props).length).toBe(3);
});
it("should set component name", () => {
const { props, errors } = createProps(getcomponent())
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([]);
expect(props._component).toBe("some_component");
});
const { errors } = createProps(comp)
it("should return error when component name not supplied", () => {
const comp = getcomponent();
comp.name = "";
expect(errors.length).toEqual(1)
})
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", () => {
const comp = getcomponent();
comp.props.fieldName = {type:"string"};
expect(errors).toEqual([])
expect(props.fieldName).toBeDefined()
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([]);
expect(props.fieldName).toBeDefined();
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([])
expect(props.fieldName).toBeDefined()
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([]);
expect(props.fieldName).toBeDefined();
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([])
expect(props.isVisible).toBeDefined()
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([]);
expect(props.isVisible).toBeDefined();
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([])
expect(props.width).toBeDefined()
expect(props.width).toBe(0)
})
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([]);
expect(props.width).toBeDefined();
expect(props.width).toBe(0);
});
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([])
expect(props._children).toBeDefined()
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([]);
expect(props._children).toBeDefined();
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([])
expect(props._children).not.toBeDefined()
})
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([]);
expect(props._children).not.toBeDefined();
});
const { props, errors } = createProps(comp)
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();
comp.props.onClick = "event";
it("should create a object with empty state when prop def is 'state' ", () => {
const comp = getcomponent()
comp.props.data = "state"
const { props, errors } = createProps(comp);
const { props, errors } = createProps(comp)
expect(errors).toEqual([]);
expect(props.onClick).toBeDefined();
expect(props.onClick).toEqual([]);
});
expect(errors).toEqual([])
expect(props.data[BB_STATE_BINDINGPATH]).toBeDefined()
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();
comp.props.data = "state";
const { props, errors } = createProps(comp)
const { props, errors } = createProps(comp);
expect(errors).toEqual([])
expect(props._children).toBeDefined()
expect(props._children).toEqual([])
})
expect(errors).toEqual([]);
expect(props.data[BB_STATE_BINDINGPATH]).toBeDefined();
expect(props.data[BB_STATE_BINDINGPATH]).toBe("");
});
it("should create a _children array when children not defined ", () => {
const comp = getcomponent()
it("should create a object children array when children == true ", () => {
const { props, errors } = createProps(comp)
const comp = getcomponent();
comp.children = true;
expect(errors).toEqual([])
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([]);
expect(props._children).toBeDefined();
expect(props._children).toEqual([]);
});
const { props, errors } = createProps(comp)
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(props._children).toBeDefined();
expect(props._children).toEqual([]);
});
expect(errors).toEqual([])
expect(props.fieldName).toBeDefined()
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();
comp.children = false;
const { errors } = createProps(comp)
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([]);
expect(props._children).not.toBeDefined();
});
it("should return error default value is not of declared type", () => {
const comp = getcomponent()
comp.props.fieldName = { type: "string", default: 1 }
it("should create an object with multiple prop names", () => {
const comp = getcomponent();
comp.props.fieldName = "string";
comp.props.fieldLength = { type: "number", default: 500 };
const { errors } = createProps(comp)
const { props, errors } = createProps(comp);
expect(errors.length).toBe(1)
expect(some(e => e.propName === "fieldName")(errors)).toBeTruthy()
})
expect(errors).toEqual([]);
expect(props.fieldName).toBeDefined();
expect(props.fieldName).toBe("");
expect(props.fieldLength).toBeDefined();
expect(props.fieldLength).toBe(500);
})
it("should merge in derived props", () => {
const propDef = {
fieldName: "string",
fieldLength: { type: "number", default: 500 },
}
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()
comp.props.fieldName = "string"
comp.props.fieldLength = { type: "number", default: 500 }
const { errors } = createProps(comp);
const derivedFrom = {
fieldName: "surname",
}
expect(errors.length).toBe(2);
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);
});
const { props, errors } = createProps(comp, derivedFrom)
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 = () => ({
name: "comp",
props: {
label: "string",
width: {type:"number"},
color: {type:"string", required:true},
}
name: "comp",
props: {
label: "string",
width: { type: "number" },
color: { type: "string", required: true },
},
})
describe("expandPropDefintion", () => {
it("should expand property defined as string, into default for that type", () => {
const result = expandComponentDefinition(componentDef());
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());
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 set children=true when not included", () => {
const result = expandComponentDefinition(componentDef());
expect(result.children).toBe(true);
});
it("should not change children when specified", () => {
const c = componentDef();
c.children = false;
const result = expandComponentDefinition(c);
expect(result.children).toBe(false);
c.children = true;
const result2 = expandComponentDefinition(c);
expect(result2.children).toBe(true);
});
})
it("should expand property defined as string, into default for that type", () => {
const result = expandComponentDefinition(componentDef())
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())
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 set children=true when not included", () => {
const result = expandComponentDefinition(componentDef())
expect(result.children).toBe(true)
})
it("should not change children when specified", () => {
const c = componentDef()
c.children = false
const result = expandComponentDefinition(c)
expect(result.children).toBe(false)
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';
describe('make_margin', () => {
test('it should generate a valid rule', () => {
expect(make_margin(["1", "1", "1", "1"])).toEqual('1px 1px 1px 1px')
})
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')
})
import {
generate_css,
make_margin,
generate_screen_css,
} from "../src/builderStore/generate_css.js"
describe("make_margin", () => {
test("it should generate a valid rule", () => {
expect(make_margin(["1", "1", "1", "1"])).toEqual("1px 1px 1px 1px")
})
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', () => {
test('it should generate a valid css rule: grid-area', () => {
expect(
generate_css({ layout: { gridarea: ["", "", "", ""] } })
).toEqual({
layout: '',
position: ''
});
})
test('it should generate a valid css rule: grid-gap', () => {
expect(
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 2', () => {
expect(
generate_css({ position: { column: ["1", ""] } })
).toEqual({
position: 'grid-column-start: 1;',
layout: ''
});
})
test('it should generate a valid css rule: column 3', () => {
expect(
generate_css({ position: { column: ["", "1"] } })
).toEqual({
position: 'grid-column-end: 1;',
layout: ''
});
})
test('it should generate a valid css rule: column 4', () => {
expect(
generate_css({ position: { column: ["1", "1"] } })
).toEqual({
position: 'grid-column-start: 1;\ngrid-column-end: 1;',
layout: ''
});
})
test('it should generate a valid css rule: row 1', () => {
expect(
generate_css({ position: { row: ["", ""] } })
).toEqual({ layout: '', position: '' });
})
test('it should generate a valid css rule: row 2', () => {
expect(
generate_css({ position: { row: ["1", ""] } })
).toEqual({
position: 'grid-row-start: 1;',
layout: ''
});
})
test('it should generate a valid css rule: row 3', () => {
expect(
generate_css({ position: { row: ["", "1"] } })
).toEqual({
position: 'grid-row-end: 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;',
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;',
layout: ''
});
})
test('it should generate a valid css rule: padding 2', () => {
expect(
generate_css({ position: { padding: ["1", "", "", "1"] } })
).toEqual({
position: 'padding: 1px 0px 0px 1px;',
layout: ''
});
})
test('it should generate a valid css rule: margin 1', () => {
expect(
generate_css({ position: { margin: ["1", "1", "1", "1"] } })
).toEqual({
position: 'margin: 1px 1px 1px 1px;',
layout: ''
});
})
test('it should generate a valid css rule: margin 2', () => {
expect(
generate_css({ position: { margin: ["1", "", "", "1"] } })
).toEqual({
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_css", () => {
test("it should generate a valid css rule: grid-area", () => {
expect(generate_css({ layout: { gridarea: ["", "", "", ""] } })).toEqual({
layout: "",
position: "",
})
})
test("it should generate a valid css rule: grid-gap", () => {
expect(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 2", () => {
expect(generate_css({ position: { column: ["1", ""] } })).toEqual({
position: "grid-column-start: 1;",
layout: "",
})
})
test("it should generate a valid css rule: column 3", () => {
expect(generate_css({ position: { column: ["", "1"] } })).toEqual({
position: "grid-column-end: 1;",
layout: "",
})
})
test("it should generate a valid css rule: column 4", () => {
expect(generate_css({ position: { column: ["1", "1"] } })).toEqual({
position: "grid-column-start: 1;\ngrid-column-end: 1;",
layout: "",
})
})
test("it should generate a valid css rule: row 1", () => {
expect(generate_css({ position: { row: ["", ""] } })).toEqual({
layout: "",
position: "",
})
})
test("it should generate a valid css rule: row 2", () => {
expect(generate_css({ position: { row: ["1", ""] } })).toEqual({
position: "grid-row-start: 1;",
layout: "",
})
})
test("it should generate a valid css rule: row 3", () => {
expect(generate_css({ position: { row: ["", "1"] } })).toEqual({
position: "grid-row-end: 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;",
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;",
layout: "",
})
})
test("it should generate a valid css rule: padding 2", () => {
expect(generate_css({ position: { padding: ["1", "", "", "1"] } })).toEqual(
{
position: "padding: 1px 0px 0px 1px;",
layout: "",
}
)
})
test("it should generate a valid css rule: margin 1", () => {
expect(
generate_css({ position: { margin: ["1", "1", "1", "1"] } })
).toEqual({
position: "margin: 1px 1px 1px 1px;",
layout: "",
})
})
test("it should generate a valid css rule: margin 2", () => {
expect(generate_css({ position: { margin: ["1", "", "", "1"] } })).toEqual({
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', () => {
test('it should compile the css for a list of components', () => {
const components = [
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] }
},
_id: 1
},
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] }
},
_id: 2
}, {
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] }
},
_id: 3
}
, {
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] }
},
_id: 4
}
]
const compiled = `.pos-1 {
describe("generate_screen_css", () => {
test("it should compile the css for a list of components", () => {
const components = [
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 1,
},
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 2,
},
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 3,
},
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 4,
},
]
const compiled = `.pos-1 {
margin: 1px 1px 1px 1px;
}
.lay-1 {
@ -210,80 +194,79 @@ margin: 1px 1px 1px 1px;
}`
expect(generate_screen_css(components)).toEqual(compiled)
})
test('it should compile the css for a list of components', () => {
const components = [
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] }
},
_id: 1,
_children: [
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] }
},
_id: 2,
_children: [
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] }
},
_id: 3,
_children: [
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] }
},
_id: 4,
_children: [
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] }
},
_id: 5,
_children: [
]
},
]
},
]
},
]
},
]
},
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] }
},
_id: 6
}, {
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] }
},
_id: 7
}
, {
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] }
},
_id: 8
}
]
const compiled = `.pos-1 {
expect(generate_screen_css(components)).toEqual(compiled)
})
test("it should compile the css for a list of components", () => {
const components = [
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 1,
_children: [
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 2,
_children: [
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 3,
_children: [
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 4,
_children: [
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 5,
_children: [],
},
],
},
],
},
],
},
],
},
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 6,
},
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 7,
},
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 8,
},
]
const compiled = `.pos-1 {
margin: 1px 1px 1px 1px;
}
.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 {
keys, some, find
} from "lodash/fp";
import { componentsAndScreens } from "./testData";
getInstanceProps,
getScreenInfo,
getComponentInfo,
} from "../src/userInterface/pagesParsing/createProps"
import { keys, some, find } from "lodash/fp"
import { componentsAndScreens } from "./testData"
describe("getComponentInfo", () => {
it("should return default props for root component", () => {
const result = getComponentInfo(
componentsAndScreens().components,
"budibase-components/TextBox");
expect(result.errors).toEqual([]);
expect(result.fullProps).toEqual({
_component: "budibase-components/TextBox",
size: "",
isPassword: false,
placeholder: "",
label:""
});
});
it("getInstanceProps should set supplied props on top of default props", () => {
const result = getInstanceProps(
getComponentInfo(
componentsAndScreens().components,
"budibase-components/TextBox"),
{size:"small"});
expect(result).toEqual({
_component: "budibase-components/TextBox",
size: "small",
isPassword: false,
placeholder: "",
label:""
});
});
});
it("should return default props for root component", () => {
const result = getComponentInfo(
componentsAndScreens().components,
"budibase-components/TextBox"
)
expect(result.errors).toEqual([])
expect(result.fullProps).toEqual({
_component: "budibase-components/TextBox",
size: "",
isPassword: false,
placeholder: "",
label: "",
})
})
it("getInstanceProps should set supplied props on top of default props", () => {
const result = getInstanceProps(
getComponentInfo(
componentsAndScreens().components,
"budibase-components/TextBox"
),
{ size: "small" }
)
expect(result).toEqual({
_component: "budibase-components/TextBox",
size: "small",
isPassword: false,
placeholder: "",
label: "",
})
})
})
describe("getScreenInfo", () => {
const getScreen = (screens, name) =>
find(s => s.name === name)(screens);
it("should return correct props for screen", () => {
const {components, screens} = componentsAndScreens();
const result = getScreenInfo(
components,
getScreen(screens, "common/SmallTextbox"));
expect(result.errors).toEqual([]);
expect(result.fullProps).toEqual({
_component: "common/SmallTextbox",
size: "small",
isPassword: false,
placeholder: "",
label:""
});
});
it("should return correct props for twice derived component", () => {
const {components, screens} = componentsAndScreens();
const result = getScreenInfo(
components,
getScreen(screens, "common/PasswordBox"));
expect(result.errors).toEqual([]);
expect(result.fullProps).toEqual({
_component: "common/PasswordBox",
size: "small",
isPassword: true,
placeholder: "",
label:""
});
});
it("should list unset props as those that are only defined in root", () => {
const {components, screens} = componentsAndScreens();
const result = getScreenInfo(
components,
getScreen(screens, "common/PasswordBox"));
expect(result.unsetProps).toEqual([
"placeholder", "label"]);
});
})
const getScreen = (screens, name) => find(s => s.name === name)(screens)
it("should return correct props for screen", () => {
const { components, screens } = componentsAndScreens()
const result = getScreenInfo(
components,
getScreen(screens, "common/SmallTextbox")
)
expect(result.errors).toEqual([])
expect(result.fullProps).toEqual({
_component: "common/SmallTextbox",
size: "small",
isPassword: false,
placeholder: "",
label: "",
})
})
it("should return correct props for twice derived component", () => {
const { components, screens } = componentsAndScreens()
const result = getScreenInfo(
components,
getScreen(screens, "common/PasswordBox")
)
expect(result.errors).toEqual([])
expect(result.fullProps).toEqual({
_component: "common/PasswordBox",
size: "small",
isPassword: true,
placeholder: "",
label: "",
})
})
it("should list unset props as those that are only defined in root", () => {
const { components, screens } = componentsAndScreens()
const result = getScreenInfo(
components,
getScreen(screens, "common/PasswordBox")
)
expect(result.unsetProps).toEqual(["placeholder", "label"])
})
})

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

@ -1,27 +1,21 @@
import {
getExactComponent
} from "../src/userInterface/pagesParsing/searchComponents";
import {
rename
} from "../src/userInterface/pagesParsing/renameScreen";
import { componentsAndScreens } from "./testData";
import { getExactComponent } from "../src/userInterface/pagesParsing/searchComponents"
import { rename } from "../src/userInterface/pagesParsing/renameScreen"
import { componentsAndScreens } from "./testData"
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 newComponent = getExactComponent(result.screens, "MainButton");
const oldComponent = getExactComponent(result.screens, "Primary");
expect(oldComponent).toBeUndefined();
expect(newComponent).toBeDefined();
expect(newComponent.name).toBe("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")
})
/* 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", () => {
const {screens} = componentsAndScreens();
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 {screens} = componentsAndScreens();
const pages = {
main: {
appBody: "PrimaryButton"
}
};
const result = rename(pages, screens, "PrimaryButton", "MainButton");
expect(result.pages.main.appBody).toBe("MainButton");
});
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", () => {
const {screens} = componentsAndScreens();
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 {
searchAllComponents,
getExactComponent,
getAncestorProps
} from "../src/userInterface/pagesParsing/searchComponents";
import { componentsAndScreens } from "./testData";
searchAllComponents,
getExactComponent,
getAncestorProps,
} from "../src/userInterface/pagesParsing/searchComponents"
import { componentsAndScreens } from "./testData"
describe("searchAllComponents", () => {
it("should match component by name", () => {
const results = searchAllComponents(
componentsAndScreens().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,
"record"
);
expect(results.length).toBe(1);
expect(results[0].name).toBe("budibase-components/RecordView");
});
});
it("should match component by name", () => {
const results = searchAllComponents(
componentsAndScreens().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,
"record"
)
expect(results.length).toBe(1)
expect(results[0].name).toBe("budibase-components/RecordView")
})
})
describe("getExactComponent", () => {
it("should get component by name", () => {
const {components, screens} = componentsAndScreens();
const result = getExactComponent(
[...components, ...screens],
"common/SmallTextbox"
)
expect(result).toBeDefined();
expect(result.name).toBe("common/SmallTextbox");
});
it("should return nothing when no result (should not fail)", () => {
const {components, screens} = componentsAndScreens();
const result = getExactComponent(
[...components, ...screens],
"bla/bla/bla"
)
expect(result).not.toBeDefined();
});
});
it("should get component by name", () => {
const { components, screens } = componentsAndScreens()
const result = getExactComponent(
[...components, ...screens],
"common/SmallTextbox"
)
expect(result).toBeDefined()
expect(result.name).toBe("common/SmallTextbox")
})
it("should return nothing when no result (should not fail)", () => {
const { components, screens } = componentsAndScreens()
const result = getExactComponent([...components, ...screens], "bla/bla/bla")
expect(result).not.toBeDefined()
})
})
describe("getAncestorProps", () => {
it("should return props of root component", () => {
const result = getAncestorProps(
componentsAndScreens().components,
"budibase-components/TextBox"
);
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(
allComponentsAndScreens,
"common/PasswordBox"
);
expect(result).toEqual([
allComponentsAndScreens[0].props,
{...allComponentsAndScreens[5].props}
]);
});
it("should return props of root component", () => {
const result = getAncestorProps(
componentsAndScreens().components,
"budibase-components/TextBox"
)
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(
allComponentsAndScreens,
"common/PasswordBox"
)
expect(result).toEqual([
allComponentsAndScreens[0].props,
{ ...allComponentsAndScreens[5].props },
])
})
})

197
packages/builder/tests/testData.js

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

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

@ -1,105 +1,95 @@
import {
validatePages,
validatePage
} from "../src/userInterface/pagesParsing/validatePages";
import {
validatePages,
validatePage,
} from "../src/userInterface/pagesParsing/validatePages"
const validPages = () => ({
"main" : {
"index" : {
"title": "My Cool App"
},
"appBody" : "./main.app.json"
main: {
index: {
title: "My Cool App",
},
"unauthenticated" : {
"index" : {
"title": "My Cool App - Login"
},
"appBody" : "./unauthenticated.app.json"
appBody: "./main.app.json",
},
unauthenticated: {
index: {
title: "My Cool App - Login",
},
"componentLibraries": ["./myComponents"]
});
const getComponent = name => ({
testIndexHtml : {
name: "testIndexHtml",
props: {
title: "string",
}
}
}[name])
appBody: "./unauthenticated.app.json",
},
componentLibraries: ["./myComponents"],
})
const getComponent = name =>
({
testIndexHtml: {
name: "testIndexHtml",
props: {
title: "string",
},
},
}[name])
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", () => {
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);
});
expect(errors).toEqual([])
})
it("should return error when appBody is not set, or set incorrectly", () => {
let page = validPages().main;
delete page.appBody;
expect(validatePage(page, getComponent).length).toEqual(1);
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)
})
page.appBody = true; // not a string
expect(validatePage(page, getComponent).length).toEqual(1);
it("should return error when appBody is not set, or set incorrectly", () => {
let page = validPages().main
delete page.appBody
expect(validatePage(page, getComponent).length).toEqual(1)
page.appBody = "something.js"; // not a json
expect(validatePage(page, getComponent).length).toEqual(1);
page.appBody = true // not a string
expect(validatePage(page, getComponent).length).toEqual(1)
});
});
page.appBody = "something.js" // not a json
expect(validatePage(page, getComponent).length).toEqual(1)
})
})
describe("validate pages", () => {
it("should return no errors when pages are valid", () => {
const errors = validatePages(
validPages(),
getComponent);
expect(errors).toEqual([]);
});
it("should return error when component libraries not set", () => {
const pages = validPages();
delete pages.componentLibraries;
let 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);
expect(errors.length).toBe(1);
pages = validPages();
delete pages.unauthenticated;
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);
});
});
it("should return no errors when pages are valid", () => {
const errors = validatePages(validPages(), getComponent)
expect(errors).toEqual([])
})
it("should return error when component libraries not set", () => {
const pages = validPages()
delete pages.componentLibraries
let 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)
expect(errors.length).toBe(1)
pages = validPages()
delete pages.unauthenticated
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 chalk = require("chalk");
const yargs = require("yargs")
const chalk = require("chalk")
module.exports = () => {
yargs
.scriptName("budi")
.usage('$0 <cmd> [args]')
.usage("$0 <cmd> [args]")
.command(require("./commands/init"))
.command(require("./commands/new"))
.command(require("./commands/run"))
.command(require("./commands/instance"))
.fail((msg, err) => {
if(err) {
console.log(chalk.red(err.message));
console.log(chalk.gray(err.toString()));
if (err) {
console.log(chalk.red(err.message))
console.log(chalk.gray(err.toString()))
} 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 = {
command: "init [dir] [config] [username] [password]",
desc: "Initialise Budibase. Run this first to setup your local Budibase",
builder: yargs => {
yargs.positional("dir", {
type: "string",
describe: "your apps directory - directory will be created if it does not exist",
default: ".",
alias: "d"
});
yargs.positional("config", {
type: "string",
describe: "config template file to use - optional, defaults to config.js",
alias: "c",
default: "config.dev.js",
choices: ["dev", "contributors"]
});
yargs.positional("username", {
type: "string",
describe: "username for admin interface",
alias: "u",
default: ""
})
yargs.positional("password", {
type: "string",
describe: "passord for admin interface",
alias: "p",
default: ""
})
},
handler
command: "init [dir] [config] [username] [password]",
desc: "Initialise Budibase. Run this first to setup your local Budibase",
builder: yargs => {
yargs.positional("dir", {
type: "string",
describe:
"your apps directory - directory will be created if it does not exist",
default: ".",
alias: "d",
})
yargs.positional("config", {
type: "string",
describe: "config template file to use - optional, defaults to config.js",
alias: "c",
default: "config.dev.js",
choices: ["dev", "contributors"],
})
yargs.positional("username", {
type: "string",
describe: "username for admin interface",
alias: "u",
default: "",
})
yargs.positional("password", {
type: "string",
describe: "passord for admin interface",
alias: "p",
default: "",
})
},
handler,
}

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

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

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

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

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

@ -1,59 +1,61 @@
const { getAppContext } = require("../../common");
const {
getMasterApisWithFullAccess
} = require("@budibase/server/utilities/budibaseApi");
const { copy, readJSON, writeJSON, remove, exists } = require("fs-extra");
const { resolve, join } = require("path");
const thisPackageJson = require("../../../package.json");
const chalk = require("chalk");
const {exec} = require('child_process');
module.exports = (opts) => {
run(opts);
console.log(chalk.green(`Budibase app ${opts.name} created!`));
const { getAppContext } = require("../../common")
const {
getMasterApisWithFullAccess,
} = require("@budibase/server/utilities/budibaseApi")
const { copy, readJSON, writeJSON, remove, exists } = require("fs-extra")
const { resolve, join } = require("path")
const thisPackageJson = require("../../../package.json")
const chalk = require("chalk")
const { exec } = require("child_process")
module.exports = opts => {
run(opts)
console.log(chalk.green(`Budibase app ${opts.name} created!`))
}
const run = async (opts) => {
const context = await getAppContext({configName:opts.config, masterIsCreated:true});
opts.config = context.config;
const bb = await getMasterApisWithFullAccess(context);
const run = async opts => {
const context = await getAppContext({
configName: opts.config,
masterIsCreated: true,
})
opts.config = context.config
const bb = await getMasterApisWithFullAccess(context)
const app = bb.recordApi.getNew("/applications", "application");
app.name = opts.name;
const app = bb.recordApi.getNew("/applications", "application")
app.name = opts.name
await bb.recordApi.save(app);
await createEmptyAppPackage(opts);
await bb.recordApi.save(app)
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 templateFolder = resolve(
__dirname, "appPackageTemplate");
const createEmptyAppPackage = async opts => {
const templateFolder = resolve(__dirname, "appPackageTemplate")
const appsFolder = opts.config.latestPackagesFolder || ".";
const destinationFolder = resolve(appsFolder, opts.name);
const appsFolder = opts.config.latestPackagesFolder || "."
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 packageJson = await readJSON(packageJsonPath);
const packageJsonPath = join(appsFolder, opts.name, "package.json")
const packageJson = await readJSON(packageJsonPath)
packageJson.name = opts.name;
packageJson.dependencies["@budibase/standard-components"] = `^${thisPackageJson.version}`;
packageJson.name = opts.name
packageJson.dependencies[
"@budibase/standard-components"
] = `^${thisPackageJson.version}`
await writeJSON(packageJsonPath, packageJson);
await writeJSON(packageJsonPath, packageJson)
const removePlaceholder = async (...args) => {
await remove(join(destinationFolder, ...args, "placeholder"));
}
const removePlaceholder = async (...args) => {
await remove(join(destinationFolder, ...args, "placeholder"))
}
await removePlaceholder("components");
await removePlaceholder("public", "shared");
await removePlaceholder("public", "main");
await removePlaceholder("public", "unauthenticated");
}
await removePlaceholder("components")
await removePlaceholder("public", "shared")
await removePlaceholder("public", "main")
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 = {
command: "run [config]",
aliases: ["$0"],
desc: "Start budibase Server. You can access your apps and the builder from here if you have dev=true in your config",
builder: yargs => {
yargs.positional("config", {
type: "string",
describe: "config file to use. optional, defaults to config.js. Use 'dev' as shorthand for 'config.dev.js' ",
alias: "c",
default: "config.js"
})
},
handler
command: "run [config]",
aliases: ["$0"],
desc:
"Start budibase Server. You can access your apps and the builder from here if you have dev=true in your config",
builder: yargs => {
yargs.positional("config", {
type: "string",
describe:
"config file to use. optional, defaults to config.js. Use 'dev' as shorthand for 'config.dev.js' ",
alias: "c",
default: "config.js",
})
},
handler,
}

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

@ -1,11 +1,9 @@
const { getAppContext } = require("../../common");
const app = require("@budibase/server/app");
const { getAppContext } = require("../../common")
const app = require("@budibase/server/app")
module.exports = ({config}) => {
getAppContext({configName:config, masterIsCreated:true})
.then(context => {
app(context);
console.log(`Budibase Builder running on port ${context.config.port}..`);
});
module.exports = ({ config }) => {
getAppContext({ configName: config, masterIsCreated: true }).then(context => {
app(context)
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 { cwd } = require("process");
const buildAppContext = require("@budibase/server/initialise/buildAppContext");
const { resolve, join } = require("path")
const { cwd } = require("process")
const buildAppContext = require("@budibase/server/initialise/buildAppContext")
module.exports.serverFileName = relativePath =>
resolve(__dirname,
"..",
"node_modules",
"@budibase",
"server",
relativePath);
module.exports.serverFileName = relativePath =>
resolve(__dirname, "..", "node_modules", "@budibase", "server", relativePath)
module.exports.getAppContext = async ({configName, masterIsCreated}) => {
if(configName) {
if(!configName.endsWith(".js")) {
configName = `config.${configName}.js`;
}
} else {
configName = "config.js";
module.exports.getAppContext = async ({ configName, masterIsCreated }) => {
if (configName) {
if (!configName.endsWith(".js")) {
configName = `config.${configName}.js`
}
} else {
configName = "config.js"
}
const config = require(resolve(cwd(), configName))();
return await buildAppContext(config, masterIsCreated);
}
const config = require(resolve(cwd(), configName))()
return await buildAppContext(config, masterIsCreated)
}

25
packages/client/babel.config.js

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

165
packages/client/rollup.config.js

@ -1,70 +1,111 @@
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import builtins from 'rollup-plugin-node-builtins';
import nodeglobals from 'rollup-plugin-node-globals';
import { terser } from 'rollup-plugin-terser';
import resolve from "rollup-plugin-node-resolve"
import commonjs from "rollup-plugin-commonjs"
import builtins from "rollup-plugin-node-builtins"
import nodeglobals from "rollup-plugin-node-globals"
import { terser } from "rollup-plugin-terser"
const lodash_fp_exports = [
"find", "isUndefined", "split", "max",
"last", "union", "reduce", "isObject",
"cloneDeep", "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"];
"find",
"isUndefined",
"split",
"max",
"last",
"union",
"reduce",
"isObject",
"cloneDeep",
"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 = [
"flow", "head", "find", "each",
"tail", "findIndex", "startsWith",
"dropRight", "takeRight",
"trim", "split", "replace",
"merge", "assign"];
"flow",
"head",
"find",
"each",
"tail",
"findIndex",
"startsWith",
"dropRight",
"takeRight",
"trim",
"split",
"replace",
"merge",
"assign",
]
const coreExternal = [
"lodash", "lodash/fp",
"lunr", "safe-buffer", "shortid",
"@nx-js/compiler-util"
];
"lodash",
"lodash/fp",
"lunr",
"safe-buffer",
"shortid",
"@nx-js/compiler-util",
]
export default {
input: 'src/index.js',
output: [
{
sourcemap: true,
format: 'iife',
name: 'app',
file: `./dist/budibase-client.js`
},
{
file: 'dist/budibase-client.esm.mjs',
format: 'esm',
sourcemap: "inline"
}
],
plugins: [
resolve({
preferBuiltins:true,
browser:true,
dedupe: importee => {
return coreExternal.includes(importee);
}
}),
commonjs({
namedExports: {
"lodash/fp": lodash_fp_exports,
"lodash":lodash_exports,
"shortid": ["generate"]
}
}),
builtins(),
nodeglobals(),
//terser()
],
watch: {
clearScreen: false
}
};
input: "src/index.js",
output: [
{
sourcemap: true,
format: "iife",
name: "app",
file: `./dist/budibase-client.js`,
},
{
file: "dist/budibase-client.esm.mjs",
format: "esm",
sourcemap: "inline",
},
],
plugins: [
resolve({
preferBuiltins: true,
browser: true,
dedupe: importee => {
return coreExternal.includes(importee)
},
}),
commonjs({
namedExports: {
"lodash/fp": lodash_fp_exports,
lodash: lodash_exports,
shortid: ["generate"],
},
}),
builtins(),
nodeglobals(),
//terser()
],
watch: {
clearScreen: false,
},
}

91
packages/client/scripts/publishDev.js

@ -1,53 +1,52 @@
const { readdir, stat, copyFile } = require("fs-extra");
const { constants } = require("fs");
const { join, basename } = require("path");
const packagesFolder = "..";
const jsFile = dir => join(dir, "budibase-client.js");
const jsMapFile = dir => join(dir, "budibase-client.js.map");
const sourceJs = jsFile("dist");
const sourceJsMap = jsMapFile("dist");
const appPackages = join(packagesFolder, "server", "appPackages");
const publicMain = appName => join(appPackages, appName, "public", "main");
const publicUnauth = appName => join(appPackages, appName, "public", "unauthenticated");
const nodeModules = appName => join(appPackages, appName, "node_modules", "@budibase", "client", "dist");
(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 { readdir, stat, copyFile } = require("fs-extra")
const { constants } = require("fs")
const { join, basename } = require("path")
const packagesFolder = ".."
const jsFile = dir => join(dir, "budibase-client.js")
const jsMapFile = dir => join(dir, "budibase-client.js.map")
const sourceJs = jsFile("dist")
const sourceJsMap = jsMapFile("dist")
const appPackages = join(packagesFolder, "server", "appPackages")
const publicMain = appName => join(appPackages, appName, "public", "main")
const publicUnauth = appName =>
join(appPackages, appName, "public", "unauthenticated")
const nodeModules = appName =>
join(appPackages, appName, "node_modules", "@budibase", "client", "dist")
;(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 copySourceJs = copySource(sourceJs)
const copySourceJsMap = copySource(sourceJsMap)
for(let app of apps) {
if(!(await stat(join(appPackages, app))).isDirectory()) continue;
for (let app of apps) {
if (!(await stat(join(appPackages, app))).isDirectory()) continue
await copySourceJs(nodeModules(app));
await copySourceJsMap(nodeModules(app));
await copySourceJs(nodeModules(app))
await copySourceJsMap(nodeModules(app))
await copySourceJs(publicMain(app));
await copySourceJsMap(publicMain(app));
await copySourceJs(publicMain(app))
await copySourceJsMap(publicMain(app))
await copySourceJs(publicUnauth(app));
await copySourceJsMap(publicUnauth(app));
await copySource(
join("dist", "budibase-client.esm.mjs"))(
join(packagesFolder, "server", "builder" ));
}
await copySourceJs(publicUnauth(app))
await copySourceJsMap(publicUnauth(app))
})();
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) {
api.error("Authenticate: username not set");
return;
}
if(!password) {
api.error("Authenticate: password not set");
return;
}
const user = await api.post({
url: "/api/authenticate",
body: { username, password },
})
const user = await api.post({
url:"/api/authenticate",
body : {username, password}
});
// set user even if error - so it is defined at least
api.setState(USER_STATE_PATH, user);
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 {loadRecord} from "./loadRecord";
import {listRecords} from "./listRecords";
import {authenticate} from "./authenticate";
import {saveRecord} from "./saveRecord";
import { ERROR } from "../state/standardState"
import { loadRecord } from "./loadRecord"
import { listRecords } from "./listRecords"
import { authenticate } from "./authenticate"
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}) => {
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 post = apiCall("POST")
const get = apiCall("GET")
const patch = apiCall("PATCH")
const del = apiCall("DELETE")
const post = apiCall("POST");
const get = apiCall("GET");
const patch = apiCall("PATCH");
const del = apiCall("DELETE");
const ERROR_MEMBER = "##error"
const error = message => {
const e = {}
e[ERROR_MEMBER] = message
setState(ERROR, message)
return e
}
const ERROR_MEMBER = "##error";
const error = message => {
const e = {};
e[ERROR_MEMBER] = message;
setState(ERROR, message);
return e;
}
const isSuccess = obj => !obj || !obj[ERROR_MEMBER]
const isSuccess = obj => !obj || !obj[ERROR_MEMBER];
const apiOpts = {
rootPath,
setState,
getState,
isSuccess,
error,
post,
get,
patch,
delete: del,
}
const apiOpts = {
rootPath, setState, getState, isSuccess, error,
post, get, patch, delete:del
};
return {
loadRecord:loadRecord(apiOpts),
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}) => {
if(!indexKey) {
api.error("Load Record: record key not set");
return;
}
if(!statePath) {
api.error("Load Record: state path not set");
return;
}
export const listRecords = api => async ({ indexKey, statePath }) => {
if (!indexKey) {
api.error("Load Record: record key not set")
return
}
const records = await api.get({
url:`/api/listRecords/${trimSlash(indexKey)}`
});
if (!statePath) {
api.error("Load Record: state path not set")
return
}
if(api.isSuccess(records))
api.setState(statePath, records);
}
const records = await api.get({
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) {
api.error("Load Record: record key not set");
return;
}
if(!statePath) {
api.error("Load Record: state path not set");
return;
}
if (!statePath) {
api.error("Load Record: state path not set")
return
}
const record = await api.get({
url:`/api/record/${trimSlash(recordKey)}`
});
const record = await api.get({
url: `/api/record/${trimSlash(recordKey)}`,
})
if(api.isSuccess(record))
api.setState(statePath, record);
}
if (api.isSuccess(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}) => {
if(!statePath) {
api.error("Load Record: state path not set");
return;
}
export const saveRecord = api => async ({ statePath }) => {
if (!statePath) {
api.error("Load Record: state path not set")
return
}
const recordtoSave = api.getState(statePath);
const recordtoSave = api.getState(statePath)
if(!recordtoSave) {
api.error(`there is no record in state: ${statePath}`);
return;
}
if (!recordtoSave) {
api.error(`there is no record in state: ${statePath}`)
return
}
if(!recordtoSave.key) {
api.error(`item in state does not appear to be a record - it has no key (${statePath})`);
return;
}
if (!recordtoSave.key) {
api.error(
`item in state does not appear to be a record - it has no key (${statePath})`
)
return
}
const savedRecord = await api.post({
url:`/api/record/${trimSlash(recordtoSave.key)}`,
body: recordtoSave
});
const savedRecord = await api.post({
url: `/api/record/${trimSlash(recordtoSave.key)}`,
body: recordtoSave,
})
if(api.isSuccess(savedRecord))
api.setState(statePath, savedRecord);
}
if (api.isSuccess(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) => {
const app = {
datastore: null,
crypto:null,
publish: () => {},
hierarchy: appDefinition.hierarchy,
actions: appDefinition.actions,
user
};
const app = {
datastore: null,
crypto: null,
publish: () => {},
hierarchy: appDefinition.hierarchy,
actions: appDefinition.actions,
user,
}
return app;
}
return app
}

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

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

159
packages/client/src/createApp.js

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

106
packages/client/src/index.js

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

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

@ -1,72 +1,80 @@
import {
setupBinding
} from "../state/stateBinding";
import {
split,
last
} from "lodash/fp";
import { $ } from "../core/common";
import { renderComponent } from "./renderComponent";
import { setupBinding } from "../state/stateBinding"
import { split, last } from "lodash/fp"
import { $ } from "../core/common"
import { renderComponent } from "./renderComponent"
export const _initialiseChildren = (initialiseOpts) =>
(childrenProps, htmlElement, anchor=null) => {
export const _initialiseChildren = initialiseOpts => (
childrenProps,
htmlElement,
anchor = null
) => {
const {
uiFunctions,
bb,
coreApi,
store,
componentLibraries,
treeNode,
appDefinition,
document,
hydrate,
} = initialiseOpts
const { uiFunctions, bb, coreApi,
store, componentLibraries, treeNode,
appDefinition, document, hydrate } = initialiseOpts;
for (let childNode of treeNode.children) {
if (childNode.unsubscribe) childNode.unsubscribe()
if (childNode.component) childNode.component.$destroy()
}
for(let childNode of treeNode.children) {
if(childNode.unsubscribe)
childNode.unsubscribe();
if(childNode.component)
childNode.component.$destroy();
if (hydrate) {
while (htmlElement.firstChild) {
htmlElement.removeChild(htmlElement.firstChild)
}
}
if(hydrate) {
while (htmlElement.firstChild) {
htmlElement.removeChild(htmlElement.firstChild);
}
}
htmlElement.classList.add(`lay-${treeNode.props._id}`)
htmlElement.classList.add(`lay-${treeNode.props._id}`)
const renderedComponents = []
for (let childProps of childrenProps) {
const { componentName, libName } = splitName(childProps._component)
if (!componentName || !libName) return
const { initialProps, bind } = setupBinding(
store,
childProps,
coreApi,
appDefinition.appRootPath
)
const renderedComponents = [];
for(let childProps of childrenProps) {
const {componentName, libName} = splitName(childProps._component);
const componentConstructor = componentLibraries[libName][componentName]
if(!componentName || !libName) return;
const {initialProps, bind} = setupBinding(
store, childProps, coreApi,
appDefinition.appRootPath);
const componentConstructor = componentLibraries[libName][componentName];
const renderedComponentsThisIteration = renderComponent({
props: childProps,
parentNode: treeNode,
componentConstructor,
uiFunctions,
htmlElement,
anchor,
initialProps,
bb,
})
const renderedComponentsThisIteration = renderComponent({
props: childProps,
parentNode: treeNode,
componentConstructor,uiFunctions,
htmlElement, anchor, initialProps,
bb});
for(let comp of renderedComponentsThisIteration) {
comp.unsubscribe = bind(comp.component);
renderedComponents.push(comp);
}
for (let comp of renderedComponentsThisIteration) {
comp.unsubscribe = bind(comp.component)
renderedComponents.push(comp)
}
}
return renderedComponents;
return renderedComponents
}
const splitName = fullname => {
const componentName = $(fullname, [
split("/"),
last
]);
const componentName = $(fullname, [split("/"), last])
const libName =fullname.substring(
0, fullname.length - componentName.length - 1);
const libName = fullname.substring(
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 = ({
componentConstructor, uiFunctions,
htmlElement, anchor, props,
initialProps, bb,
parentNode}) => {
componentConstructor,
uiFunctions,
htmlElement,
anchor,
props,
initialProps,
bb,
parentNode,
}) => {
const func = initialProps._id ? uiFunctions[initialProps._id] : undefined
const func = initialProps._id
? uiFunctions[initialProps._id]
: undefined;
const parentContext = (parentNode && parentNode.context) || {}
const parentContext = (parentNode && parentNode.context) || {};
let renderedNodes = [];
const render = (context) => {
let componentContext = parentContext;
if(context) {
componentContext = {...componentContext};
componentContext.$parent = parentContext;
}
let renderedNodes = []
const render = context => {
let componentContext = parentContext
if (context) {
componentContext = { ...componentContext }
componentContext.$parent = parentContext
}
const thisNode = createTreeNode();
thisNode.context = componentContext;
thisNode.parentNode = parentNode;
thisNode.props = props;
const thisNode = createTreeNode()
thisNode.context = componentContext
thisNode.parentNode = parentNode
thisNode.props = props
parentNode.children.push(thisNode);
renderedNodes.push(thisNode);
parentNode.children.push(thisNode)
renderedNodes.push(thisNode)
initialProps._bb = bb(thisNode, props);
initialProps._bb = bb(thisNode, props)
thisNode.component = new componentConstructor({
target: htmlElement,
props: initialProps,
hydrate:false,
anchor
});
thisNode.component = new componentConstructor({
target: htmlElement,
props: initialProps,
hydrate: false,
anchor,
})
thisNode.rootElement = htmlElement.children[
htmlElement.children.length - 1];
if (initialProps._id) {
thisNode.rootElement.classList.add(`pos-${initialProps._id}`)
}
}
thisNode.rootElement = htmlElement.children[htmlElement.children.length - 1]
if(func) {
func(render, parentContext);
} else {
render();
if (initialProps._id) {
thisNode.rootElement.classList.add(`pos-${initialProps._id}`)
}
}
return renderedNodes;
if (func) {
func(render, parentContext)
} else {
render()
}
return renderedNodes
}
export const createTreeNode = () => ({
context: {},
props: {},
rootElement: null,
parentNode: null,
children: [],
component: null,
unsubscribe: () => {}
});
context: {},
props: {},
rootElement: null,
parentNode: null,
children: [],
component: null,
unsubscribe: () => {},
})

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

@ -1,63 +1,71 @@
import { ERROR } from "./standardState";
export const getNewChildRecordToState = (coreApi, setState) =>
({recordKey, collectionName,childRecordType,statePath}) => {
const error = errorHandler(setState);
try {
if(!recordKey) {
error("getNewChild > recordKey not set");
return;
}
if(!collectionName) {
error("getNewChild > collectionName not set");
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);
import { ERROR } from "./standardState"
export const getNewChildRecordToState = (coreApi, setState) => ({
recordKey,
collectionName,
childRecordType,
statePath,
}) => {
const error = errorHandler(setState)
try {
if (!recordKey) {
error("getNewChild > recordKey not set")
return
}
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) =>
({collectionKey,childRecordType,statePath}) => {
const error = errorHandler(setState);
try {
if(!collectionKey) {
error("getNewChild > collectionKey not set");
return;
}
if (!statePath) {
error("getNewChild > statePath not set")
return
}
if(!childRecordType) {
error("getNewChild > childRecordType not set");
return;
}
const rec = coreApi.recordApi.getNewChild(
recordKey,
collectionName,
childRecordType
)
setState(statePath, rec)
} catch (e) {
error(e.message)
}
}
if(!statePath) {
error("getNewChild > statePath not set");
return;
}
export const getNewRecordToState = (coreApi, setState) => ({
collectionKey,
childRecordType,
statePath,
}) => {
const error = errorHandler(setState)
try {
if (!collectionKey) {
error("getNewChild > collectionKey not set")
return
}
const rec = coreApi.recordApi.getNew(collectionKey, childRecordType);
setState(statePath, rec);
if (!childRecordType) {
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 { getState } from "./getState";
import {
isArray, isUndefined
} from "lodash/fp";
import { createApi } from "../api";
import {
getNewChildRecordToState, getNewRecordToState
} from "./coreHandlers";
export const EVENT_TYPE_MEMBER_NAME = "##eventHandlerType";
export const eventHandlers = (store,coreApi,rootPath) => {
const handler = (parameters, execute) => ({
execute, parameters
});
const setStateWithStore = (path, value) => setState(store, path, value);
let currentState;
store.subscribe(s => {
currentState = s;
});
const api = createApi({
rootPath:rootPath,
setState: setStateWithStore,
getState: (path, fallback) => getState(currentState, path, fallback)
});
const setStateHandler = ({path, value}) => setState(store, path, value);
return {
"Set State": handler(["path", "value"], setStateHandler),
"Load Record": handler(["recordKey", "statePath"], api.loadRecord),
"List Records": handler(["indexKey", "statePath"], api.listRecords),
"Save Record": handler(["statePath"], api.saveRecord),
"Get New Child Record": handler(
["recordKey", "collectionName", "childRecordType", "statePath"],
getNewChildRecordToState(coreApi, setStateWithStore)),
"Get New Record": handler(
["collectionKey", "childRecordType", "statePath"],
getNewRecordToState(coreApi, setStateWithStore)),
"Authenticate": handler(["username", "password"], api.authenticate)
};
};
export const isEventType = prop =>
isArray(prop)
&& prop.length > 0
&& !isUndefined(prop[0][EVENT_TYPE_MEMBER_NAME]);
import { setState } from "./setState"
import { getState } from "./getState"
import { isArray, isUndefined } from "lodash/fp"
import { createApi } from "../api"
import { getNewChildRecordToState, getNewRecordToState } from "./coreHandlers"
export const EVENT_TYPE_MEMBER_NAME = "##eventHandlerType"
export const eventHandlers = (store, coreApi, rootPath) => {
const handler = (parameters, execute) => ({
execute,
parameters,
})
const setStateWithStore = (path, value) => setState(store, path, value)
let currentState
store.subscribe(s => {
currentState = s
})
const api = createApi({
rootPath: rootPath,
setState: setStateWithStore,
getState: (path, fallback) => getState(currentState, path, fallback),
})
const setStateHandler = ({ path, value }) => setState(store, path, value)
return {
"Set State": handler(["path", "value"], setStateHandler),
"Load Record": handler(["recordKey", "statePath"], api.loadRecord),
"List Records": handler(["indexKey", "statePath"], api.listRecords),
"Save Record": handler(["statePath"], api.saveRecord),
"Get New Child Record": handler(
["recordKey", "collectionName", "childRecordType", "statePath"],
getNewChildRecordToState(coreApi, setStateWithStore)
),
"Get New Record": handler(
["collectionKey", "childRecordType", "statePath"],
getNewRecordToState(coreApi, setStateWithStore)
),
Authenticate: handler(["username", "password"], api.authenticate),
}
}
export const isEventType = prop =>
isArray(prop) &&
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 {
isUndefined,
isObject
} from "lodash/fp";
import {
isBound,BB_STATE_BINDINGPATH, BB_STATE_FALLBACK, takeStateFromStore
} from "./isState";
isBound,
BB_STATE_BINDINGPATH,
BB_STATE_FALLBACK,
takeStateFromStore,
} from "./isState"
export const getState = (s, path, fallback) => {
if (!s) return fallback
if (!path || path.length === 0) return fallback
if(!s) return fallback;
if(!path || path.length === 0) return fallback;
if (path === "$") return s
if(path === "$") return s;
const pathParts = path.split(".")
const safeGetPath = (obj, currentPartIndex = 0) => {
const currentKey = pathParts[currentPartIndex]
const pathParts = path.split(".");
const safeGetPath = (obj, currentPartIndex=0) => {
const currentKey = pathParts[currentPartIndex];
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 (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)
}
return safeGetPath(s);
return safeGetPath(s)
}
export const getStateOrValue = (globalState, prop, currentContext) => {
if(!prop) return prop;
if(isBound(prop)) {
if (!prop) return prop
const stateToUse = takeStateFromStore(prop)
? globalState
: currentContext;
if (isBound(prop)) {
const stateToUse = takeStateFromStore(prop) ? globalState : 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) {
const stateToUse = prop.source === "store"
? globalState
: currentContext;
if (prop.path && prop.source) {
const stateToUse = prop.source === "store" ? 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_BINDINGSOURCE = "##bbsource";
export const BB_STATE_FALLBACK = "##bbstatefallback";
export const BB_STATE_BINDINGPATH = "##bbstate"
export const BB_STATE_BINDINGSOURCE = "##bbsource"
export const BB_STATE_FALLBACK = "##bbstatefallback"
export const isBound = (prop) =>
prop !== undefined
&& prop[BB_STATE_BINDINGPATH] !== undefined;
export const takeStateFromStore = (prop) =>
prop[BB_STATE_BINDINGSOURCE] === undefined
|| prop[BB_STATE_BINDINGSOURCE] === "store";
export const isBound = prop =>
prop !== undefined && prop[BB_STATE_BINDINGPATH] !== undefined
export const takeStateFromContext = (prop) =>
prop[BB_STATE_BINDINGSOURCE] === "context";
export const takeStateFromStore = prop =>
prop[BB_STATE_BINDINGSOURCE] === undefined ||
prop[BB_STATE_BINDINGSOURCE] === "store"
export const takeStateFromEventParameters = (prop) =>
prop[BB_STATE_BINDINGSOURCE] === "event";
export const takeStateFromContext = prop =>
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 {
isObject
} from "lodash/fp";
import { BB_STATE_BINDINGPATH } from "./isState";
import { isObject } from "lodash/fp"
import { BB_STATE_BINDINGPATH } from "./isState"
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(".");
const safeSetPath = (obj, currentPartIndex=0) => {
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 (pathParts.length - 1 == currentPartIndex) {
obj[currentKey] = value
return
}
if (
obj[currentKey] === null ||
obj[currentKey] === undefined ||
!isObject(obj[currentKey])
) {
obj[currentKey] = {}
}
store.update(s => {
safeSetPath(s);
return s;
});
};
safeSetPath(obj[currentKey], currentPartIndex + 1)
}
store.update(s => {
safeSetPath(s)
return s
})
}
export const setStateFromBinding = (store, binding, value) =>
setState(store, binding[BB_STATE_BINDINGPATH], value);
export const setStateFromBinding = (store, binding, 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