Browse Source

Merge pull request #80 from shogunpurple/housekeeping

running prettier over codebase, removing merge files
master
Martin McKeaveney 7 years ago
committed by GitHub
parent
commit
68c3588def
  1. 3
      .eslintrc.json
  2. 3
      .prettierrc
  3. 6
      package.json
  4. 812
      packages/bootstrap-components/dist/generators.js
  5. 30671
      packages/bootstrap-components/public/bundle.js
  6. 104
      packages/bootstrap-components/public/clientAppDefinition.js
  7. 36
      packages/bootstrap-components/rollup.config.js
  8. 26
      packages/bootstrap-components/rollup.generatorsconfig.js
  9. 196
      packages/bootstrap-components/rollup.testconfig.js
  10. 137
      packages/bootstrap-components/scripts/publishDev.js
  11. 73
      packages/bootstrap-components/src/Form.svelte
  12. 201
      packages/bootstrap-components/src/Nav.svelte
  13. 36
      packages/bootstrap-components/src/Test/TestApp.svelte
  14. 81
      packages/bootstrap-components/src/Test/createApp.js
  15. 470
      packages/bootstrap-components/src/Test/props.js
  16. 8
      packages/bootstrap-components/src/Test/testMain.js
  17. 16
      packages/bootstrap-components/src/buildStyle.js
  18. 34
      packages/bootstrap-components/src/cssVars.js
  19. 2
      packages/bootstrap-components/src/emptyProps.js
  20. 8
      packages/bootstrap-components/src/generators.js
  21. 66
      packages/bootstrap-components/src/generators/appGenerator.js
  22. 32
      packages/bootstrap-components/src/generators/buttonGenerators.js
  23. 223
      packages/bootstrap-components/src/generators/formsGenerator.js
  24. 23
      packages/bootstrap-components/src/generators/getRecordPath.js
  25. 85
      packages/bootstrap-components/src/generators/indexTablesGenerator.js
  26. 335
      packages/bootstrap-components/src/generators/recordHomePageGenerator.js
  27. 56
      packages/bootstrap-components/src/generators/selectedNavContentGenerator.js
  28. 5
      packages/bootstrap-components/src/index.js
  29. 25
      packages/builder/babel.config.js
  30. 16
      packages/builder/build/copy.js
  31. 316
      packages/builder/rollup.config.js
  32. 69
      packages/builder/src/App.svelte
  33. 75
      packages/builder/src/BackendRoot.svelte
  34. 63
      packages/builder/src/NoPackage.svelte
  35. 130
      packages/builder/src/PackageRoot.svelte
  36. 31
      packages/builder/src/Settings.svelte
  37. 154
      packages/builder/src/accessLevels/AccessLevelView.svelte
  38. 178
      packages/builder/src/accessLevels/AccessLevelsRoot.svelte
  39. 199
      packages/builder/src/actionsAndTriggers/ActionView.svelte
  40. 167
      packages/builder/src/actionsAndTriggers/Actions.svelte
  41. 225
      packages/builder/src/actionsAndTriggers/ActionsAndTriggersRoot.svelte
  42. 105
      packages/builder/src/actionsAndTriggers/TriggerView.svelte
  43. 130
      packages/builder/src/actionsAndTriggers/Triggers.svelte
  44. 31
      packages/builder/src/builderStore/api.js
  45. 44
      packages/builder/src/builderStore/buildCodeForScreens.js
  46. 20
      packages/builder/src/builderStore/createPackage.js
  47. 183
      packages/builder/src/builderStore/generate_css.js
  48. 44
      packages/builder/src/builderStore/index.js
  49. 57
      packages/builder/src/builderStore/loadComponentLibraries.js
  50. 1290
      packages/builder/src/builderStore/store.js
  51. 799
      packages/builder/src/builderStore/store.js.orig
  52. 10
      packages/builder/src/builderStore/uuid.js
  53. 34
      packages/builder/src/common/ActionButton.svelte
  54. 166
      packages/builder/src/common/Button.svelte
  55. 28
      packages/builder/src/common/ButtonGroup.svelte
  56. 19
      packages/builder/src/common/Checkbox.svelte
  57. 28
      packages/builder/src/common/CodeArea.svelte
  58. 14
      packages/builder/src/common/ComingSoon.svelte
  59. 46
      packages/builder/src/common/DatePicker.svelte
  60. 73
      packages/builder/src/common/Dropdown.svelte
  61. 79
      packages/builder/src/common/DropdownButton.svelte
  62. 26
      packages/builder/src/common/ErrorsBox.svelte
  63. 90
      packages/builder/src/common/IconButton.svelte
  64. 12
      packages/builder/src/common/Icons/ArrowDown.svelte
  65. 4
      packages/builder/src/common/Icons/CircleIndicator.svelte
  66. 15
      packages/builder/src/common/Icons/Image.svelte
  67. 14
      packages/builder/src/common/Icons/Input.svelte
  68. 12
      packages/builder/src/common/Icons/Layout.svelte
  69. 14
      packages/builder/src/common/Icons/Paint.svelte
  70. 2
      packages/builder/src/common/Icons/Pencil.svelte
  71. 14
      packages/builder/src/common/Icons/Terminal.svelte
  72. 18
      packages/builder/src/common/Icons/index.js
  73. 12
      packages/builder/src/common/Icons/index.js.orig
  74. 6
      packages/builder/src/common/Input.svelte
  75. 34
      packages/builder/src/common/Inputs/InputGroup.svelte
  76. 38
      packages/builder/src/common/Modal.svelte
  77. 29
      packages/builder/src/common/NumberBox.svelte
  78. 4
      packages/builder/src/common/PlusButton.svelte
  79. 22
      packages/builder/src/common/Select.svelte
  80. 42
      packages/builder/src/common/Textbox.svelte
  81. 41
      packages/builder/src/common/ValuesList.svelte
  82. 41
      packages/builder/src/common/binding.js
  83. 164
      packages/builder/src/common/core.js
  84. 43
      packages/builder/src/common/eventHandlers.js
  85. 7
      packages/builder/src/common/icon.js
  86. 95
      packages/builder/src/database/ActionsHeader.svelte
  87. 18
      packages/builder/src/database/CollectionView.svelte
  88. 123
      packages/builder/src/database/DatabaseRoot.svelte
  89. 251
      packages/builder/src/database/FieldView.svelte
  90. 45
      packages/builder/src/database/HierarchyRow.svelte
  91. 125
      packages/builder/src/database/IndexView.svelte
  92. 425
      packages/builder/src/database/RecordView.svelte
  93. 34
      packages/builder/src/main.js
  94. 214
      packages/builder/src/nav/BackendNav.svelte
  95. 95
      packages/builder/src/nav/HierarchyRow.svelte
  96. 44
      packages/builder/src/nav/NavItem.svelte
  97. 136
      packages/builder/src/userInterface/CodeEditor.svelte
  98. 193
      packages/builder/src/userInterface/ComponentPanel.svelte
  99. 149
      packages/builder/src/userInterface/ComponentPanel.svelte.orig
  100. 69
      packages/builder/src/userInterface/ComponentSearch.svelte

3
.eslintrc.json

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

3
.prettierrc

@ -3,5 +3,6 @@
"semi": false, "semi": false,
"singleQuote": false, "singleQuote": false,
"trailingComma": "es5", "trailingComma": "es5",
"plugins": ["prettier-plugin-svelte"] "plugins": ["prettier-plugin-svelte"],
"svelteSortOrder" : "scripts-markup-styles"
} }

6
package.json

@ -7,7 +7,8 @@
"eslint-plugin-svelte3": "^2.7.3", "eslint-plugin-svelte3": "^2.7.3",
"lerna": "^3.14.1", "lerna": "^3.14.1",
"prettier": "^1.19.1", "prettier": "^1.19.1",
"prettier-plugin-svelte": "^0.7.0" "prettier-plugin-svelte": "^0.7.0",
"svelte": "^3.18.1"
}, },
"dependencies": {}, "dependencies": {},
"scripts": { "scripts": {
@ -18,6 +19,7 @@
"dev": "lerna run --parallel --stream dev:builder", "dev": "lerna run --parallel --stream dev:builder",
"test": "lerna run test", "test": "lerna run test",
"lint": "eslint packages", "lint": "eslint packages",
"format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx}\"" "lint:fix": "eslint --fix packages",
"format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx,svelte}\""
} }
} }

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

File diff suppressed because one or more lines are too long

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

File diff suppressed because it is too large

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

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

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

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

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

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

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

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

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

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

73
packages/bootstrap-components/src/Form.svelte

@ -1,47 +1,44 @@
<script> <script>
export let formControls = []
export let formControls = [];
export let _bb
export let _bb;
let htmlElements = {}
let htmlElements = {}; let labelElements = {}
let labelElements = {}; let labels = {}
let labels = {}; let isInitialised = false
let isInitialised = false;
$: {
$ : { if (_bb && htmlElements && !isInitialised) {
let cIndex = 0
if(_bb && htmlElements && !isInitialised) { for (let c of formControls) {
labels[cIndex] = c.label
let cIndex = 0; cIndex++
for(let c of formControls) { }
labels[cIndex] = c.label;
cIndex++; for (let el in htmlElements) {
} if (formControls[el].control.controlPosition === "Before Label") {
_bb.insertChildren(
for(let el in htmlElements) { _bb.props.formControls[el].control,
if(formControls[el].control.controlPosition === "Before Label") { htmlElements[el],
_bb.insertChildren( htmlElements[el].childNodes.find(n => n.tagName === "LABEL")
_bb.props.formControls[el].control, )
htmlElements[el], } else {
htmlElements[el].childNodes.find(n => n.tagName === "LABEL")); _bb.appendChildren(
} else { _bb.props.formControls[el].control,
_bb.appendChildren( htmlElements[el]
_bb.props.formControls[el].control, )
htmlElements[el]);
}
} }
isInitialised = true; }
isInitialised = true
} }
} }
</script> </script>
<form> <form>
{#each formControls as child, idx} {#each formControls as child, idx}
<div class="form-group" bind:this={htmlElements[idx]}> <div class="form-group" bind:this={htmlElements[idx]}>
<label>{labels[idx]}</label> <label>{labels[idx]}</label>
</div> </div>
{/each} {/each}
</form> </form>

201
packages/bootstrap-components/src/Nav.svelte

@ -1,132 +1,119 @@
<script> <script>
export let items = []
export let items = []; export let hideNavBar = false
export let hideNavBar=false; export let selectedItem = ""
export let selectedItem=""; export let orientation = "horizontal" // horizontal, verical
export let orientation="horizontal"; // horizontal, verical export let alignment = "start" // start, center, end
export let alignment="start"; // start, center, end export let pills = false
export let pills=false; export let fill = false
export let fill=false; export let className = ""
export let className=""; export let _bb
export let _bb;
let selectedIndex = -1
let selectedIndex = -1; let styleVars = {}
let styleVars={}; let components = {}
let components = {}; let componentElement
let componentElement; let orientationClass = ""
let orientationClass=""; let navClasses = ""
let navClasses=""; let currentComponent
let currentComponent; let _selectedItem = ""
let _selectedItem="";
const hasComponentElements = () =>
const hasComponentElements = () => Object.getOwnPropertyNames(componentElements).length > 0
Object.getOwnPropertyNames(componentElements).length > 0;
const getSelectedItemByIndex = index => (index >= 0 ? items[index].title : "")
const getSelectedItemByIndex = (index) =>
index >= 0 $: {
? items[index].title let _navClasses = ""
: "";
if (orientation === "vertical") {
$: { _navClasses += " flex-column"
let _navClasses = "";
if(orientation === "vertical") {
_navClasses += " flex-column";
} else { } else {
_navClasses += ` justify-content-${alignment}`; _navClasses += ` justify-content-${alignment}`
} }
if(pills) if (pills) _navClasses += " nav-pills"
_navClasses += " nav-pills";
if(fill)
_navClasses += " nav-fill nav-justified";
navClasses = _navClasses;
if(items && componentElement) {
const currentSelectedItem = getSelectedItemByIndex(selectedIndex);
if(selectedItem && currentSelectedItem !== selectedItem) {
let i=0;
for(let item of items) {
if(item.title === selectedItem) {
SelectItem(i);
}
i++;
}
} else if(!selectedItem) {
SelectItem(-1);
}
}
}
const SelectItem = (index) => { if (fill) _navClasses += " nav-fill nav-justified"
selectedIndex = index; navClasses = _navClasses
const newSelectedItem = getSelectedItemByIndex(index);
if(newSelectedItem !== selectedItem) {
selectedItem = newSelectedItem;
}
if(currentComponent) { if (items && componentElement) {
try { const currentSelectedItem = getSelectedItemByIndex(selectedIndex)
currentComponent.$destroy();
} catch(_) {} if (selectedItem && currentSelectedItem !== selectedItem) {
let i = 0
for (let item of items) {
if (item.title === selectedItem) {
SelectItem(i)
}
i++
}
} else if (!selectedItem) {
SelectItem(-1)
}
} }
}
if(index >= 0) const SelectItem = index => {
currentComponent = _bb.hydrateChildren( selectedIndex = index
_bb.props.items[index].component, componentElement); const newSelectedItem = getSelectedItemByIndex(index)
if (newSelectedItem !== selectedItem) {
selectedItem = newSelectedItem
}
} if (currentComponent) {
try {
currentComponent.$destroy()
} catch (_) {}
}
const onSelectItemClicked = index => () => { if (index >= 0)
if(_bb.props.selectedItem) { currentComponent = _bb.hydrateChildren(
// binding - call state, which should SelectItem(..) _bb.props.items[index].component,
const selectedItemBinding = _bb.props.selectedItem; componentElement
_bb.setStateFromBinding( )
selectedItemBinding, getSelectedItemByIndex(index)) }
const onSelectItemClicked = index => () => {
if (_bb.props.selectedItem) {
// binding - call state, which should SelectItem(..)
const selectedItemBinding = _bb.props.selectedItem
_bb.setStateFromBinding(
selectedItemBinding,
getSelectedItemByIndex(index)
)
} else { } else {
// no binding - call this // no binding - call this
SelectItem(index); SelectItem(index)
} }
} }
</script> </script>
<div class="root {className}"> <div class="root {className}">
{#if !hideNavBar} {#if !hideNavBar}
<ul class="nav {navClasses}"> <ul class="nav {navClasses}">
{#each items as navItem, index} {#each items as navItem, index}
<li class="nav-item"> <li class="nav-item">
<button class="nav-link btn btn-link" <button
on:click={onSelectItemClicked(index)} class="nav-link btn btn-link"
class:disabled={navItem.disabled} on:click={onSelectItemClicked(index)}
class:active={selectedIndex === index}> class:disabled={navItem.disabled}
{navItem.title} class:active={selectedIndex === index}>
</button> {navItem.title}
</button>
</li> </li>
{/each} {/each}
</ul> </ul>
{/if} {/if}
{#each items as navItem, index} {#each items as navItem, index}
<div bind:this={componentElement} />
<div bind:this={componentElement}> {/each}
</div>
{/each}
</div> </div>
<style> <style>
.root {
.root {
height: 100%; height: 100%;
width:100%; width: 100%;
} }
</style> </style>

36
packages/bootstrap-components/src/Test/TestApp.svelte

@ -1,40 +1,34 @@
<script> <script>
import createApp from "./createApp"; import createApp from "./createApp"
import { props } from "./props"; import { props } from "./props"
let _bb; let _bb
const _appPromise = createApp(); const _appPromise = createApp()
_appPromise.then(a => _bb = a); _appPromise.then(a => (_bb = a))
const testProps = props.hiddenNav; const testProps = props.hiddenNav
let currentComponent; let currentComponent
$: { $: {
if(_bb && currentComponent) { if (_bb && currentComponent) {
_bb.hydrateChildren(testProps, currentComponent); _bb.hydrateChildren(testProps, currentComponent)
} }
} }
</script> </script>
{#await _appPromise} {#await _appPromise}
loading loading
{:then _bb} {:then _bb}
<div id="current_component" bind:this={currentComponent}> <div id="current_component" bind:this={currentComponent} />
</div>
{/await} {/await}
<style> <style>
#current_component { #current_component {
height: 100%; height: 100%;
width: 100%; width: 100%;
} }
</style> </style>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

25
packages/builder/babel.config.js

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

16
packages/builder/build/copy.js

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

316
packages/builder/rollup.config.js

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

69
packages/builder/src/App.svelte

@ -1,38 +1,33 @@
<script> <script>
import NoPackage from "./NoPackage.svelte"
import NoPackage from "./NoPackage.svelte"; import PackageRoot from "./PackageRoot.svelte"
import PackageRoot from "./PackageRoot.svelte"; import Settings from "./Settings.svelte"
import Settings from "./Settings.svelte"; import { store, initialise } from "./builderStore"
import {store, initialise} from "./builderStore"; import { onMount } from "svelte"
import { onMount } from 'svelte'; import IconButton from "./common/IconButton.svelte"
import IconButton from "./common/IconButton.svelte";
let init = initialise()
let init = initialise();
</script> </script>
<main> <main>
{#await init} {#await init}
<h1>loading</h1>
{:then result}
{#if $store.hasAppPackage}
<PackageRoot />
{:else} <h1>loading</h1>
<NoPackage /> {:then result}
{/if}
{#if $store.hasAppPackage}
<PackageRoot />
{:else}
<NoPackage />
{/if}
{:catch err} {:catch err}
<h1 style="color:red">{err}</h1> <h1 style="color:red">{err}</h1>
{/await} {/await}
<!-- <!--
<div class="settings"> <div class="settings">
<IconButton icon="settings" <IconButton icon="settings"
on:click={store.showSettings}/> on:click={store.showSettings}/>
@ -46,15 +41,15 @@
</main> </main>
<style> <style>
main { main {
height: 100%; height: 100%;
width: 100%; width: 100%;
font-family: "Roboto", Helvetica, Arial, sans-serif; font-family: "Roboto", Helvetica, Arial, sans-serif;
} }
.settings { .settings {
position: absolute; position: absolute;
bottom: 25px; bottom: 25px;
right: 25px; right: 25px;
} }
</style> </style>

75
packages/builder/src/BackendRoot.svelte

@ -1,56 +1,47 @@
<script> <script>
import BackendNav from "./nav/BackendNav.svelte"
import Database from "./database/DatabaseRoot.svelte"
import UserInterface from "./userInterface/UserInterfaceRoot.svelte"
import ActionsAndTriggers from "./actionsAndTriggers/ActionsAndTriggersRoot.svelte"
import AccessLevels from "./accessLevels/AccessLevelsRoot.svelte"
import ComingSoon from "./common/ComingSoon.svelte"
import BackendNav from "./nav/BackendNav.svelte"; import { store } from "./builderStore"
import Database from "./database/DatabaseRoot.svelte" ;
import UserInterface from "./userInterface/UserInterfaceRoot.svelte" ;
import ActionsAndTriggers from "./actionsAndTriggers/ActionsAndTriggersRoot.svelte" ;
import AccessLevels from "./accessLevels/AccessLevelsRoot.svelte" ;
import ComingSoon from "./common/ComingSoon.svelte";
import {store} from "./builderStore";
export let navWidth = "50px";
export let navWidth = "50px"
</script> </script>
<div class="root"> <div class="root">
<div class="nav"> <div class="nav">
<BackendNav /> <BackendNav />
</div> </div>
<div class="content" <div class="content" style="width: calc(100% - {navWidth}); left: {navWidth}">
style="width: calc(100% - {navWidth}); left: {navWidth}"> {#if $store.activeNav === 'database'}
{#if $store.activeNav === "database"} <Database />
<Database /> {:else if $store.activeNav === 'actions'}
{:else if $store.activeNav === "actions"} <ActionsAndTriggers />
<ActionsAndTriggers /> {:else if $store.activeNav === 'access levels'}
{:else if $store.activeNav === "access levels"} <AccessLevels />
<AccessLevels />
{/if} {/if}
</div> </div>
</div> </div>
<style> <style>
.root {
.root { height: 100%;
height: 100%; display: flex;
display: flex; }
}
.content {
flex: 1 1 auto;
.content { height: 100%;
flex: 1 1 auto; background-color: var(--white);
height: 100%; margin: 0;
background-color: var(--white); }
margin:0;
} .nav {
flex: 0 1 auto;
.nav { width: 300px;
flex: 0 1 auto; height: 100%;
width: 300px; }
height: 100%; </style>
}
</style>

63
packages/builder/src/NoPackage.svelte

@ -1,56 +1,55 @@
<script> <script>
import Button from "./common/Button.svelte"
import { store } from "./builderStore"
import Button from "./common/Button.svelte" let errors = []
import { store } from "./builderStore";
let errors = [];
</script> </script>
<div class="root"> <div class="root">
<div class="inner"> <div class="inner">
<img src="/_builder/assets/budibase-logo.png" class="logo" alt="budibase logo"/> <img
<div> src="/_builder/assets/budibase-logo.png"
class="logo"
<div> alt="budibase logo" />
<h4 style="margin-bottom: 20px">Choose an Application</h4> <div>
{#each $store.apps as app}
<a href={`#/${app}`} class="app-link">{app}</a> <div>
{/each} <h4 style="margin-bottom: 20px">Choose an Application</h4>
</div> {#each $store.apps as app}
</div> <a href={`#/${app}`} class="app-link">{app}</a>
{/each}
</div>
</div> </div>
</div>
</div> </div>
<style> <style>
.root {
.root {
position: fixed; position: fixed;
margin: 0 auto; margin: 0 auto;
text-align: center; text-align: center;
top: 20%; top: 20%;
/*color: #333333; /*color: #333333;
background-color: #fdfdfd;*/ background-color: #fdfdfd;*/
width:100%; width: 100%;
} }
.inner { .inner {
display:inline-block; display: inline-block;
margin: auto; margin: auto;
} }
.logo { .logo {
width: 300px; width: 300px;
margin-bottom: 40px; margin-bottom: 40px;
} }
.root :global(.option) { .root :global(.option) {
width:250px; width: 250px;
} }
.app-link { .app-link {
margin-top: 10px; margin-top: 10px;
display: block; display: block;
} }
</style>
</style>

130
packages/builder/src/PackageRoot.svelte

@ -1,57 +1,57 @@
<script> <script>
import IconButton from "./common/IconButton.svelte"
import IconButton from "./common/IconButton.svelte"; import { store } from "./builderStore"
import { store } from "./builderStore"; import UserInterfaceRoot from "./userInterface/UserInterfaceRoot.svelte"
import UserInterfaceRoot from "./userInterface/UserInterfaceRoot.svelte"; import BackendRoot from "./BackendRoot.svelte"
import BackendRoot from "./BackendRoot.svelte"; import { fade } from "svelte/transition"
import { fade } from "svelte/transition";
</script> </script>
<div class="root"> <div class="root">
<div class="top-nav"> <div class="top-nav">
<button class="home-logo"><img src="/assets/budibase-logo-only.png"/></button> <button class="home-logo">
<!-- <IconButton icon="home" <img src="/assets/budibase-logo-only.png" />
</button>
<!-- <IconButton icon="home"
color="var(--slate)" color="var(--slate)"
hoverColor="var(--secondary75)"/> --> hoverColor="var(--secondary75)"/> -->
<span class:active={$store.isBackend} <span
class="topnavitem" class:active={$store.isBackend}
on:click={store.showBackend}> class="topnavitem"
Backend on:click={store.showBackend}>
</span> Backend
<span class:active={!$store.isBackend} </span>
class="topnavitem" <span
on:click={store.showFrontend}> class:active={!$store.isBackend}
Frontend class="topnavitem"
</span> on:click={store.showFrontend}>
</div> Frontend
</span>
<div class="content"> </div>
{#if $store.isBackend}
<div in:fade out:fade> <div class="content">
<BackendRoot /> {#if $store.isBackend}
</div> <div in:fade out:fade>
{:else} <BackendRoot />
<div in:fade out:fade> </div>
<UserInterfaceRoot /> {:else}
</div> <div in:fade out:fade>
{/if} <UserInterfaceRoot />
</div> </div>
{/if}
</div>
</div> </div>
<style> <style>
.root {
.root { height: 100%;
height:100%; width: 100%;
width:100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
}
} .top-nav {
.top-nav {
flex: 0 0 auto; flex: 0 0 auto;
height: 48px; height: 48px;
background: white; background: white;
@ -60,21 +60,20 @@ import { fade } from "svelte/transition";
display: flex; display: flex;
align-items: center; align-items: center;
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
} }
.content { .content {
flex: 1 1 auto; flex: 1 1 auto;
width: 100%; width: 100%;
height: 100px; height: 100px;
} }
.content > div { .content > div {
height:100%; height: 100%;
width:100%; width: 100%;
}
}
.topnavitem { .topnavitem {
cursor: pointer; cursor: pointer;
color: var(--secondary50); color: var(--secondary50);
margin: 0px 15px; margin: 0px 15px;
@ -85,41 +84,38 @@ import { fade } from "svelte/transition";
display: flex; display: flex;
align-items: center; align-items: center;
box-sizing: border-box; box-sizing: border-box;
} }
.topnavitem:hover { .topnavitem:hover {
color: var(--secondary75); color: var(--secondary75);
font-weight: 600; font-weight: 600;
}
} .active {
.active {
color: var(--primary100); color: var(--primary100);
font-weight: 600; font-weight: 600;
border-bottom: 2px solid var(--primary100); border-bottom: 2px solid var(--primary100);
border-top: 2px solid transparent; border-top: 2px solid transparent;
} }
.home-logo { .home-logo {
border-style: none; border-style: none;
background-color: rgba(0,0,0,0); background-color: rgba(0, 0, 0, 0);
cursor: pointer; cursor: pointer;
outline: none; outline: none;
height: 40px; height: 40px;
padding: 8px 10px; padding: 8px 10px;
} }
.home-logo:hover { .home-logo:hover {
color: var(--hovercolor); color: var(--hovercolor);
} }
.home-logo:active {
outline:none;
}
.home-logo:active {
outline: none;
}
.home-logo img { .home-logo img {
height: 100%; height: 100%;
} }
</style> </style>

31
packages/builder/src/Settings.svelte

@ -1,27 +1,26 @@
<script> <script>
import IconButton from "./common/IconButton.svelte"
import IconButton from "./common/IconButton.svelte"; import { store } from "./builderStore"
import { store } from "./builderStore"; import UserInterfaceRoot from "./userInterface/UserInterfaceRoot.svelte"
import UserInterfaceRoot from "./userInterface/UserInterfaceRoot.svelte"; import { fade } from "svelte/transition"
import { fade } from "svelte/transition";
</script> </script>
<div class="root"> <div class="root">
<div class="content uk-container"> <div class="content uk-container">
<h1>Settings</h1> <h1>Settings</h1>
<label> <label>
<input type="checkbox" <input
class="uk-checkbox" type="checkbox"
bind:checked={$store.useAnalytics}> class="uk-checkbox"
Send analytics bind:checked={$store.useAnalytics} />
</label> Send analytics
</label>
</div> </div>
</div> </div>
<style> <style>
</style> </style>

154
packages/builder/src/accessLevels/AccessLevelView.svelte

@ -1,99 +1,91 @@
<script> <script>
import { cloneDeep, map, some, filter } from "lodash/fp"
import Textbox from "../common/Textbox.svelte"
import Checkbox from "../common/Checkbox.svelte"
import ButtonGroup from "../common/ButtonGroup.svelte"
import Button from "../common/Button.svelte"
import { validateAccessLevels } from "../common/core"
import ErrorsBox from "../common/ErrorsBox.svelte"
import {cloneDeep, map, some, filter} from "lodash/fp"; export let level
import Textbox from "../common/Textbox.svelte"; export let allPermissions
import Checkbox from "../common/Checkbox.svelte"; export let onFinished
import ButtonGroup from "../common/ButtonGroup.svelte"; export let isNew
import Button from "../common/Button.svelte"; export let allLevels
import {validateAccessLevels} from "../common/core"; export let hierarchy
import ErrorsBox from "../common/ErrorsBox.svelte"; export let actions
export let level; let errors = []
export let allPermissions; let clonedLevel = cloneDeep(level)
export let onFinished;
export let isNew; const matchPermissions = (p1, p2) =>
export let allLevels; p1.type === p2.type &&
export let hierarchy; ((!p2.nodeKey && !p1.nodeKey) || p2.nodeKey === p1.nodeKey)
export let actions;
const hasPermission = hasPerm =>
let errors = []; some(p => matchPermissions(p, hasPerm))(clonedLevel.permissions)
let clonedLevel = cloneDeep(level);
$: permissionMatrix = map(p => ({
const matchPermissions = (p1, p2) => permission: p,
p1.type === p2.type hasPermission: hasPermission(p),
&& }))(allPermissions)
((!p2.nodeKey && !p1.nodeKey)
|| p2.nodeKey === p1.nodeKey); const getPermissionName = perm =>
perm.nodeKey ? `${perm.type} - ${perm.nodeKey}` : perm.type
const hasPermission = hasPerm =>
some(p => matchPermissions(p, hasPerm)) const save = () => {
(clonedLevel.permissions); const newLevels = isNew
? [...allLevels, clonedLevel]
$: permissionMatrix = : [...filter(l => l.name !== level.name)(allLevels), clonedLevel]
map(p => ({permission:p, hasPermission: hasPermission(p)}))
(allPermissions) errors = validateAccessLevels(hierarchy, actions, newLevels)
const getPermissionName = perm => if (errors.length > 0) return
perm.nodeKey
? `${perm.type} - ${perm.nodeKey}` onFinished(clonedLevel)
: perm.type; }
const save = () => { const permissionChanged = perm => ev => {
const hasPermission = ev.target.checked
const newLevels =
isNew if (hasPermission) {
? [...allLevels, clonedLevel] clonedLevel.permissions.push(perm)
: [...filter(l => l.name !== level.name)(allLevels), clonedLevel];
errors = validateAccessLevels(
hierarchy,
actions,
newLevels
);
if(errors.length > 0) return;
onFinished(clonedLevel);
}
const permissionChanged = perm => ev => {
const hasPermission = ev.target.checked;
if(hasPermission) {
clonedLevel.permissions.push(perm);
} else { } else {
clonedLevel.permissions = filter(p => !matchPermissions(p, perm))(clonedLevel.permissions); clonedLevel.permissions = filter(p => !matchPermissions(p, perm))(
clonedLevel.permissions
)
} }
} }
</script> </script>
<div> <div>
<ErrorsBox {errors} /> <ErrorsBox {errors} />
<form class="uk-form-horizontal">
<Textbox label="Name" bind:text={clonedLevel.name} /> <form class="uk-form-horizontal">
{#each permissionMatrix as permission} <Textbox label="Name" bind:text={clonedLevel.name} />
<div>
<Checkbox label={getPermissionName(permission.permission)}
checked={permission.hasPermission}
on:change={permissionChanged(permission.permission)} />
</div>
{/each}
</form> {#each permissionMatrix as permission}
<div>
<Checkbox
label={getPermissionName(permission.permission)}
checked={permission.hasPermission}
on:change={permissionChanged(permission.permission)} />
</div>
{/each}
<ButtonGroup style="margin-top: 10px"> </form>
<Button color="primary" grouped on:click={save}>Save</Button>
<Button color="secondary" grouped on:click={() => onFinished()}>Cancel</Button>
</ButtonGroup>
<ButtonGroup style="margin-top: 10px">
<Button color="primary" grouped on:click={save}>Save</Button>
<Button color="secondary" grouped on:click={() => onFinished()}>
Cancel
</Button>
</ButtonGroup>
</div> </div>
<style> <style>
</style> </style>

178
packages/builder/src/accessLevels/AccessLevelsRoot.svelte

@ -1,119 +1,115 @@
<script> <script>
import ButtonGroup from "../common/ButtonGroup.svelte"
import ButtonGroup from "../common/ButtonGroup.svelte"; import Button from "../common/Button.svelte"
import Button from "../common/Button.svelte"; import { store } from "../builderStore"
import {store} from "../builderStore"; import { generateFullPermissions, getNewAccessLevel } from "../common/core"
import {generateFullPermissions, getNewAccessLevel} from "../common/core"; import getIcon from "../common/icon"
import getIcon from "../common/icon"; import AccessLevelView from "./AccessLevelView.svelte"
import AccessLevelView from "./AccessLevelView.svelte"; import Modal from "../common/Modal.svelte"
import Modal from "../common/Modal.svelte";
let editingLevel = null
let editingLevel = null; let editingLevelIsNew = false
let editingLevelIsNew = false; $: isEditing = editingLevel !== null
$: isEditing = (editingLevel !== null);
let allPermissions = []
let allPermissions = []; store.subscribe(db => {
store.subscribe(db => { allPermissions = generateFullPermissions(db.hierarchy, db.actions)
allPermissions = generateFullPermissions(db.hierarchy, db.actions); })
})
let onLevelEdit = level => {
let onLevelEdit = (level) => { editingLevel = level
editingLevel = level; editingLevelIsNew = false
editingLevelIsNew = false; }
};
let onLevelCancel = () => {
let onLevelCancel = () => { editingAction = null
editingAction = null; }
};
let onLevelDelete = level => {
let onLevelDelete = (level) => { store.deleteLevel(level)
store.deleteLevel(level); }
};
let createNewLevel = () => {
editingLevelIsNew = true
let createNewLevel = () => { editingLevel = getNewAccessLevel()
editingLevelIsNew = true; }
editingLevel = getNewAccessLevel();
} let onEditingFinished = level => {
if (level) {
let onEditingFinished = (level) => { store.saveLevel(level, editingLevelIsNew, editingLevel)
if(level) {
store.saveLevel(level, editingLevelIsNew, editingLevel);
} }
editingLevel = null; editingLevel = null
} }
const getPermissionsString = perms => {
return `${perms.length} / ${allPermissions.length}`;
}
const getPermissionsString = perms => {
return `${perms.length} / ${allPermissions.length}`
}
</script> </script>
<div class="root"> <div class="root">
<ButtonGroup> <ButtonGroup>
<Button grouped color="secondary" on:click={createNewLevel}>Create New Access Level</Button> <Button grouped color="secondary" on:click={createNewLevel}>
</ButtonGroup> Create New Access Level
</Button>
</ButtonGroup>
{#if $store.accessLevels} {#if $store.accessLevels}
<table class="fields-table uk-table uk-table-small"> <table class="fields-table uk-table uk-table-small">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Permissions</th> <th>Permissions</th>
<th></th> <th />
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#each $store.accessLevels.levels as level} {#each $store.accessLevels.levels as level}
<tr> <tr>
<td >{level.name}</td> <td>{level.name}</td>
<td >{getPermissionsString(level.permissions)}</td> <td>{getPermissionsString(level.permissions)}</td>
<td class="edit-button"> <td class="edit-button">
<span on:click={() => onLevelEdit(level)}>{@html getIcon("edit")}</span> <span on:click={() => onLevelEdit(level)}>
<span on:click={() => onLevelDelete(level)}>{@html getIcon("trash")}</span> {@html getIcon('edit')}
</span>
<span on:click={() => onLevelDelete(level)}>
{@html getIcon('trash')}
</span>
</td> </td>
</tr> </tr>
{/each} {/each}
</tbody> </tbody>
</table> </table>
{:else} {:else}(no actions added){/if}
(no actions added)
{/if}
<Modal bind:isOpen={isEditing}> <Modal bind:isOpen={isEditing}>
{#if isEditing} {#if isEditing}
<AccessLevelView level={editingLevel} <AccessLevelView
allPermissions={allPermissions} level={editingLevel}
onFinished={onEditingFinished} {allPermissions}
isNew={editingLevelIsNew} onFinished={onEditingFinished}
allLevels={$store.accessLevels.levels} isNew={editingLevelIsNew}
hierarchy={$store.hierarchy} allLevels={$store.accessLevels.levels}
actions={$store.actions} /> hierarchy={$store.hierarchy}
{/if} actions={$store.actions} />
</Modal> {/if}
</Modal>
</div> </div>
<style> <style>
.root {
.root {
height: 100%; height: 100%;
position: relative; position: relative;
padding: 1.5rem; padding: 1.5rem;
}
} .actions-header {
.actions-header {
flex: 0 1 auto; flex: 0 1 auto;
} }
.node-view { .node-view {
overflow-y: auto; overflow-y: auto;
flex: 1 1 auto; flex: 1 1 auto;
} }
</style>
</style>

199
packages/builder/src/actionsAndTriggers/ActionView.svelte

@ -1,123 +1,134 @@
<script> <script>
import Textbox from "../common/Textbox.svelte"
import Textbox from "../common/Textbox.svelte"; import Button from "../common/Button.svelte"
import Button from "../common/Button.svelte"; import ButtonGroup from "../common/ButtonGroup.svelte"
import ButtonGroup from "../common/ButtonGroup.svelte"; import { cloneDeep, filter, keys, map, isUndefined } from "lodash/fp"
import {cloneDeep, filter, keys, import ErrorsBox from "../common/ErrorsBox.svelte"
map, isUndefined} from "lodash/fp"; import { validateActions, pipe } from "../common/core"
import ErrorsBox from "../common/ErrorsBox.svelte"; import getIcon from "../common/icon"
import {validateActions, pipe} from "../common/core";
import getIcon from "../common/icon"; export let action
export let onFinished = action => {}
export let action; export let allActions
export let onFinished = (action) => {}; export let isNew = true
export let allActions;
export let isNew = true; let optKey = ""
let optValue = ""
let optKey = "";
let optValue = ""; let clonedAction = cloneDeep(action)
let initialOptions = pipe(action.initialOptions, [
let clonedAction = cloneDeep(action);
let initialOptions = pipe(action.initialOptions, [
keys, keys,
map(k => ({key:k, value:action.initialOptions[k]})) map(k => ({ key: k, value: action.initialOptions[k] })),
]); ])
let errors = []; let errors = []
const addNewOption = () => { const addNewOption = () => {
if (
if(optKey && optValue && isUndefined(clonedAction.initialOptions[optKey])) { optKey &&
clonedAction.initialOptions[optKey] = optValue; optValue &&
initialOptions = [...initialOptions, { isUndefined(clonedAction.initialOptions[optKey])
key:optKey, value: optValue ) {
}]; clonedAction.initialOptions[optKey] = optValue
optKey = ""; initialOptions = [
optValue = ""; ...initialOptions,
} {
} key: optKey,
value: optValue,
const removeOption = (opt) => { },
if(opt) { ]
delete clonedAction.initialOptions[opt.key] optKey = ""
initialOptions = pipe(initialOptions, [ optValue = ""
filter(o => o.key !== opt.key)
]);
} }
} }
const save = () => { const removeOption = opt => {
if (opt) {
delete clonedAction.initialOptions[opt.key]
initialOptions = pipe(initialOptions, [filter(o => o.key !== opt.key)])
}
}
const save = () => {
const newActionsList = [ const newActionsList = [
...pipe(allActions ,[filter(a => a !== action)]), ...pipe(allActions, [filter(a => a !== action)]),
clonedAction] clonedAction,
]
errors = pipe(newActionsList ,[ errors = pipe(newActionsList, [validateActions, map(e => e.error)])
validateActions,
map(e => e.error)
]);
if(errors.length === 0) if (errors.length === 0) onFinished(clonedAction)
onFinished(clonedAction); }
}
const cancel = () => {
onFinished();
}
const cancel = () => {
onFinished()
}
</script> </script>
<div class="root"> <div class="root">
<ErrorsBox {errors} /> <ErrorsBox {errors} />
<form class="uk-form-horizontal"> <form class="uk-form-horizontal">
<Textbox label="Name" bind:text={clonedAction.name} /> <Textbox label="Name" bind:text={clonedAction.name} />
<Textbox label="Behaviour Source" bind:text={clonedAction.behaviourSource} /> <Textbox
<Textbox label="Behaviour" bind:text={clonedAction.behaviourName} /> label="Behaviour Source"
bind:text={clonedAction.behaviourSource} />
</form> <Textbox label="Behaviour" bind:text={clonedAction.behaviourName} />
<div class=" uk-form-stacked" style="margin-bottom: 20px"> </form>
<label class="uk-form-label">Default Options</label>
<div class="uk-grid-small" uk-grid> <div class=" uk-form-stacked" style="margin-bottom: 20px">
<input class="uk-input uk-width-1-4 uk-margin-right" placeholder="key" bind:value={optKey} > <label class="uk-form-label">Default Options</label>
<input class="uk-input uk-width-1-4 uk-margin-right" placeholder="value" bind:value={optValue} > <div class="uk-grid-small" uk-grid>
<Button color="primary-outline uk-width-1-4" on:click={addNewOption}>Add</Button> <input
</div> class="uk-input uk-width-1-4 uk-margin-right"
<div style="margin-top: 10px"> placeholder="key"
{#each initialOptions as option} bind:value={optKey} />
<span class="option-container">{option.key} : {option.value} <span style="font-size:10pt; cursor: pointer" on:click={() => removeOption(option)}>{@html getIcon("trash-2")}</span></span> <input
{/each} class="uk-input uk-width-1-4 uk-margin-right"
</div> placeholder="value"
bind:value={optValue} />
<Button color="primary-outline uk-width-1-4" on:click={addNewOption}>
Add
</Button>
</div>
<div style="margin-top: 10px">
{#each initialOptions as option}
<span class="option-container">
{option.key} : {option.value}
<span
style="font-size:10pt; cursor: pointer"
on:click={() => removeOption(option)}>
{@html getIcon('trash-2')}
</span>
</span>
{/each}
</div> </div>
</div>
<ButtonGroup> <ButtonGroup>
<Button color="secondary" grouped on:click={save}>Save</Button> <Button color="secondary" grouped on:click={save}>Save</Button>
<Button color="tertiary" grouped on:click={cancel}>Cancel</Button> <Button color="tertiary" grouped on:click={cancel}>Cancel</Button>
</ButtonGroup> </ButtonGroup>
</div> </div>
<style> <style>
.root {
.root {
padding: 2rem; padding: 2rem;
border-radius: 2rem; border-radius: 2rem;
} }
.uk-grid-small { .uk-grid-small {
padding: 1rem; padding: 1rem;
} }
.option-container { .option-container {
border-style: dotted; border-style: dotted;
border-width: 1px; border-width: 1px;
border-color: var(--primary75); border-color: var(--primary75);
padding: 3px; padding: 3px;
margin-right: 5px; margin-right: 5px;
} }
</style>
</style>

167
packages/builder/src/actionsAndTriggers/Actions.svelte

@ -1,112 +1,111 @@
<script> <script>
import getIcon from "../common/icon"
import getIcon from "../common/icon"; import { store } from "../builderStore"
import {store} from "../builderStore"; import Button from "../common/Button.svelte"
import Button from "../common/Button.svelte"; import ButtonGroup from "../common/ButtonGroup.svelte"
import ButtonGroup from "../common/ButtonGroup.svelte"; import ActionView from "./ActionView.svelte"
import ActionView from "./ActionView.svelte"; import Modal from "../common/Modal.svelte"
import Modal from "../common/Modal.svelte"; import { pipe } from "../common/core"
import {pipe} from "../common/core"; import { keys, map, join } from "lodash/fp"
import {keys, map, join} from "lodash/fp";
export let editingActionIsNew = false
export let editingActionIsNew = false; export let editingAction = null
export let editingAction = null; export let onActionEdit = action => {}
export let onActionEdit = (action) => {}; export let onActionDelete = action => {}
export let onActionDelete = (action) => {}; export let onActionSave = action => {}
export let onActionSave = (action) => {}; export let onActionCancel = () => {}
export let onActionCancel = () => {};
$: isEditing = editingAction !== null
$: isEditing = (editingAction !== null);
let actionsArray = []
let actionsArray = []; store.subscribe(s => {
store.subscribe(s => { actionsArray = pipe(s.actions, [keys, map(k => s.actions[k])])
actionsArray = pipe(s.actions, [ })
keys,
map(k => s.actions[k]) let getDefaultOptionsHtml = defaultOptions =>
]);
});
let getDefaultOptionsHtml = defaultOptions =>
pipe(defaultOptions, [ pipe(defaultOptions, [
keys, keys,
map(k => `<span style="color:var(--slate)">${k}: </span>${JSON.stringify(defaultOptions[k])}`), map(
join("<br>") k =>
]); `<span style="color:var(--slate)">${k}: </span>${JSON.stringify(
defaultOptions[k]
)}`
let actionEditingFinished = (action) => { ),
join("<br>"),
if(action) { ])
onActionSave(action)
let actionEditingFinished = action => {
if (action) {
onActionSave(action)
} else { } else {
onActionCancel(); onActionCancel()
} }
} }
</script> </script>
<h3 class="title">Actions</h3> <h3 class="title">Actions</h3>
{#if actionsArray} {#if actionsArray}
<table class="fields-table uk-table uk-table-small uk-table-striped"> <table class="fields-table uk-table uk-table-small uk-table-striped">
<thead> <thead>
<tr> <tr>
<th >Description</th> <th>Description</th>
<th>Behaviour Source</th> <th>Behaviour Source</th>
<th>Behaviour Name</th> <th>Behaviour Name</th>
<th>Default Options</th> <th>Default Options</th>
<th></th> <th />
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#each actionsArray as action} {#each actionsArray as action}
<tr> <tr>
<td class="table-content">{action.name}</td> <td class="table-content">{action.name}</td>
<td class="table-content">{action.behaviourSource}</td> <td class="table-content">{action.behaviourSource}</td>
<td class="table-content">{action.behaviourName}</td> <td class="table-content">{action.behaviourName}</td>
<td class="table-content">{@html getDefaultOptionsHtml(action.initialOptions)}</td> <td class="table-content">
<td class="edit-button"> {@html getDefaultOptionsHtml(action.initialOptions)}
<span on:click={() => onActionEdit(action)}>{@html getIcon("edit")}</span> </td>
<span on:click={() => onActionDelete(action)}>{@html getIcon("trash")}</span> <td class="edit-button">
</td> <span on:click={() => onActionEdit(action)}>
{@html getIcon('edit')}
</span>
<span on:click={() => onActionDelete(action)}>
{@html getIcon('trash')}
</span>
</td>
</tr> </tr>
{/each} {/each}
</tbody> </tbody>
</table> </table>
{:else} {:else}(no actions added){/if}
(no actions added)
{/if}
<Modal bind:isOpen={isEditing}> <Modal bind:isOpen={isEditing}>
{#if isEditing} {#if isEditing}
<ActionView action={editingAction} <ActionView
allActions={$store.actions} action={editingAction}
onFinished={actionEditingFinished} allActions={$store.actions}
isNew={editingActionIsNew}/> onFinished={actionEditingFinished}
{/if} isNew={editingActionIsNew} />
{/if}
</Modal> </Modal>
<style> <style>
.edit-button {
.edit-button { cursor: pointer;
cursor:pointer;
color: var(--secondary25); color: var(--secondary25);
} }
tr:hover .edit-button { tr:hover .edit-button {
color: var(--secondary75); color: var(--secondary75);
} }
.title { .title {
margin: 3rem 0rem 0rem 0rem; margin: 3rem 0rem 0rem 0rem;
font-weight: 700; font-weight: 700;
} }
.table-content { .table-content {
font-weight: 500; font-weight: 500;
font-size: .9rem; font-size: 0.9rem;
} }
</style>
</style>

225
packages/builder/src/actionsAndTriggers/ActionsAndTriggersRoot.svelte

@ -1,129 +1,130 @@
<script> <script>
import getIcon from "../common/icon"
import getIcon from "../common/icon"; import { store } from "../builderStore"
import {store} from "../builderStore"; import Button from "../common/Button.svelte"
import Button from "../common/Button.svelte"; import ButtonGroup from "../common/ButtonGroup.svelte"
import ButtonGroup from "../common/ButtonGroup.svelte"; import Actions from "./Actions.svelte"
import Actions from "./Actions.svelte"; import Triggers from "./Triggers.svelte"
import Triggers from "./Triggers.svelte"; import { getNewAction, getNewTrigger } from "../common/core"
import {getNewAction, getNewTrigger} from "../common/core";
let editingAction = null
let editingAction = null; let editingActionIsNew = true
let editingActionIsNew = true; let editingTrigger = null
let editingTrigger = null; let editingTriggerIsNew = true
let editingTriggerIsNew = true;
let getDefaultOptionsHtml = defaultOptions =>
let getDefaultOptionsHtml = defaultOptions =>
pipe(defaultOptions, [ pipe(defaultOptions, [
keys, keys,
map(k => `<span style="color:var(--slate)">${k}: </span>${JSON.parse(typeOptions[k])}`), map(
join("<br>") k =>
]); `<span style="color:var(--slate)">${k}: </span>${JSON.parse(
typeOptions[k]
let onActionEdit = (action) => { )}`
editingAction = action; ),
editingActionIsNew = false; join("<br>"),
} ])
let newAction = () => { let onActionEdit = action => {
editingAction = getNewAction(); editingAction = action
editingActionIsNew = true; editingActionIsNew = false
} }
let onActionDelete = (action) => { let newAction = () => {
store.deleteAction(action); editingAction = getNewAction()
} editingActionIsNew = true
}
let deleteTrigger = () => {}
let onActionDelete = action => {
let editTrigger = (trigger) => { store.deleteAction(action)
editingTrigger = trigger; }
editingTriggerIsNew = false;
} let deleteTrigger = () => {}
let newTrigger = () => { let editTrigger = trigger => {
editingTrigger = getNewTrigger(); editingTrigger = trigger
editingTriggerIsNew = true; editingTriggerIsNew = false
} }
let onActionSave = action => { let newTrigger = () => {
store.saveAction( editingTrigger = getNewTrigger()
action, editingTriggerIsNew = true
editingActionIsNew, }
editingAction);
let onActionSave = action => {
editingAction = null; store.saveAction(action, editingActionIsNew, editingAction)
}
editingAction = null
let onActionCancel = () => { }
editingAction = null;
} let onActionCancel = () => {
editingAction = null
let onTriggerSave = trigger => { }
store.saveTrigger(
trigger, let onTriggerSave = trigger => {
editingTriggerIsNew, store.saveTrigger(trigger, editingTriggerIsNew, editingTrigger)
editingTrigger);
editingTrigger = null
editingTrigger = null; }
}
let onTriggerCancel = () => {
let onTriggerCancel = () => { editingTrigger = null
editingTrigger = null; }
}
let onTriggerEdit = trigger => {
let onTriggerEdit = (trigger) => { editingTrigger = trigger
editingTrigger = trigger; editingTriggerIsNew = false
editingTriggerIsNew = false; }
}
let onTriggerDelete = trigger => {
store.deleteTrigger(trigger)
let onTriggerDelete = (trigger) => { }
store.deleteTrigger(trigger);
}
</script> </script>
<div class="root"> <div class="root">
<div class="actions-header"> <div class="actions-header">
<ButtonGroup> <ButtonGroup>
<Button color="secondary" <Button color="secondary" grouped on:click={newAction}>
grouped Create New Action
on:click={newAction}>Create New Action</Button> </Button>
<Button color="tertiary" <Button color="tertiary" grouped on:click={newTrigger}>
grouped Create New Trigger
on:click={newTrigger}>Create New Trigger</Button> </Button>
</ButtonGroup> </ButtonGroup>
</div> </div>
<div class="node-view"> <div class="node-view">
<Actions {editingActionIsNew} {editingAction} <Actions
{onActionEdit} {onActionDelete} {onActionSave} {editingActionIsNew}
{onActionCancel} /> {editingAction}
{onActionEdit}
<Triggers {editingTriggerIsNew} {editingTrigger} {onActionDelete}
{onTriggerEdit} {onTriggerDelete} {onTriggerSave} {onActionSave}
{onTriggerCancel} /> {onActionCancel} />
</div>
<Triggers
{editingTriggerIsNew}
{editingTrigger}
{onTriggerEdit}
{onTriggerDelete}
{onTriggerSave}
{onTriggerCancel} />
</div>
</div> </div>
<style> <style>
.root {
.root {
height: 100%; height: 100%;
position: relative; position: relative;
padding: 1.5rem; padding: 1.5rem;
}
} .actions-header {
.actions-header {
flex: 0 1 auto; flex: 0 1 auto;
} }
.node-view { .node-view {
overflow-y: auto; overflow-y: auto;
flex: 1 1 auto; flex: 1 1 auto;
} }
</style>
</style>

105
packages/builder/src/actionsAndTriggers/TriggerView.svelte

@ -1,68 +1,71 @@
<script> <script>
import Textbox from "../common/Textbox.svelte"
import Textbox from "../common/Textbox.svelte"; import Button from "../common/Button.svelte"
import Button from "../common/Button.svelte"; import Dropdown from "../common/Dropdown.svelte"
import Dropdown from "../common/Dropdown.svelte"; import ButtonGroup from "../common/ButtonGroup.svelte"
import ButtonGroup from "../common/ButtonGroup.svelte"; import CodeArea from "../common/CodeArea.svelte"
import CodeArea from "../common/CodeArea.svelte"; import { cloneDeep, filter, keys, some, map, isUndefined } from "lodash/fp"
import {cloneDeep, filter, keys, some, import ErrorsBox from "../common/ErrorsBox.svelte"
map, isUndefined} from "lodash/fp"; import { validateTriggers, pipe, events } from "../common/core"
import ErrorsBox from "../common/ErrorsBox.svelte"; import getIcon from "../common/icon"
import {validateTriggers, pipe, events} from "../common/core";
import getIcon from "../common/icon"; export let trigger
export let onFinished = action => {}
export let trigger; export let allTriggers
export let onFinished = (action) => {}; export let allActions
export let allTriggers; export let isNew = true
export let allActions;
export let isNew = true; let clonedTrigger = cloneDeep(trigger)
let errors = []
let clonedTrigger = cloneDeep(trigger); $: actionNames = map(a => a.name)(allActions)
let errors = [];
$: actionNames = map(a => a.name)(allActions); let cancel = () => onFinished()
let save = () => {
let cancel = () => onFinished();
let save = () => {
const newTriggersList = [ const newTriggersList = [
...pipe(allTriggers ,[filter(t => t !== trigger)]), ...pipe(allTriggers, [filter(t => t !== trigger)]),
clonedTrigger] clonedTrigger,
]
errors = validateTriggers(newTriggersList, allActions);
const test = map(t =>(!t.actionName || some(a => a.name === t.actionName)(allActions)))(newTriggersList) errors = validateTriggers(newTriggersList, allActions)
if(errors.length === 0) const test = map(
onFinished(clonedTrigger); t => !t.actionName || some(a => a.name === t.actionName)(allActions)
} )(newTriggersList)
if (errors.length === 0) onFinished(clonedTrigger)
}
</script> </script>
<div> <div>
<ErrorsBox {errors} style="margin-bottom:20px"/> <ErrorsBox {errors} style="margin-bottom:20px" />
<form class="uk-form-horizontal">
<form class="uk-form-horizontal"> <Dropdown
label="Event"
<Dropdown label="Event" options={['', ...events]}
options={["",...events]} bind:selected={clonedTrigger.eventName} />
bind:selected={clonedTrigger.eventName} /> <Dropdown
<Dropdown label="Action" label="Action"
options={["",...actionNames]} options={['', ...actionNames]}
bind:selected={clonedTrigger.actionName} /> bind:selected={clonedTrigger.actionName} />
<CodeArea label="Condition (javascript)" <CodeArea
bind:text={clonedTrigger.condition} /> label="Condition (javascript)"
<CodeArea label="Action Options Creator (javascript)" bind:text={clonedTrigger.condition} />
bind:text={clonedTrigger.optionsCreator} /> <CodeArea
label="Action Options Creator (javascript)"
bind:text={clonedTrigger.optionsCreator} />
</form> </form>
<ButtonGroup> <ButtonGroup>
<Button color="primary" grouped on:click={save}>Save</Button> <Button color="primary" grouped on:click={save}>Save</Button>
<Button color="tertiary" grouped on:click={cancel}>Cancel</Button> <Button color="tertiary" grouped on:click={cancel}>Cancel</Button>
</ButtonGroup> </ButtonGroup>
</div> </div>
<style> <style>
</style> </style>

130
packages/builder/src/actionsAndTriggers/Triggers.svelte

@ -1,94 +1,90 @@
<script> <script>
import { store } from "../builderStore"
import {store} from "../builderStore"; import getIcon from "../common/icon"
import getIcon from "../common/icon"; import Button from "../common/Button.svelte"
import Button from "../common/Button.svelte"; import Modal from "../common/Modal.svelte"
import Modal from "../common/Modal.svelte"; import TriggerView from "./TriggerView.svelte"
import TriggerView from "./TriggerView.svelte";
export let editingTrigger = null
export let editingTriggerIsNew = true
export let editingTrigger = null; export let onTriggerEdit = trigger => {}
export let editingTriggerIsNew = true; export let onTriggerDelete = trigger => {}
export let onTriggerEdit = (trigger) => {}; export let onTriggerSave = trigger => {}
export let onTriggerDelete = (trigger) => {}; export let onTriggerCancel = () => {}
export let onTriggerSave = (trigger) => {};
export let onTriggerCancel = () => {}; $: isEditing = editingTrigger !== null
$: isEditing = (editingTrigger !== null); let triggerEditingFinished = trigger => {
if (trigger) {
let triggerEditingFinished = (trigger) => { onTriggerSave(trigger)
if(trigger) {
onTriggerSave(trigger)
} else { } else {
onTriggerCancel(); onTriggerCancel()
} }
} }
</script> </script>
<h3 class="title">Triggers</h3> <h3 class="title">Triggers</h3>
{#if $store.triggers} {#if $store.triggers}
<table class="fields-table uk-table uk-table-small uk-table-striped"> <table class="fields-table uk-table uk-table-small uk-table-striped">
<thead> <thead>
<tr> <tr>
<th>Event</th> <th>Event</th>
<th>Action</th> <th>Action</th>
<th>Condition</th> <th>Condition</th>
<th>Create Options</th> <th>Create Options</th>
<th></th> <th />
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#each $store.triggers as trigger} {#each $store.triggers as trigger}
<tr> <tr>
<td class="table-content">{trigger.eventName}</td> <td class="table-content">{trigger.eventName}</td>
<td class="table-content">{trigger.actionName}</td> <td class="table-content">{trigger.actionName}</td>
<td class="table-content">{trigger.condition}</td> <td class="table-content">{trigger.condition}</td>
<td class="table-content">{trigger.optionsCreator}</td> <td class="table-content">{trigger.optionsCreator}</td>
<td class="edit-button"> <td class="edit-button">
<span on:click={() => onTriggerEdit(trigger)}>{@html getIcon("edit")}</span> <span on:click={() => onTriggerEdit(trigger)}>
<span on:click={() => onTriggerDelete(trigger)}>{@html getIcon("trash")}</span> {@html getIcon('edit')}
</td> </span>
<span on:click={() => onTriggerDelete(trigger)}>
{@html getIcon('trash')}
</span>
</td>
</tr> </tr>
{/each} {/each}
</tbody> </tbody>
</table> </table>
{:else} {:else}(no triggers added){/if}
(no triggers added)
{/if}
<Modal bind:isOpen={isEditing}> <Modal bind:isOpen={isEditing}>
{#if isEditing} {#if isEditing}
<TriggerView trigger={editingTrigger} <TriggerView
allActions={$store.actions} trigger={editingTrigger}
allTriggers={$store.triggers} allActions={$store.actions}
onFinished={triggerEditingFinished} allTriggers={$store.triggers}
isNew={editingTriggerIsNew}/> onFinished={triggerEditingFinished}
{/if} isNew={editingTriggerIsNew} />
{/if}
</Modal> </Modal>
<style> <style>
.edit-button {
.edit-button { cursor: pointer;
cursor:pointer;
color: var(--secondary25); color: var(--secondary25);
} }
.title { .title {
margin: 3rem 0rem 0rem 0rem; margin: 3rem 0rem 0rem 0rem;
font-weight: 700; font-weight: 700;
} }
.table-content { .table-content {
font-weight: 500; font-weight: 500;
font-size: .9rem; font-size: 0.9rem;
} }
tr:hover .edit-button { tr:hover .edit-button {
color: var(--secondary75); color: var(--secondary75);
} }
</style>
</style>

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

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

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

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

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

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

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

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

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

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

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

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

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

File diff suppressed because it is too large

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

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

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

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

34
packages/builder/src/common/ActionButton.svelte

@ -1,11 +1,22 @@
<script> <script>
export let disabled = false; export let disabled = false
export let hidden = false; export let hidden = false
export let primary = true; export let primary = true
export let alert = false; export let alert = false
export let warning = false; export let warning = false
</script> </script>
<button
on:click
class="button"
class:hidden
class:primary
class:alert
class:warning
{disabled}>
<slot />
</button>
<style> <style>
.primary { .primary {
color: #0055ff; color: #0055ff;
@ -14,7 +25,7 @@
.alert { .alert {
color: rgba(255, 0, 31, 1); color: rgba(255, 0, 31, 1);
background: rgba(255, 0, 31, 0.1);; background: rgba(255, 0, 31, 0.1);
} }
.button { .button {
@ -40,14 +51,3 @@
visibility: hidden; visibility: hidden;
} }
</style> </style>
<button
on:click
class="button"
class:hidden
class:primary
class:alert
class:warning
{disabled}>
<slot />
</button>

166
packages/builder/src/common/Button.svelte

@ -1,163 +1,167 @@
<script> <script>
export let color = "primary"
export let className = ""
export let style = ""
export let groupPosition = ""
export let grouped = false
export let color = "primary"; $: borderClass = grouped ? "" : "border-normal"
export let className = "";
export let style = "";
export let groupPosition = "";
export let grouped = false;
$: borderClass = grouped
? ""
: "border-normal";
</script> </script>
<button class="{color} {className} {borderClass} {grouped ? "grouped" : ""}" <button
style="{style}" class="{color}
on:click > {className}
<slot/> {borderClass}
{grouped ? 'grouped' : ''}"
{style}
on:click>
<slot />
</button> </button>
<style> <style>
.border-normal {
.border-normal { border-radius: var(--borderradiusall); } border-radius: var(--borderradiusall);
.border-left { border-radius: var(--borderradius) 0 0 var(--borderradius); } }
.border-right { border-radius: 0 var(--borderradius) var(--borderradius) 0; } .border-left {
.border-middle { border-radius: 0; } border-radius: var(--borderradius) 0 0 var(--borderradius);
}
button { .border-right {
border-style: solid; border-radius: 0 var(--borderradius) var(--borderradius) 0;
}
.border-middle {
border-radius: 0;
}
button {
border-style: solid;
padding: 7.5px 15px; padding: 7.5px 15px;
cursor:pointer; cursor: pointer;
margin:5px; margin: 5px;
border-radius: 5px; border-radius: 5px;
} }
/* ---- PRIMARY ----*/ /* ---- PRIMARY ----*/
.primary { .primary {
background-color: var(--primary100); background-color: var(--primary100);
border-color: var(--primary100); border-color: var(--primary100);
color: var(--white); color: var(--white);
} }
.primary:hover { .primary:hover {
background-color: var(--primary75); background-color: var(--primary75);
border-color: var(--primary75); border-color: var(--primary75);
} }
.primary:active { .primary:active {
background-color: var(--primarydark); background-color: var(--primarydark);
border-color: var(--primarydark); border-color: var(--primarydark);
} }
.primary-outline { .primary-outline {
background-color: var(--white); background-color: var(--white);
border-color: var(--primary100); border-color: var(--primary100);
color: var(--primary100); color: var(--primary100);
} }
.primary-outline:hover { .primary-outline:hover {
background-color: var(--primary10); background-color: var(--primary10);
} }
.primary-outline:pressed { .primary-outline:pressed {
background-color: var(--primary25); background-color: var(--primary25);
} }
/* ---- secondary ----*/ /* ---- secondary ----*/
.secondary { .secondary {
background-color: var(--secondary100); background-color: var(--secondary100);
border-color: var(--secondary100); border-color: var(--secondary100);
color: var(--white); color: var(--white);
} }
.secondary:hover { .secondary:hover {
background-color: var(--secondary75); background-color: var(--secondary75);
border-color: var(--secondary75); border-color: var(--secondary75);
} }
.secondary:pressed { .secondary:pressed {
background-color: var(--secondarydark); background-color: var(--secondarydark);
border-color: var(--secondarydark); border-color: var(--secondarydark);
} }
.secondary-outline { .secondary-outline {
background-color: var(--white); background-color: var(--white);
border-color: var(--secondary100); border-color: var(--secondary100);
color: var(--secondary100); color: var(--secondary100);
} }
.secondary-outline:hover { .secondary-outline:hover {
background-color: var(--secondary10); background-color: var(--secondary10);
} }
.secondary-outline:pressed { .secondary-outline:pressed {
background-color: var(--secondary25); background-color: var(--secondary25);
} }
/* ---- success ----*/
/* ---- success ----*/ .success {
.success {
background-color: var(--success100); background-color: var(--success100);
border-color: var(--success100); border-color: var(--success100);
color: var(--white); color: var(--white);
} }
.success:hover { .success:hover {
background-color: var(--success75); background-color: var(--success75);
border-color: var(--success75); border-color: var(--success75);
} }
.success:pressed { .success:pressed {
background-color: var(--successdark); background-color: var(--successdark);
border-color: var(--successdark); border-color: var(--successdark);
} }
.success-outline { .success-outline {
background-color: var(--white); background-color: var(--white);
border-color: var(--success100); border-color: var(--success100);
color: var(--success100); color: var(--success100);
} }
.success-outline:hover { .success-outline:hover {
background-color: var(--success10); background-color: var(--success10);
} }
.success-outline:pressed { .success-outline:pressed {
background-color: var(--success25); background-color: var(--success25);
} }
/* ---- deletion ----*/ /* ---- deletion ----*/
.deletion { .deletion {
background-color: var(--deletion100); background-color: var(--deletion100);
border-color: var(--deletion100); border-color: var(--deletion100);
color: var(--white); color: var(--white);
} }
.deletion:hover { .deletion:hover {
background-color: var(--deletion75); background-color: var(--deletion75);
border-color: var(--deletion75); border-color: var(--deletion75);
} }
.deletion:pressed { .deletion:pressed {
background-color: var(--deletiondark); background-color: var(--deletiondark);
border-color: var(--deletiondark); border-color: var(--deletiondark);
} }
.deletion-outline { .deletion-outline {
background-color: var(--white); background-color: var(--white);
border-color: var(--deletion100); border-color: var(--deletion100);
color: var(--deletion100); color: var(--deletion100);
} }
.deletion-outline:hover { .deletion-outline:hover {
background-color: var(--deletion10); background-color: var(--deletion10);
} }
.deletion-outline:pressed { .deletion-outline:pressed {
background-color: var(--deletion25); background-color: var(--deletion25);
} }
</style>
</style>

28
packages/builder/src/common/ButtonGroup.svelte

@ -1,27 +1,25 @@
<script> <script>
export let style=""; export let style = ""
</script> </script>
<div class="root" style={style}> <div class="root" {style}>
<slot /> <slot />
</div> </div>
<style> <style>
.root {
.root {
display: flex; display: flex;
} }
.root:last-child { .root:last-child {
border-radius: 0 var(--borderradius) var(--borderradius) 0; border-radius: 0 var(--borderradius) var(--borderradius) 0;
} }
.root:first-child { .root:first-child {
border-radius: var(--borderradius) 0 0 var(--borderradius); border-radius: var(--borderradius) 0 0 var(--borderradius);
} }
.root:not(:first-child):not(:last-child) { .root:not(:first-child):not(:last-child) {
border-radius: 0; border-radius: 0;
} }
</style>
</style>

19
packages/builder/src/common/Checkbox.svelte

@ -1,16 +1,13 @@
<script> <script>
export let checked = false
export let checked=false; export let label = ""
export let label="";
</script> </script>
<input class="uk-checkbox" type="checkbox" bind:checked on:change />{label} <input class="uk-checkbox" type="checkbox" bind:checked on:change />
{label}
<style> <style>
input {
input { margin-right: 7px;
margin-right:7px; }
} </style>
</style>

28
packages/builder/src/common/CodeArea.svelte

@ -1,24 +1,22 @@
<script> <script>
// todo: use https://ace.c9.io // todo: use https://ace.c9.io
export let text = ""; export let text = ""
export let label = ""; export let label = ""
</script> </script>
<div>{label}</div> <div>{label}</div>
<textarea class="uk-textarea" bind:value={text}></textarea> <textarea class="uk-textarea" bind:value={text} />
<style> <style>
textarea {
textarea { padding: 3px;
padding:3px; margin-top: 5px;
margin-top:5px; margin-bottom: 10px;
margin-bottom:10px;
background: var(--lightslate); background: var(--lightslate);
color: var(--white); color: var(--white);
font-family: 'Courier New', Courier, monospace; font-family: "Courier New", Courier, monospace;
width:95%; width: 95%;
height:100px; height: 100px;
border-radius: 5px; border-radius: 5px;
} }
</style>
</style>

14
packages/builder/src/common/ComingSoon.svelte

@ -1,17 +1,13 @@
<script> <script>
export let name = ""
export let name = "";
</script> </script>
<div> <div>
<h4>Coming Sometime: {name}</h4> <h4>Coming Sometime: {name}</h4>
</div> </div>
<style> <style>
h4 {
h4 {
margin-top: 20px; margin-top: 20px;
} }
</style>
</style>

46
packages/builder/src/common/DatePicker.svelte

@ -1,36 +1,34 @@
<script> <script>
import flatpickr from "flatpickr"
import "flatpickr/dist/flatpickr.css"
import { onMount } from "svelte"
import flatpickr from "flatpickr"; export let value
import "flatpickr/dist/flatpickr.css"; export let label
import { onMount } from 'svelte'; export let width = "medium"
export let size = "small"
export let value; let input
export let label; let fpInstance
export let width = "medium";
export let size = "small";
let input; $: if (fpInstance) fpInstance.setDate(value)
let fpInstance;
$: if (fpInstance) fpInstance.setDate(value); onMount(() => {
fpInstance = flatpickr(input, {})
onMount(() => {
fpInstance = flatpickr(input, {});
fpInstance.config.onChange.push(selectedDates => { fpInstance.config.onChange.push(selectedDates => {
if(selectedDates.length > 0) if (selectedDates.length > 0) value = new Date(selectedDates[0])
value = new Date(selectedDates[0]); })
});
return fpInstance;
})
return fpInstance
})
</script> </script>
<div class="uk-margin"> <div class="uk-margin">
<label class="uk-form-label">{label}</label> <label class="uk-form-label">{label}</label>
<div class="uk-form-controls"> <div class="uk-form-controls">
<input class="uk-input uk-form-width-{width} uk-form-{size}" bind:this={input} > <input
</div> class="uk-input uk-form-width-{width} uk-form-{size}"
bind:this={input} />
</div>
</div> </div>

73
packages/builder/src/common/Dropdown.svelte

@ -1,39 +1,44 @@
<script> <script>
import {createEventDispatcher} from "svelte"; import { createEventDispatcher } from "svelte"
export let selected; export let selected
export let label; export let label
export let options; export let options
export let valueMember; export let valueMember
export let textMember; export let textMember
export let multiple=false; export let multiple = false
export let width = "medium"; export let width = "medium"
export let size = "small"; export let size = "small"
const dispatch =createEventDispatcher(); const dispatch = createEventDispatcher()
</script> </script>
<div class="uk-margin"> <div class="uk-margin">
<label class="uk-form-label">{label}</label> <label class="uk-form-label">{label}</label>
<div class="uk-form-controls"> <div class="uk-form-controls">
{#if multiple} {#if multiple}
<select
<select class="uk-select uk-form-width-{width} uk-form-{size}" multiple bind:value={selected} on:change> class="uk-select uk-form-width-{width} uk-form-{size}"
{#each options as option} multiple
<option value={!valueMember ? option : valueMember(option)}>{!textMember ? option : textMember(option)}</option> bind:value={selected}
{/each} on:change>
</select> {#each options as option}
<option value={!valueMember ? option : valueMember(option)}>
{:else} {!textMember ? option : textMember(option)}
</option>
<select class="uk-select uk-form-width-{width} uk-form-{size}" bind:value={selected} on:change> {/each}
{#each options as option} </select>
<option value={!valueMember ? option : valueMember(option)}>{!textMember ? option : textMember(option)}</option> {:else}
{/each} <select
</select> class="uk-select uk-form-width-{width} uk-form-{size}"
{/if} bind:value={selected}
</div> on:change>
{#each options as option}
<option value={!valueMember ? option : valueMember(option)}>
{!textMember ? option : textMember(option)}
</option>
{/each}
</select>
{/if}
</div>
</div> </div>

79
packages/builder/src/common/DropdownButton.svelte

@ -1,68 +1,65 @@
<script> <script>
import getIcon from "./icon"
import getIcon from "./icon"; export let iconName
export let iconName; export let actions = [] // [ {label: "Action Name", onclick: () => {...} } ]
export let actions = []; // [ {label: "Action Name", onclick: () => {...} } ] let isDroppedDown = false
let isDroppedDown = false;
</script> </script>
<div class="root" on:click={() => (isDroppedDown = !isDroppedDown)}>
{@html getIcon(iconName)}
<div class="root" on:click={() => isDroppedDown = !isDroppedDown}> <div
{@html getIcon(iconName)} class="dropdown-background"
on:click|stopPropagation={() => (isDroppedDown = false)}
<div class="dropdown-background" on:click|stopPropagation={() => isDroppedDown = false} style="display: {isDroppedDown ? 'block' : 'none'}"></div> style="display: {isDroppedDown ? 'block' : 'none'}" />
<div class="dropdown-content" style="display: {isDroppedDown ? 'inline-block' : 'none'}"> <div
{#each actions as action} class="dropdown-content"
<div class="action-row" on:click={action.onclick}> style="display: {isDroppedDown ? 'inline-block' : 'none'}">
{action.label} {#each actions as action}
</div> <div class="action-row" on:click={action.onclick}>{action.label}</div>
{/each} {/each}
</div> </div>
</div>
</div>
<style> <style>
.dropdown-background {
.dropdown-background {
position: fixed; position: fixed;
top:0; top: 0;
left:0; left: 0;
width:100vw; width: 100vw;
height:100vh; height: 100vh;
} }
.root { .root {
cursor: pointer; cursor: pointer;
z-index: 1; z-index: 1;
} }
.dropdown-content { .dropdown-content {
position: absolute; position: absolute;
background-color: var(--white); background-color: var(--white);
min-width: 160px; min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
z-index: 1; z-index: 1;
font-weight: normal; font-weight: normal;
border-style: solid; border-style: solid;
border-width: 1px; border-width: 1px;
border-color: var(--secondary10); border-color: var(--secondary10);
} }
.dropdown-content:not(:focus) { .dropdown-content:not(:focus) {
display: none; display: none;
} }
.action-row { .action-row {
padding: 7px 10px; padding: 7px 10px;
cursor:pointer; cursor: pointer;
} }
.action-row:hover { .action-row:hover {
background-color: var(--primary100); background-color: var(--primary100);
color:var(--white); color: var(--white);
} }
</style>
</style>

26
packages/builder/src/common/ErrorsBox.svelte

@ -1,29 +1,29 @@
<script> <script>
export let errors = []; export let errors = []
$:hasErrors = errors.length > 0; $: hasErrors = errors.length > 0
</script> </script>
{#if hasErrors} {#if hasErrors}
<div class="error-container"> <div class="error-container">
{#each errors as error} {#each errors as error}
<div class="error-row">{error.field ? `${error.field}: ` : ""}{error.error}</div> <div class="error-row">
{error.field ? `${error.field}: ` : ''}{error.error}
</div>
{/each} {/each}
</div> </div>
{/if} {/if}
<style> <style>
.error-container {
.error-container { padding: 10px;
padding:10px;
border-style: solid; border-style: solid;
border-color: var(--deletion100); border-color: var(--deletion100);
border-radius: var(--borderradiusall); border-radius: var(--borderradiusall);
background: var(--deletion75); background: var(--deletion75);
} }
.error-row { .error-row {
padding: 5px 0px; padding: 5px 0px;
} }
</style>
</style>

90
packages/builder/src/common/IconButton.svelte

@ -1,63 +1,59 @@
<script> <script>
import getIcon from "./icon"; import getIcon from "./icon"
export let size = 18; export let size = 18
export let icon = ""; export let icon = ""
export let style = ""; export let style = ""
export let color = "var(--secondary100)"; export let color = "var(--secondary100)"
export let hoverColor = "var(--secondary75)"; export let hoverColor = "var(--secondary75)"
export let attributes = {}; export let attributes = {}
let currentAttributes = []; let currentAttributes = []
const addAttributes = (node, attributes) => { const addAttributes = (node, attributes) => {
const add = _attributes => {
const add = (_attributes) => { const attrs = []
const attrs = []; for (let attr in _attributes) {
for(let attr in _attributes) { node.setAttribute(attr, _attributes[attr])
node.setAttribute(attr, _attributes[attr]); attrs.push("uk-toggle")
attrs.push("uk-toggle") }
} currentAttributes = attrs
currentAttributes = attrs;
} }
add(attributes); add(attributes)
return { return {
// should implement update method // should implement update method
update(attributes) { update(attributes) {
for(let attr of currentAttributes) { for (let attr of currentAttributes) {
node.removeAttribute(attr) node.removeAttribute(attr)
} }
add(attributes); add(attributes)
}, },
destroy() {} destroy() {},
} }
} }
</script> </script>
<button style="{style}{style ? ";" : ""} color:{color}; --hovercolor:{hoverColor}" <button
on:click style="{style}{style ? ';' : ''} color:{color}; --hovercolor:{hoverColor}"
use:addAttributes={attributes}> on:click
{@html getIcon(icon, size)} use:addAttributes={attributes}>
{@html getIcon(icon, size)}
</button> </button>
<style> <style>
button {
button {
border-style: none; border-style: none;
background-color: rgba(0,0,0,0); background-color: rgba(0, 0, 0, 0);
cursor: pointer; cursor: pointer;
outline:none; outline: none;
} }
button:hover { button:hover {
color: var(--hovercolor); color: var(--hovercolor);
} }
button:active {
outline:none;
}
</style> button:active {
outline: none;
}
</style>

12
packages/builder/src/common/Icons/ArrowDown.svelte

@ -1,4 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"> <svg
<path fill="none" d="M0 0h24v24H0z"/> xmlns="http://www.w3.org/2000/svg"
<path fill="currentColor" d="M12 13.172l4.95-4.95 1.414 1.414L12 16 5.636 9.636 7.05 8.222z"/> viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M12 13.172l4.95-4.95 1.414 1.414L12 16 5.636 9.636 7.05 8.222z" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 228 B

After

Width:  |  Height:  |  Size: 246 B

4
packages/builder/src/common/Icons/CircleIndicator.svelte

@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8" width="8" height="8"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8" width="8" height="8">
<circle cx="4" cy="4" r="4" stroke-width="0" fill="currentColor" /> <circle cx="4" cy="4" r="4" stroke-width="0" fill="currentColor" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 158 B

After

Width:  |  Height:  |  Size: 157 B

15
packages/builder/src/common/Icons/Image.svelte

@ -1,4 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"> <svg
<path fill="none" d="M0 0h24v24H0z"/> xmlns="http://www.w3.org/2000/svg"
<path fill="currentColor" d="M4.828 21l-.02.02-.021-.02H2.992A.993.993 0 0 1 2 20.007V3.993A1 1 0 0 1 2.992 3h18.016c.548 0 .992.445.992.993v16.014a1 1 0 0 1-.992.993H4.828zM20 15V5H4v14L14 9l6 6zm0 2.828l-6-6L6.828 19H20v-1.172zM8 11a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/> viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M4.828 21l-.02.02-.021-.02H2.992A.993.993 0 0 1 2 20.007V3.993A1 1 0 0 1
2.992 3h18.016c.548 0 .992.445.992.993v16.014a1 1 0 0 1-.992.993H4.828zM20
15V5H4v14L14 9l6 6zm0 2.828l-6-6L6.828 19H20v-1.172zM8 11a2 2 0 1 1 0-4 2 2
0 0 1 0 4z" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 400 B

After

Width:  |  Height:  |  Size: 430 B

14
packages/builder/src/common/Icons/Input.svelte

@ -1,4 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"> <svg
<path fill="none" d="M0 0h24v24H0z"/> xmlns="http://www.w3.org/2000/svg"
<path fill="currentColor" d="M5 5v14h14V5H5zM4 3h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm5.869 12l-.82 2H6.833L11 7h2l4.167 10H14.95l-.82-2H9.87zm.82-2h2.622L12 9.8 10.689 13z"/> viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M5 5v14h14V5H5zM4 3h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1
1 0 0 1 1-1zm5.869 12l-.82 2H6.833L11 7h2l4.167
10H14.95l-.82-2H9.87zm.82-2h2.622L12 9.8 10.689 13z" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 339 B

After

Width:  |  Height:  |  Size: 365 B

12
packages/builder/src/common/Icons/Layout.svelte

@ -1,3 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/> <svg
<path fill="currentColor" d="M21 20a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16zM11 5H5v14h6V5zm8 8h-6v6h6v-6zm0-8h-6v6h6V5z"/> xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M21 20a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h16a1 1 0 0 1 1
1v16zM11 5H5v14h6V5zm8 8h-6v6h6v-6zm0-8h-6v6h6V5z" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 280 B

After

Width:  |  Height:  |  Size: 305 B

14
packages/builder/src/common/Icons/Paint.svelte

@ -1,3 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/> <svg
<path fill="currentColor" d="M19.228 18.732l1.768-1.768 1.767 1.768a2.5 2.5 0 1 1-3.535 0zM8.878 1.08l11.314 11.313a1 1 0 0 1 0 1.415l-8.485 8.485a1 1 0 0 1-1.414 0l-8.485-8.485a1 1 0 0 1 0-1.415l7.778-7.778-2.122-2.121L8.88 1.08zM11 6.03L3.929 13.1 11 20.173l7.071-7.071L11 6.029z"/> xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M19.228 18.732l1.768-1.768 1.767 1.768a2.5 2.5 0 1 1-3.535 0zM8.878
1.08l11.314 11.313a1 1 0 0 1 0 1.415l-8.485 8.485a1 1 0 0 1-1.414
0l-8.485-8.485a1 1 0 0 1 0-1.415l7.778-7.778-2.122-2.121L8.88 1.08zM11
6.03L3.929 13.1 11 20.173l7.071-7.071L11 6.029z" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 415 B

After

Width:  |  Height:  |  Size: 448 B

2
packages/builder/src/common/Icons/Pencil.svelte

@ -16,4 +16,4 @@
svg:hover { svg:hover {
cursor: pointer; cursor: pointer;
} }
</style> </style>

Before

Width:  |  Height:  |  Size: 446 B

After

Width:  |  Height:  |  Size: 447 B

14
packages/builder/src/common/Icons/Terminal.svelte

@ -1,4 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"> <svg
<path fill="none" d="M0 0h24v24H0z"/> xmlns="http://www.w3.org/2000/svg"
<path fill="currentColor" d="M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm1 2v14h16V5H4zm8 10h6v2h-6v-2zm-3.333-3L5.838 9.172l1.415-1.415L11.495 12l-4.242 4.243-1.415-1.415L8.667 12z"/> viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm1
2v14h16V5H4zm8 10h6v2h-6v-2zm-3.333-3L5.838 9.172l1.415-1.415L11.495
12l-4.242 4.243-1.415-1.415L8.667 12z" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 346 B

After

Width:  |  Height:  |  Size: 372 B

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

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

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

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

6
packages/builder/src/common/Input.svelte

@ -1,7 +1,9 @@
<script> <script>
export let value = ""; export let value = ""
</script> </script>
<input type="text" on:change bind:value />
<style> <style>
input { input {
display: block; display: block;
@ -23,5 +25,3 @@
height: 50px; height: 50px;
} }
</style> </style>
<input type="text" on:change bind:value />

34
packages/builder/src/common/Inputs/InputGroup.svelte

@ -1,29 +1,29 @@
<script> <script>
export let meta = []; export let meta = []
export let size = ''; export let size = ""
export let values = []; export let values = []
export let type = "number"; export let type = "number"
export let onStyleChanged = () => {}; export let onStyleChanged = () => {}
let _values = values.map(v => v); let _values = values.map(v => v)
$: onStyleChanged(_values); $: onStyleChanged(_values)
</script> </script>
<div class="inputs {size}"> <div class="inputs {size}">
{#each meta as { placeholder }, i} {#each meta as { placeholder }, i}
<input {type} <input
placeholder="{placeholder}" {type}
value={values[i]} {placeholder}
on:input={(e) => _values[i] = e.target.value} /> value={values[i]}
on:input={e => (_values[i] = e.target.value)} />
{/each} {/each}
</div> </div>
<style> <style>
.inputs { .inputs {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
input { input {
@ -34,18 +34,18 @@
opacity: 0.7; opacity: 0.7;
padding: 5px 10px; padding: 5px 10px;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid #DBDBDB; border: 1px solid #dbdbdb;
border-radius: 2px; border-radius: 2px;
outline: none; outline: none;
} }
input[type=number]::-webkit-inner-spin-button, input[type="number"]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button { input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none; -moz-appearance: none;
appearance: none; appearance: none;
margin: 0; margin: 0;
} }
.small > input { .small > input {
width: 38px; width: 38px;

38
packages/builder/src/common/Modal.svelte

@ -1,29 +1,38 @@
<script> <script>
import UIkit from "uikit"; import UIkit from "uikit"
export let isOpen = false; export let isOpen = false
export let onClosed = () => {}; export let onClosed = () => {}
export let id = ""; export let id = ""
let ukModal; let ukModal
let listenerAdded = false; let listenerAdded = false
$: { $: {
if (ukModal && !listenerAdded) { if (ukModal && !listenerAdded) {
listenerAdded = true; listenerAdded = true
ukModal.addEventListener("hidden", onClosed); ukModal.addEventListener("hidden", onClosed)
} }
if (ukModal) { if (ukModal) {
if (isOpen) { if (isOpen) {
UIkit.modal(ukModal).show(); UIkit.modal(ukModal).show()
} else { } else {
UIkit.modal(ukModal).hide(); UIkit.modal(ukModal).hide()
} }
} }
} }
</script> </script>
<div bind:this={ukModal} uk-modal {id}>
<div class="uk-modal-dialog uk-modal-body" uk-overflow-auto>
{#if onClosed}
<button class="uk-modal-close-default" type="button" uk-close />
{/if}
<slot />
</div>
</div>
<style> <style>
.uk-modal-dialog { .uk-modal-dialog {
border-radius: 0.3rem; border-radius: 0.3rem;
@ -33,12 +42,3 @@
flex-direction: column; flex-direction: column;
} }
</style> </style>
<div bind:this={ukModal} uk-modal {id}>
<div class="uk-modal-dialog uk-modal-body" uk-overflow-auto>
{#if onClosed}
<button class="uk-modal-close-default" type="button" uk-close />
{/if}
<slot />
</div>
</div>

29
packages/builder/src/common/NumberBox.svelte

@ -1,26 +1,21 @@
<script> <script>
export let value
export let label
export let value; const inputChanged = ev => {
export let label;
const inputChanged = ev => {
try { try {
value = Number(ev.target.value); value = Number(ev.target.value)
} catch(_) { } catch (_) {
value = null; value = null
} }
} }
let numberText = value === null || value === undefined
? "" : value.toString();
let numberText = value === null || value === undefined ? "" : value.toString()
</script> </script>
<div class="uk-margin"> <div class="uk-margin">
<label class="uk-form-label">{label}</label> <label class="uk-form-label">{label}</label>
<div class="uk-form-controls"> <div class="uk-form-controls">
<input class="uk-input" value={value} on:change={inputChanged} > <input class="uk-input" {value} on:change={inputChanged} />
</div> </div>
</div> </div>

4
packages/builder/src/common/PlusButton.svelte

@ -1,3 +1,5 @@
<button on:click>+</button>
<style> <style>
button { button {
cursor: pointer; cursor: pointer;
@ -19,5 +21,3 @@
color: rgba(22, 48, 87, 1); color: rgba(22, 48, 87, 1);
} }
</style> </style>
<button on:click>+</button>

22
packages/builder/src/common/Select.svelte

@ -1,8 +1,17 @@
<script> <script>
import getIcon from "./icon"; import getIcon from "./icon"
export let value; export let value
</script> </script>
<div class="select-container">
<select on:change {value}>
<slot />
</select>
<span class="arrow">
{@html getIcon('chevron-down', '24')}
</span>
</div>
<style> <style>
.select-container { .select-container {
padding-bottom: 10px; padding-bottom: 10px;
@ -44,12 +53,3 @@
color: var(--primary100); color: var(--primary100);
} }
</style> </style>
<div class="select-container">
<select on:change {value}>
<slot />
</select>
<span class="arrow">
{@html getIcon('chevron-down', '24')}
</span>
</div>

42
packages/builder/src/common/Textbox.svelte

@ -1,32 +1,32 @@
<script> <script>
export let text = ""; export let text = ""
export let label = ""; export let label = ""
export let width = "medium"; export let width = "medium"
export let size = "small"; export let size = "small"
export let margin = true; export let margin = true
export let infoText = ""; export let infoText = ""
export let hasError = false; export let hasError = false
export let disabled = false; export let disabled = false
</script> </script>
<div class:uk-margin={margin}> <div class:uk-margin={margin}>
<label class="uk-form-label">{label}</label> <label class="uk-form-label">{label}</label>
<div class="uk-form-controls"> <div class="uk-form-controls">
<input class="uk-input uk-form-width-{width} uk-form-{size}" <input
class:uk-form-danger={hasError} class="uk-input uk-form-width-{width} uk-form-{size}"
on:change class:uk-form-danger={hasError}
bind:value={text} on:change
{disabled}> bind:value={text}
</div> {disabled} />
{#if infoText} </div>
{#if infoText}
<div class="info-text">{infoText}</div> <div class="info-text">{infoText}</div>
{/if} {/if}
</div> </div>
<style> <style>
.info-text { .info-text {
font-size: 0.7rem; font-size: 0.7rem;
color: var(--secondary50); color: var(--secondary50);
} }
</style> </style>

41
packages/builder/src/common/ValuesList.svelte

@ -1,35 +1,30 @@
<script> <script>
import {join} from "lodash/fp"; import { join } from "lodash/fp"
export let values; export let values
export let label; export let label
const inputChanged = ev => { const inputChanged = ev => {
try { try {
values = ev.target.value.split("\n"); values = ev.target.value.split("\n")
} catch(_) { } catch (_) {
values = []; values = []
} }
} }
$: valuesText = join("\n")(values);
$: valuesText = join("\n")(values)
</script> </script>
<div class="uk-margin"> <div class="uk-margin">
<label class="uk-form-label">{label}</label> <label class="uk-form-label">{label}</label>
<div class="uk-form-controls"> <div class="uk-form-controls">
<textarea value={valuesText} on:change={inputChanged} ></textarea> <textarea value={valuesText} on:change={inputChanged} />
</div> </div>
</div> </div>
<style> <style>
textarea {
textarea { width: 300px;
width:300px; height: 200px;
height:200px; }
} </style>
</style>

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

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

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

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

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

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

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

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

95
packages/builder/src/database/ActionsHeader.svelte

@ -1,62 +1,59 @@
<script> <script>
import Button from "../common/Button.svelte"; import Button from "../common/Button.svelte"
import ButtonGroup from "../common/ButtonGroup.svelte"; import ButtonGroup from "../common/ButtonGroup.svelte"
import {store} from "../builderStore"; import { store } from "../builderStore"
import Modal from "../common/Modal.svelte"; import Modal from "../common/Modal.svelte"
import ErrorsBox from "../common/ErrorsBox.svelte"; import ErrorsBox from "../common/ErrorsBox.svelte"
export let left; export let left
let confirmDelete = false; let confirmDelete = false
const openConfirmDelete = () => { const openConfirmDelete = () => {
confirmDelete = true; confirmDelete = true
} }
const deleteCurrentNode = () => { const deleteCurrentNode = () => {
confirmDelete = false; confirmDelete = false
store.deleteCurrentNode(); store.deleteCurrentNode()
} }
</script> </script>
<div class="root" style="left: {left}"> <div class="root" style="left: {left}">
<ButtonGroup> <ButtonGroup>
<Button color="secondary" grouped on:click={store.saveCurrentNode}> <Button color="secondary" grouped on:click={store.saveCurrentNode}>
{#if $store.currentNodeIsNew} {#if $store.currentNodeIsNew}Create{:else}Update{/if}
Create </Button>
{:else}
Update {#if !$store.currentNodeIsNew}
{/if} <Button color="tertiary" grouped on:click={openConfirmDelete}>
</Button> Delete
</Button>
{#if !$store.currentNodeIsNew} {/if}
<Button color="tertiary" grouped on:click={openConfirmDelete}> </ButtonGroup>
Delete
</Button> {#if !!$store.errors && $store.errors.length > 0}
{/if}
</ButtonGroup>
{#if !!$store.errors && $store.errors.length > 0}
<div style="width: 500px"> <div style="width: 500px">
<ErrorsBox errors={$store.errors}/> <ErrorsBox errors={$store.errors} />
</div> </div>
{/if} {/if}
<Modal bind:isOpen={confirmDelete}> <Modal bind:isOpen={confirmDelete}>
<div style="margin: 10px 0px 20px 0px">Are you sure you want to delete {$store.currentNode.name} ?</div> <div style="margin: 10px 0px 20px 0px">
<div style="float:right"> Are you sure you want to delete {$store.currentNode.name} ?
<Button color="primary" on:click={deleteCurrentNode}>Yes</Button> </div>
<Button color="secondary" on:click={() => confirmDelete = false}>No</Button> <div style="float:right">
</div> <Button color="primary" on:click={deleteCurrentNode}>Yes</Button>
</Modal> <Button color="secondary" on:click={() => (confirmDelete = false)}>
No
</Button>
</div>
</Modal>
</div> </div>
<style> <style>
.root {
.root {
padding: 1.5rem; padding: 1.5rem;
width: 100%; width: 100%;
align-items: right; align-items: right;
} }
</style>
</style>

18
packages/builder/src/database/CollectionView.svelte

@ -1,20 +1,16 @@
<script> <script>
import RecordCard from "./RecordCard.svelte"
import RecordCard from "./RecordCard.svelte"; export let record = {}
export let record = {};
</script> </script>
<div class="root"> <div class="root">
<div class="title">{record.name}</div> <div class="title">{record.name}</div>
<div class="inner"> <div class="inner">
<div class="node-path">{record.nodeKey()}</div> <div class="node-path">{record.nodeKey()}</div>
</div> </div>
</div> </div>
<style> <style>
</style>
</style>

123
packages/builder/src/database/DatabaseRoot.svelte

@ -1,82 +1,85 @@
<script> <script>
import HierarchyRow from "./HierarchyRow.svelte"
import RecordView from "./RecordView.svelte"
import IndexView from "./IndexView.svelte"
import ActionsHeader from "./ActionsHeader.svelte"
import { store } from "../builderStore"
import getIcon from "../common/icon"
import DropdownButton from "../common/DropdownButton.svelte"
import { hierarchy as hierarchyFunctions } from "../../../core/src"
import HierarchyRow from "./HierarchyRow.svelte"; const hierarchyWidth = "200px"
import RecordView from "./RecordView.svelte";
import IndexView from "./IndexView.svelte";
import ActionsHeader from "./ActionsHeader.svelte";
import {store} from "../builderStore";
import getIcon from "../common/icon";
import DropdownButton from "../common/DropdownButton.svelte";
import {hierarchy as hierarchyFunctions} from "../../../core/src";
const hierarchyWidth = "200px"; const defaultNewIndexActions = [
{
label: "New Root Index",
onclick: store.newRootIndex,
},
]
const defaultNewIndexActions = [{ const defaultNewRecordActions = [
label:"New Root Index", {
onclick: store.newRootIndex label: "New Root Record",
}]; onclick: store.newRootRecord,
},
]
const defaultNewRecordActions = [{ let newIndexActions = defaultNewIndexActions
label:"New Root Record", let newRecordActions = defaultNewRecordActions
onclick: store.newRootRecord
}];
let newIndexActions = defaultNewIndexActions; store.subscribe(db => {
let newRecordActions = defaultNewRecordActions; if (!db.currentNode || hierarchyFunctions.isIndex(db.currentNode)) {
newRecordActions = defaultNewRecordActions
store.subscribe(db => { newIndexActions = defaultNewIndexActions
if(!db.currentNode || hierarchyFunctions.isIndex(db.currentNode)) {
newRecordActions = defaultNewRecordActions;
newIndexActions = defaultNewIndexActions;
} else { } else {
newRecordActions = [ newRecordActions = [
...defaultNewRecordActions, ...defaultNewRecordActions,
{label: `New Child Record of ${db.currentNode.name}`, {
onclick: store.newChildRecord} label: `New Child Record of ${db.currentNode.name}`,
]; onclick: store.newChildRecord,
},
]
newIndexActions = [ newIndexActions = [
...defaultNewIndexActions, ...defaultNewIndexActions,
{label: `New Index on ${db.currentNode.name}`, {
onclick: store.newChildIndex} label: `New Index on ${db.currentNode.name}`,
]; onclick: store.newChildIndex,
},
]
} }
}); })
</script> </script>
<div class="root"> <div class="root">
<div class="actions-header"> <div class="actions-header">
{#if $store.currentNode} {#if $store.currentNode}
<ActionsHeader left={hierarchyWidth}/> <ActionsHeader left={hierarchyWidth} />
{/if} {/if}
</div> </div>
<div class="node-view"> <div class="node-view">
{#if !$store.currentNode} {#if !$store.currentNode}
<h1 style="margin-left: 100px">:)</h1> <h1 style="margin-left: 100px">:)</h1>
{:else if $store.currentNode.type === "record"} {:else if $store.currentNode.type === 'record'}
<RecordView /> <RecordView />
{:else} {:else}
<IndexView /> <IndexView />
{/if} {/if}
</div> </div>
</div> </div>
<style> <style>
.root { .root {
height: 100%; height: 100%;
position: relative; position: relative;
} }
.actions-header { .actions-header {
flex: 0 1 auto; flex: 0 1 auto;
} }
.node-view { .node-view {
overflow-y: auto; overflow-y: auto;
flex: 1 1 auto; flex: 1 1 auto;
} }
</style>
</style>

251
packages/builder/src/database/FieldView.svelte

@ -1,121 +1,158 @@
<script> <script>
import Dropdown from "../common/Dropdown.svelte"; import Dropdown from "../common/Dropdown.svelte"
import Textbox from "../common/Textbox.svelte"; import Textbox from "../common/Textbox.svelte"
import Button from "../common/Button.svelte"; import Button from "../common/Button.svelte"
import ButtonGroup from "../common/ButtonGroup.svelte"; import ButtonGroup from "../common/ButtonGroup.svelte"
import NumberBox from "../common/NumberBox.svelte"; import NumberBox from "../common/NumberBox.svelte"
import ValuesList from "../common/ValuesList.svelte"; import ValuesList from "../common/ValuesList.svelte"
import ErrorsBox from "../common/ErrorsBox.svelte"; import ErrorsBox from "../common/ErrorsBox.svelte"
import Checkbox from "../common/Checkbox.svelte"; import Checkbox from "../common/Checkbox.svelte"
import DatePicker from "../common/DatePicker.svelte"; import DatePicker from "../common/DatePicker.svelte"
import {cloneDeep, assign, keys, import {
isNumber, includes, map, isBoolean} from "lodash/fp"; cloneDeep,
import {allTypes, validate, getPotentialReferenceIndexes, assign,
getDefaultTypeOptions, getNode, keys,
getPotentialReverseReferenceIndexes} from "../common/core"; isNumber,
includes,
export let field; map,
export let allFields; isBoolean,
export let onFinished = () => {}; } from "lodash/fp"
export let store; import {
allTypes,
let errors = []; validate,
let clonedField = cloneDeep(field); getPotentialReferenceIndexes,
getDefaultTypeOptions,
$: isNew = !!field && field.name.length === 0; getNode,
getPotentialReverseReferenceIndexes,
$: possibleReferenceIndexes = getPotentialReferenceIndexes( } from "../common/core"
store.hierarchy, store.currentNode
); export let field
export let allFields
$: selectedReverseRefIndex = export let onFinished = () => {}
!clonedField.typeOptions.indexNodeKey export let store
let errors = []
let clonedField = cloneDeep(field)
$: isNew = !!field && field.name.length === 0
$: possibleReferenceIndexes = getPotentialReferenceIndexes(
store.hierarchy,
store.currentNode
)
$: selectedReverseRefIndex = !clonedField.typeOptions.indexNodeKey
? "" ? ""
: getNode(store.hierarchy, clonedField.typeOptions.indexNodeKey); : getNode(store.hierarchy, clonedField.typeOptions.indexNodeKey)
$: possibleReverseReferenceIndexes = $: possibleReverseReferenceIndexes = !selectedReverseRefIndex
!selectedReverseRefIndex
? [] ? []
: getPotentialReverseReferenceIndexes( : getPotentialReverseReferenceIndexes(
store.hierarchy, selectedReverseRefIndex); store.hierarchy,
selectedReverseRefIndex
const typeChanged = (ev) => )
clonedField.typeOptions = getDefaultTypeOptions(ev.detail);
const typeChanged = ev =>
(clonedField.typeOptions = getDefaultTypeOptions(ev.detail))
const save = () => {
const save = () => {
errors = validate.field(allFields)(clonedField); errors = validate.field(allFields)(clonedField)
if(errors.length > 0) return; if (errors.length > 0) return
field.typeOptions = cloneDeep(clonedField.typeOptions); field.typeOptions = cloneDeep(clonedField.typeOptions)
onFinished( onFinished(assign(field)(clonedField))
assign(field)(clonedField) }
);
}
</script> </script>
<div class="root"> <div class="root">
<ErrorsBox errors={errors} /> <ErrorsBox {errors} />
<form class="uk-form-horizontal"> <form class="uk-form-horizontal">
<Dropdown label="Type" bind:selected={clonedField.type} options={keys(allTypes)} on:change={typeChanged} /> <Dropdown
label="Type"
{#if isNew} bind:selected={clonedField.type}
<Textbox label="Field Name" bind:text={clonedField.name} /> options={keys(allTypes)}
{:else} on:change={typeChanged} />
<div style="font-weight: bold">{clonedField.name}</div>
{/if} {#if isNew}
<Textbox label="Field Name" bind:text={clonedField.name} />
<Textbox label="Label" bind:text={clonedField.label} /> {:else}
<div style="font-weight: bold">{clonedField.name}</div>
{#if clonedField.type === "string"} {/if}
<NumberBox label="Max Length" bind:value={clonedField.typeOptions.maxLength} />
<ValuesList label="Values (options)" bind:values={clonedField.typeOptions.values} /> <Textbox label="Label" bind:text={clonedField.label} />
<Checkbox label="Declared Values Only" bind:checked={clonedField.typeOptions.allowDeclaredValuesOnly} />
{:else if clonedField.type === "bool"} {#if clonedField.type === 'string'}
<Checkbox label="Allow Null" bind:checked={clonedField.typeOptions.allowNulls} /> <NumberBox
{:else if clonedField.type === "datetime"} label="Max Length"
<DatePicker label="Min Value" bind:value={clonedField.typeOptions.minValue} /> bind:value={clonedField.typeOptions.maxLength} />
<DatePicker label="Max Value" bind:value={clonedField.typeOptions.maxValue} /> <ValuesList
{:else if clonedField.type === "number"} label="Values (options)"
<NumberBox label="Min Value" bind:value={clonedField.typeOptions.minValue} /> bind:values={clonedField.typeOptions.values} />
<NumberBox label="Max Value" bind:value={clonedField.typeOptions.maxValue} /> <Checkbox
<NumberBox label="Decimal Places" bind:value={clonedField.typeOptions.decimalPlaces} /> label="Declared Values Only"
{:else if clonedField.type === "reference"} bind:checked={clonedField.typeOptions.allowDeclaredValuesOnly} />
<Dropdown label="Lookup Index" {:else if clonedField.type === 'bool'}
options={possibleReferenceIndexes} <Checkbox
valueMember={n => n.nodeKey()} label="Allow Null"
textMember={n => n.name} bind:checked={clonedField.typeOptions.allowNulls} />
bind:selected={clonedField.typeOptions.indexNodeKey} /> {:else if clonedField.type === 'datetime'}
<DatePicker
<Dropdown label="Reverse Reference Index" label="Min Value"
options={possibleReverseReferenceIndexes} bind:value={clonedField.typeOptions.minValue} />
multiple=true <DatePicker
valueMember={n => n.nodeKey()} label="Max Value"
textMember={n => n.name} bind:value={clonedField.typeOptions.maxValue} />
bind:selected={clonedField.typeOptions.reverseIndexNodeKeys} /> {:else if clonedField.type === 'number'}
<NumberBox
<Textbox label="Display Value" bind:text={clonedField.typeOptions.displayValue} /> label="Min Value"
bind:value={clonedField.typeOptions.minValue} />
{:else if clonedField.type.startsWith("array")} <NumberBox
<NumberBox label="Min Length" bind:value={clonedField.typeOptions.minLength} /> label="Max Value"
<NumberBox label="Max Length" bind:value={clonedField.typeOptions.maxLength} /> bind:value={clonedField.typeOptions.maxValue} />
{/if} <NumberBox
label="Decimal Places"
</form> bind:value={clonedField.typeOptions.decimalPlaces} />
{:else if clonedField.type === 'reference'}
<ButtonGroup style="float: right;"> <Dropdown
<Button color="primary" grouped on:click={save}>Save</Button> label="Lookup Index"
<Button color="tertiary" grouped on:click={() => onFinished(false)}>Cancel</Button> options={possibleReferenceIndexes}
</ButtonGroup> valueMember={n => n.nodeKey()}
textMember={n => n.name}
bind:selected={clonedField.typeOptions.indexNodeKey} />
<Dropdown
label="Reverse Reference Index"
options={possibleReverseReferenceIndexes}
multiple="true"
valueMember={n => n.nodeKey()}
textMember={n => n.name}
bind:selected={clonedField.typeOptions.reverseIndexNodeKeys} />
<Textbox
label="Display Value"
bind:text={clonedField.typeOptions.displayValue} />
{:else if clonedField.type.startsWith('array')}
<NumberBox
label="Min Length"
bind:value={clonedField.typeOptions.minLength} />
<NumberBox
label="Max Length"
bind:value={clonedField.typeOptions.maxLength} />
{/if}
</form>
<ButtonGroup style="float: right;">
<Button color="primary" grouped on:click={save}>Save</Button>
<Button color="tertiary" grouped on:click={() => onFinished(false)}>
Cancel
</Button>
</ButtonGroup>
</div> </div>
<style> <style>
</style>
</style>

45
packages/builder/src/database/HierarchyRow.svelte

@ -1,45 +1,42 @@
<script> <script>
import { store } from "../builderStore"
import {store} from "../builderStore"; import { cloneDeep } from "lodash/fp"
import {cloneDeep} from "lodash/fp"; export let level = 0
export let level = 0; export let node
export let node;
</script> </script>
<div class="root"> <div class="root">
<div class="title" on:click={() => store.selectExistingNode(node.nodeId)} style="padding-left: {20 + (level * 20)}px"> <div
{node.name} class="title"
</div> on:click={() => store.selectExistingNode(node.nodeId)}
{#if node.children} style="padding-left: {20 + level * 20}px">
{node.name}
</div>
{#if node.children}
{#each node.children as child} {#each node.children as child}
<svelte:self node={child} <svelte:self node={child} level={level + 1} />
level={level+1}/>
{/each} {/each}
{/if} {/if}
</div> </div>
<style> <style>
.root {
.root {
display: block; display: block;
font-size: .9rem; font-size: 0.9rem;
width: 100%; width: 100%;
cursor: pointer; cursor: pointer;
font-weight: bold; font-weight: bold;
} }
.title { .title {
font: var(--fontblack); font: var(--fontblack);
padding-top: 10px; padding-top: 10px;
padding-right: 5px; padding-right: 5px;
padding-bottom: 10px; padding-bottom: 10px;
color: var(--secondary100); color: var(--secondary100);
} }
.title:hover { .title:hover {
background-color: var(--secondary10); background-color: var(--secondary10);
} }
</style>
</style>

125
packages/builder/src/database/IndexView.svelte

@ -1,76 +1,79 @@
<script> <script>
import Textbox from "../common/Textbox.svelte"
import Textbox from "../common/Textbox.svelte"; import CodeArea from "../common/CodeArea.svelte"
import CodeArea from "../common/CodeArea.svelte"; import Button from "../common/Button.svelte"
import Button from "../common/Button.svelte"; import Dropdown from "../common/Dropdown.svelte"
import Dropdown from "../common/Dropdown.svelte"; import { store } from "../builderStore"
import {store} from "../builderStore"; import { filter, some, map } from "lodash/fp"
import {filter, some, map} from "lodash/fp"; import { hierarchy as hierarchyFunctions, common } from "../../../core/src"
import {hierarchy as hierarchyFunctions, common} from "../../../core/src";
const pipe = common.$
const pipe = common.$;
let index
let index; let indexableRecords = []
let indexableRecords = [];
store.subscribe($store => {
store.subscribe($store => { index = $store.currentNode
index = $store.currentNode; indexableRecords = pipe($store.hierarchy, [
indexableRecords = pipe($store.hierarchy,[ hierarchyFunctions.getFlattenedHierarchy,
hierarchyFunctions.getFlattenedHierarchy, filter(hierarchyFunctions.isDecendant(index.parent())),
filter(hierarchyFunctions.isDecendant(index.parent())), filter(hierarchyFunctions.isRecord),
filter(hierarchyFunctions.isRecord), map(n => ({
map(n => ({ node: n,
node:n, isallowed: some(id => n.nodeId === id)(index.allowedRecordNodeIds),
isallowed: some(id => n.nodeId === id)(index.allowedRecordNodeIds) })),
})) ])
]); })
});
const toggleAllowedRecord = record => {
const toggleAllowedRecord = record => { if (record.isallowed) {
if(record.isallowed) { index.allowedRecordNodeIds = filter(id => id !== record.node.nodeId)(
index.allowedRecordNodeIds = filter(id => id !== record.node.nodeId) index.allowedRecordNodeIds
(index.allowedRecordNodeIds); )
} else { } else {
index.allowedRecordNodeIds.push(record.node.nodeId); index.allowedRecordNodeIds.push(record.node.nodeId)
} }
}; }
</script> </script>
<form class="uk-form-horizontal root"> <form class="uk-form-horizontal root">
<Textbox bind:text={index.name} label="Name"/> <Textbox bind:text={index.name} label="Name" />
<div class="allowed-records"> <div class="allowed-records">
<div>Records to Index</div> <div>Records to Index</div>
{#each indexableRecords as rec} {#each indexableRecords as rec}
<input type="checkbox" checked={rec.isallowed} on:change={() => toggleAllowedRecord(rec)}/> <input
<span>{rec.node.name}</span> type="checkbox"
{/each} checked={rec.isallowed}
</div> on:change={() => toggleAllowedRecord(rec)} />
<span>{rec.node.name}</span>
{/each}
</div>
<Dropdown
label="Index Type"
bind:selected={index.indexType}
options={['ancestor', 'reference']} />
<CodeArea bind:text={index.map} label="Map (javascript)" />
<CodeArea bind:text={index.filter} label="Filter (javascript expression)" />
<CodeArea
bind:text={index.getShardName}
label="Shard Name (javascript expression)" />
<Dropdown label="Index Type" bind:selected={index.indexType} options={["ancestor", "reference"]} />
<CodeArea bind:text={index.map} label="Map (javascript)"/>
<CodeArea bind:text={index.filter} label="Filter (javascript expression)"/>
<CodeArea bind:text={index.getShardName} label="Shard Name (javascript expression)"/>
</form> </form>
<style> <style>
.root {
.root {
height: 100%; height: 100%;
padding: 15px; padding: 15px;
} }
.allowed-records { .allowed-records {
margin: 20px 0px; margin: 20px 0px;
} }
.allowed-records > span {
margin-right:30px;
}
</style> .allowed-records > span {
margin-right: 30px;
}
</style>

425
packages/builder/src/database/RecordView.svelte

@ -1,300 +1,307 @@
<script> <script>
import Textbox from "../common/Textbox.svelte"
import Textbox from "../common/Textbox.svelte"; import Button from "../common/Button.svelte"
import Button from "../common/Button.svelte"; import getIcon from "../common/icon"
import getIcon from "../common/icon"; import FieldView from "./FieldView.svelte"
import FieldView from "./FieldView.svelte"; import Modal from "../common/Modal.svelte"
import Modal from "../common/Modal.svelte"; import { map, join, filter, some, find, keys, isDate } from "lodash/fp"
import {map, join, filter, some, import { store } from "../builderStore"
find, keys, isDate} from "lodash/fp"; import { common, hierarchy as h } from "../../../core/src"
import { store } from "../builderStore"; import { templateApi, pipe, validate } from "../common/core"
import {common, hierarchy as h} from "../../../core/src";
import {templateApi, pipe, validate} from "../common/core"; let record
let getIndexAllowedRecords
let record; let editingField = false
let getIndexAllowedRecords; let fieldToEdit
let editingField = false; let isNewField = false
let fieldToEdit; let newField
let isNewField = false; let editField
let newField; let deleteField
let editField; let onFinishedFieldEdit
let deleteField; let editIndex
let onFinishedFieldEdit;
let editIndex; store.subscribe($store => {
record = $store.currentNode
store.subscribe($store => { const flattened = h.getFlattenedHierarchy($store.hierarchy)
record = $store.currentNode; getIndexAllowedRecords = index =>
const flattened = h.getFlattenedHierarchy($store.hierarchy); pipe(index.allowedRecordNodeIds, [
getIndexAllowedRecords = index => filter(id => some(n => n.nodeId === id)(flattened)),
pipe(index.allowedRecordNodeIds, [ map(id => find(n => n.nodeId === id)(flattened).name),
filter(id => some(n => n.nodeId === id)(flattened)), join(", "),
map(id => find(n => n.nodeId === id) ])
(flattened).name),
join(", ")
]);
newField = () => { newField = () => {
isNewField = true; isNewField = true
fieldToEdit = templateApi($store.hierarchy).getNewField("string"); fieldToEdit = templateApi($store.hierarchy).getNewField("string")
editingField = true; editingField = true
} }
onFinishedFieldEdit = field => {
onFinishedFieldEdit = (field) => { if (field) {
if(field) { store.saveField(field)
store.saveField(field); }
} editingField = false
editingField = false;
} }
editField = (field) => { editField = field => {
isNewField = false; isNewField = false
fieldToEdit = field; fieldToEdit = field
editingField = true; editingField = true
} }
deleteField = (field) => { deleteField = field => {
store.deleteField(field); store.deleteField(field)
} }
editIndex = index => { editIndex = index => {
store.selectExistingNode(index.nodeId); store.selectExistingNode(index.nodeId)
} }
})
})
let getTypeOptionsValueText = value => {
let getTypeOptionsValueText = value => { if (
if(value === Number.MAX_SAFE_INTEGER value === Number.MAX_SAFE_INTEGER ||
|| value === Number.MIN_SAFE_INTEGER value === Number.MIN_SAFE_INTEGER ||
|| new Date(value).getTime() === new Date(8640000000000000).getTime() new Date(value).getTime() === new Date(8640000000000000).getTime() ||
|| new Date(value).getTime() === new Date(-8640000000000000).getTime()) return "(any)"; new Date(value).getTime() === new Date(-8640000000000000).getTime()
)
if(value === null) return "(not set)"; return "(any)"
return value;
} if (value === null) return "(not set)"
return value
let getTypeOptions = typeOptions => }
let getTypeOptions = typeOptions =>
pipe(typeOptions, [ pipe(typeOptions, [
keys, keys,
map(k => `<span style="color:var(--slate)">${k}: </span>${getTypeOptionsValueText(typeOptions[k])}`), map(
join("<br>") k =>
]); `<span style="color:var(--slate)">${k}: </span>${getTypeOptionsValueText(
typeOptions[k]
const nameChanged = ev => { )}`
const pluralName = n => `${n}s`; ),
if(record.collectionName === "") { join("<br>"),
record.collectionName = pluralName(ev.target.value); ])
const nameChanged = ev => {
const pluralName = n => `${n}s`
if (record.collectionName === "") {
record.collectionName = pluralName(ev.target.value)
} }
} }
</script> </script>
<div class="root"> <div class="root">
<form class="uk-form-horizontal"> <form class="uk-form-horizontal">
<h3 class="settings-title"> <h3 class="settings-title">Settings</h3>
Settings
</h3> <Textbox label="Name:" bind:text={record.name} on:change={nameChanged} />
{#if !record.isSingle}
<Textbox label="Name:" bind:text={record.name} on:change={nameChanged}/> <Textbox label="Collection Name:" bind:text={record.collectionName} />
{#if !record.isSingle} <Textbox label="Shard Factor:" bind:text={record.allidsShardFactor} />
<Textbox label="Collection Name:" bind:text={record.collectionName} />
<Textbox label="Shard Factor:" bind:text={record.allidsShardFactor} />
{/if}
<div class="recordkey">{record.nodeKey()}</div>
</form>
<h3 class="title">
Fields <span class="add-field-button" on:click={newField}>{@html getIcon("plus")}</span>
</h3>
{#if record.fields.length > 0}
<table class="fields-table uk-table">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Options</th>
<th></th>
</tr>
</thead>
<tbody>
{#each record.fields as field}
<tr>
<td >
<div class="field-label">{field.label}</div>
<div style="font-size: 0.8em; color: var(--slate)">{field.name}</div>
</td>
<td >{field.type}</td>
<td >{@html getTypeOptions(field.typeOptions)}</td>
<td>
<span class="edit-button" on:click={() => editField(field)}>{@html getIcon("edit")}</span>
<span class="edit-button" on:click={() => deleteField(field)}>{@html getIcon("trash")}</span>
</td>
</tr>
{/each}
</tbody>
</table>
{:else}
(no fields added)
{/if} {/if}
<div class="recordkey">{record.nodeKey()}</div>
</form>
<h3 class="title">
Fields
<span class="add-field-button" on:click={newField}>
{@html getIcon('plus')}
</span>
</h3>
{#if editingField} {#if record.fields.length > 0}
<Modal bind:isOpen={editingField} onClosed={() => onFinishedFieldEdit(false) }> <table class="fields-table uk-table">
<FieldView field={fieldToEdit} <thead>
onFinished={onFinishedFieldEdit} <tr>
allFields={record.fields} <th>Name</th>
store={$store}/> <th>Type</th>
<th>Options</th>
<th />
</tr>
</thead>
<tbody>
{#each record.fields as field}
<tr>
<td>
<div class="field-label">{field.label}</div>
<div style="font-size: 0.8em; color: var(--slate)">
{field.name}
</div>
</td>
<td>{field.type}</td>
<td>
{@html getTypeOptions(field.typeOptions)}
</td>
<td>
<span class="edit-button" on:click={() => editField(field)}>
{@html getIcon('edit')}
</span>
<span class="edit-button" on:click={() => deleteField(field)}>
{@html getIcon('trash')}
</span>
</td>
</tr>
{/each}
</tbody>
</table>
{:else}(no fields added){/if}
{#if editingField}
<Modal
bind:isOpen={editingField}
onClosed={() => onFinishedFieldEdit(false)}>
<FieldView
field={fieldToEdit}
onFinished={onFinishedFieldEdit}
allFields={record.fields}
store={$store} />
</Modal> </Modal>
{/if} {/if}
<h3 class="title"> <h3 class="title">Indexes</h3>
Indexes
</h3>
{#each record.indexes as index} {#each record.indexes as index}
<div class="index-container"> <div class="index-container">
<div class="index-name"> <div class="index-name">
{index.name} {index.name}
<span style="margin-left: 7px" on:click={() => editIndex(index)}>{@html getIcon("edit")}</span> <span style="margin-left: 7px" on:click={() => editIndex(index)}>
</div> {@html getIcon('edit')}
<div class="index-field-row"> </span>
<span class="index-label">records indexed: </span> </div>
<span>{getIndexAllowedRecords(index)}</span> <div class="index-field-row">
<span class="index-label" style="margin-left: 15px">type:</span> <span class="index-label">records indexed:</span>
<span>{index.indexType}</span> <span>{getIndexAllowedRecords(index)}</span>
</div> <span class="index-label" style="margin-left: 15px">type:</span>
<span>{index.indexType}</span>
</div>
<div class="index-field-row">
<span class="index-label">map:</span>
<code class="index-mapfilter">{index.map}</code>
</div>
{#if index.filter}
<div class="index-field-row"> <div class="index-field-row">
<span class="index-label">map:</span> <span class="index-label">filter:</span>
<code class="index-mapfilter">{index.map}</code> <code class="index-mapfilter">{index.filter}</code>
</div> </div>
{#if index.filter} {/if}
<div class="index-field-row">
<span class="index-label">filter:</span>
<code class="index-mapfilter">{index.filter}</code>
</div>
{/if}
</div>
{:else}
<div class="no-indexes">
No indexes added.
</div> </div>
{/each} {:else}
<div class="no-indexes">No indexes added.</div>
{/each}
</div> </div>
<style> <style>
.root {
.root {
height: 100%; height: 100%;
padding: 2rem; padding: 2rem;
} }
.settings-title { .settings-title {
font-weight: 700; font-weight: 700;
} }
.title { .title {
margin: 3rem 0rem 0rem 0rem; margin: 3rem 0rem 0rem 0rem;
font-weight: 700; font-weight: 700;
} }
.recordkey { .recordkey {
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;
color: var(--primary100); color: var(--primary100);
} }
.fields-table { .fields-table {
margin: 1rem 1rem 0rem 0rem; margin: 1rem 1rem 0rem 0rem;
border-collapse:collapse; border-collapse: collapse;
} }
.add-field-button { .add-field-button {
cursor:pointer; cursor: pointer;
} }
.edit-button { .edit-button {
cursor:pointer; cursor: pointer;
color: var(--secondary25); color: var(--secondary25);
} }
.edit-button:hover { .edit-button:hover {
cursor:pointer; cursor: pointer;
color: var(--secondary75); color: var(--secondary75);
} }
th { th {
text-align: left; text-align: left;
} }
td { td {
padding: 1rem 5rem 1rem 0rem; padding: 1rem 5rem 1rem 0rem;
margin:0; margin: 0;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
}
} .field-label {
font-size: 14px;
.field-label { font-weight: 500;
font-size: 14px; }
font-weight: 500;
}
thead > tr { thead > tr {
border-width: 0px 0px 1px 0px; border-width: 0px 0px 1px 0px;
border-style: solid; border-style: solid;
border-color: var(--secondary75); border-color: var(--secondary75);
margin-bottom: 20px; margin-bottom: 20px;
} }
tbody > tr { tbody > tr {
border-width: 0px 0px 1px 0px; border-width: 0px 0px 1px 0px;
border-style: solid; border-style: solid;
border-color: var(--primary10); border-color: var(--primary10);
} }
tbody > tr:hover { tbody > tr:hover {
background-color: var(--primary10); background-color: var(--primary10);
} }
tbody > tr:hover .edit-button { tbody > tr:hover .edit-button {
color: var(--secondary75); color: var(--secondary75);
} }
.index-container { .index-container {
border-style: solid; border-style: solid;
border-width: 0 0 1px 0; border-width: 0 0 1px 0;
border-color: var(--secondary25); border-color: var(--secondary25);
padding: 10px; padding: 10px;
margin-bottom: 5px; margin-bottom: 5px;
} }
.index-label { .index-label {
color: var(--slate); color: var(--slate);
} }
.index-name { .index-name {
font-weight: bold; font-weight: bold;
color: var(--primary100); color: var(--primary100);
} }
.index-container code { .index-container code {
margin: 0; margin: 0;
display: inline; display: inline;
background-color: var(--primary10); background-color: var(--primary10);
color: var(--secondary100); color: var(--secondary100);
padding:3px; padding: 3px;
} }
.index-field-row { .index-field-row {
margin: 1rem 0rem 0rem 0rem; margin: 1rem 0rem 0rem 0rem;
} }
.no-indexes { .no-indexes {
margin: 1rem 0rem 0rem 0rem; margin: 1rem 0rem 0rem 0rem;
font-family: var(--fontnormal); font-family: var(--fontnormal);
font-size: 14px; font-size: 14px;
} }
</style> </style>

34
packages/builder/src/main.js

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

214
packages/builder/src/nav/BackendNav.svelte

@ -1,163 +1,153 @@
<script> <script>
import { store } from "../builderStore"
import { store } from "../builderStore"; import HierarchyRow from "./HierarchyRow.svelte"
import HierarchyRow from "./HierarchyRow.svelte"; import DropdownButton from "../common/DropdownButton.svelte"
import DropdownButton from "../common/DropdownButton.svelte"; import { hierarchy as hierarchyFunctions } from "../../../core/src"
import {hierarchy as hierarchyFunctions} from "../../../core/src"; import NavItem from "./NavItem.svelte"
import NavItem from "./NavItem.svelte"; import getIcon from "../common/icon"
import getIcon from "../common/icon";
const newRootRecord = () => {
store.newRootRecord()
const newRootRecord = () => { }
store.newRootRecord();
} const newRootIndex = () => {
store.newRootIndex()
const newRootIndex = () => { }
store.newRootIndex();
} const newChildRecord = () => {
store.newChildRecord()
const newChildRecord = () => { }
store.newChildRecord();
} const newChildIndex = () => {
store.newChildIndex()
const newChildIndex = () => { }
store.newChildIndex();
} const defaultNewChildActions = [
const defaultNewChildActions = [
{ {
label:"New Root Record", label: "New Root Record",
onclick: newRootRecord onclick: newRootRecord,
}, },
{ {
label:"New Root Index", label: "New Root Index",
onclick: newRootIndex onclick: newRootIndex,
} },
]; ]
let newChildActions = defaultNewChildActions; let newChildActions = defaultNewChildActions
const setActiveNav = (name) => () => { const setActiveNav = name => () => {
store.setActiveNav(name); store.setActiveNav(name)
} }
store.subscribe(db => { store.subscribe(db => {
if(!db.currentNode || hierarchyFunctions.isIndex(db.currentNode)) { if (!db.currentNode || hierarchyFunctions.isIndex(db.currentNode)) {
newChildActions = defaultNewChildActions; newChildActions = defaultNewChildActions
} else { } else {
newChildActions = [ newChildActions = [
{ {
label:"New Root Record", label: "New Root Record",
onclick: newRootRecord onclick: newRootRecord,
}, },
{ {
label:"New Root Index", label: "New Root Index",
onclick: newRootIndex onclick: newRootIndex,
}, },
{ {
label: `New Child Record of ${db.currentNode.name}`, label: `New Child Record of ${db.currentNode.name}`,
onclick: newChildRecord onclick: newChildRecord,
}, },
{ {
label: `New Index on ${db.currentNode.name}`, label: `New Index on ${db.currentNode.name}`,
onclick: newChildIndex onclick: newChildIndex,
} },
]; ]
} }
}); })
</script> </script>
<div class="items-root"> <div class="items-root">
<div class="hierarchy"> <div class="hierarchy">
<div class="components-list-container"> <div class="components-list-container">
<div class="nav-group-header"> <div class="nav-group-header">
<div>{@html getIcon("database","18")}</div> <div>
<div class="hierarchy-title">Database</div> {@html getIcon('database', '18')}
<DropdownButton iconName="plus" actions={newChildActions} />
</div>
</div> </div>
<div class="hierarchy-title">Database</div>
<DropdownButton iconName="plus" actions={newChildActions} />
</div>
</div>
<div class="hierarchy-items-container"> <div class="hierarchy-items-container">
{#each $store.hierarchy.children as record} {#each $store.hierarchy.children as record}
<HierarchyRow node={record} <HierarchyRow node={record} type="record" />
type="record" /> {/each}
{/each}
{#each $store.hierarchy.indexes as index} {#each $store.hierarchy.indexes as index}
<HierarchyRow node={index} <HierarchyRow node={index} type="index" />
type="index" /> {/each}
{/each}
</div>
</div> </div>
</div>
<NavItem name="actions" label="Actions & Triggers"/> <NavItem name="actions" label="Actions & Triggers" />
<NavItem name="access levels" label="User Levels"/> <NavItem name="access levels" label="User Levels" />
</div> </div>
<style> <style>
.items-root {
.items-root {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-height: 100%; max-height: 100%;
height: 100%; height: 100%;
background-color: var(--secondary5); background-color: var(--secondary5);
} }
.nav-group-header { .nav-group-header {
display:grid; display: grid;
grid-template-columns: [icon] auto [title] 1fr [button] auto; grid-template-columns: [icon] auto [title] 1fr [button] auto;
padding: 2rem 1rem 0rem 1rem; padding: 2rem 1rem 0rem 1rem;
font-size: .9rem; font-size: 0.9rem;
font-weight: bold; font-weight: bold;
} }
.nav-group-header>div:nth-child(1) { .nav-group-header > div:nth-child(1) {
padding: 0rem .7rem 0rem 0rem; padding: 0rem 0.7rem 0rem 0rem;
vertical-align: bottom; vertical-align: bottom;
grid-column-start: icon; grid-column-start: icon;
margin-right: 5px; margin-right: 5px;
} }
.nav-group-header>div:nth-child(2) { .nav-group-header > div:nth-child(2) {
margin-left:5px; margin-left: 5px;
vertical-align: bottom; vertical-align: bottom;
grid-column-start: title; grid-column-start: title;
margin-top:auto; margin-top: auto;
} }
.nav-group-header>div:nth-child(3) { .nav-group-header > div:nth-child(3) {
vertical-align: bottom; vertical-align: bottom;
grid-column-start: button; grid-column-start: button;
cursor: pointer; cursor: pointer;
color: var(--primary75); color: var(--primary75);
} }
.nav-group-header>div:nth-child(3):hover { .nav-group-header > div:nth-child(3):hover {
color: var(--primary75); color: var(--primary75);
} }
.hierarchy-title { .hierarchy-title {
flex: auto 1 1; flex: auto 1 1;
} }
.hierarchy { .hierarchy {
display:flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1 0 auto; flex: 1 0 auto;
height: 100px; height: 100px;
} }
.hierarchy-items-container { .hierarchy-items-container {
flex: 1 1 auto; flex: 1 1 auto;
overflow-y:auto; overflow-y: auto;
} }
</style>
</style>

95
packages/builder/src/nav/HierarchyRow.svelte

@ -1,66 +1,63 @@
<script> <script>
import { store } from "../builderStore"
import {store} from "../builderStore"; import { cloneDeep } from "lodash/fp"
import {cloneDeep} from "lodash/fp"; import getIcon from "../common/icon"
import getIcon from "../common/icon"; export let level = 0
export let level = 0; export let node
export let node; export let type
export let type;
let navActive = ""
let navActive = ""; $: icon = type === "index" ? "list" : "file"
$:icon = type==="index" ? "list" : "file";
store.subscribe(s => {
store.subscribe(s => { if (s.currentNode)
if(s.currentNode) navActive =
navActive = (s.activeNav === "database" && node.nodeId === s.currentNode.nodeId s.activeNav === "database" && node.nodeId === s.currentNode.nodeId
? "active" : "") ? "active"
}); : ""
})
</script> </script>
<div class="root"> <div class="root">
<div class="title {navActive}" on:click={() => store.selectExistingNode(node.nodeId)} style="padding-left: {20 + (level * 20)}px"> <div
{@html getIcon(icon, 12)} <span style="margin-left: 1rem">{node.name}</span> class="title {navActive}"
</div> on:click={() => store.selectExistingNode(node.nodeId)}
{#if node.children} style="padding-left: {20 + level * 20}px">
{#each node.children as child} {@html getIcon(icon, 12)}
<svelte:self node={child} <span style="margin-left: 1rem">{node.name}</span>
level={level+1} </div>
type="record"/> {#if node.children}
{/each} {#each node.children as child}
{/if} <svelte:self node={child} level={level + 1} type="record" />
{#if node.indexes} {/each}
{#each node.indexes as index} {/if}
<svelte:self node={index} {#if node.indexes}
level={level+1} {#each node.indexes as index}
type="index"/> <svelte:self node={index} level={level + 1} type="index" />
{/each} {/each}
{/if} {/if}
</div> </div>
<style> <style>
.root {
.root {
display: block; display: block;
font-size: .9rem; font-size: 0.9rem;
width: 100%; width: 100%;
cursor: pointer; cursor: pointer;
color: var(--secondary50); color: var(--secondary50);
font-weight: 500; font-weight: 500;
} }
.title { .title {
padding-top: .5rem; padding-top: 0.5rem;
padding-right: .5rem; padding-right: 0.5rem;
} }
.title:hover { .title:hover {
background-color: var(--secondary10); background-color: var(--secondary10);
} }
.active { .active {
background-color: var(--primary10); background-color: var(--primary10);
} }
</style>
</style>

44
packages/builder/src/nav/NavItem.svelte

@ -1,43 +1,35 @@
<script> <script>
import { store } from "../builderStore"
import getIcon from "../common/icon"
import {store} from "../builderStore"; export let name = ""
import getIcon from "../common/icon"; export let label = ""
export let name = ""; let navActive = ""
export let label = "";
let navActive = ""; store.subscribe(db => {
navActive = db.activeNav === name ? "active" : ""
store.subscribe(db => { })
navActive = (db.activeNav === name ? "active" : "")
});
const setActive = () =>
store.setActiveNav(name);
const setActive = () => store.setActiveNav(name)
</script> </script>
<div class="nav-item {navActive}" on:click={setActive}> <div class="nav-item {navActive}" on:click={setActive}>{label}</div>
{label}
</div>
<style> <style>
.nav-item {
.nav-item {
padding: 1.5rem 1rem 0rem 1rem; padding: 1.5rem 1rem 0rem 1rem;
font-size: .9rem; font-size: 0.9rem;
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer;
flex: 0 0 auto; flex: 0 0 auto;
} }
.nav-item:hover { .nav-item:hover {
background-color: var(--primary10); background-color: var(--primary10);
} }
.active { .active {
background-color: var(--primary10); background-color: var(--primary10);
} }
</style>
</style>

136
packages/builder/src/userInterface/CodeEditor.svelte

@ -1,83 +1,84 @@
<script> <script>
import { store } from "../builderStore/store"; import { store } from "../builderStore/store"
import UIkit from "uikit"; import UIkit from "uikit"
import Button from "../common/Button.svelte"; import Button from "../common/Button.svelte"
import ButtonGroup from "../common/ButtonGroup.svelte"; import ButtonGroup from "../common/ButtonGroup.svelte"
import CodeMirror from "codemirror"; import CodeMirror from "codemirror"
import "codemirror/mode/javascript/javascript.js"; import "codemirror/mode/javascript/javascript.js"
export let onCodeChanged; export let onCodeChanged
export let code; export let code
export const show = () => { export const show = () => {
UIkit.modal(codeModal).show(); UIkit.modal(codeModal).show()
}
let codeModal;
let editor;
let cmInstance;
$: currentCode = code;
$: originalCode = code;
$: {
if(editor) {
if(!cmInstance) {
cmInstance = CodeMirror.fromTextArea(editor, {
mode: 'javascript',
lineNumbers: false,
lineWrapping: true,
smartIndent: true,
matchBrackets: true,
readOnly: false
});
cmInstance.on("change", () => currentCode = cmInstance.getValue());
}
cmInstance.focus();
cmInstance.setValue(code || "");
} }
}
const cancel = () => { let codeModal
UIkit.modal(codeModal).hide(); let editor
currentCode = originalCode; let cmInstance
}
$: currentCode = code
$: originalCode = code
$: {
if (editor) {
if (!cmInstance) {
cmInstance = CodeMirror.fromTextArea(editor, {
mode: "javascript",
lineNumbers: false,
lineWrapping: true,
smartIndent: true,
matchBrackets: true,
readOnly: false,
})
cmInstance.on("change", () => (currentCode = cmInstance.getValue()))
}
cmInstance.focus()
cmInstance.setValue(code || "")
}
}
const save = () => { const cancel = () => {
originalCode = currentCode; UIkit.modal(codeModal).hide()
onCodeChanged(currentCode); currentCode = originalCode
UIkit.modal(codeModal).hide(); }
}
const save = () => {
originalCode = currentCode
onCodeChanged(currentCode)
UIkit.modal(codeModal).hide()
}
</script> </script>
<div bind:this={codeModal} uk-modal> <div bind:this={codeModal} uk-modal>
<div class="uk-modal-dialog" uk-overflow-auto> <div class="uk-modal-dialog" uk-overflow-auto>
<div class="uk-modal-header"> <div class="uk-modal-header">
<h3>Code</h3> <h3>Code</h3>
</div> </div>
<div class="uk-modal-body uk-form-horizontal">
<div class="uk-modal-body uk-form-horizontal" > <p>
Use the code box below to control how this component is displayed, with
<p>Use the code box below to control how this component is displayed, with javascript.</p> javascript.
</p>
<div>
<div class="editor-code-surround">function(render, context, store) {"{"}</div> <div>
<div class="editor"> <div class="editor-code-surround">
<textarea bind:this={editor}></textarea> function(render, context, store) {'{'}
</div> </div>
<div class="editor-code-surround"> <div class="editor">
{"}"} <textarea bind:this={editor} />
</div>
</div> </div>
<div class="editor-code-surround">{'}'}</div>
</div> </div>
<ButtonGroup style="float: right;">
<Button color="primary" grouped on:click={save}>Save</Button>
<Button color="tertiary" grouped on:click={cancel}>Close</Button>
</ButtonGroup>
</div> </div>
<ButtonGroup style="float: right;">
<Button color="primary" grouped on:click={save}>Save</Button>
<Button color="tertiary" grouped on:click={cancel}>Close</Button>
</ButtonGroup>
</div>
</div> </div>
<style> <style>
@ -105,5 +106,4 @@ const save = () => {
.editor-code-surround { .editor-code-surround {
font-family: "Courier New", Courier, monospace; font-family: "Courier New", Courier, monospace;
} }
</style> </style>

193
packages/builder/src/userInterface/ComponentPanel.svelte

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

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>

69
packages/builder/src/userInterface/ComponentSearch.svelte

@ -1,64 +1,59 @@
<script> <script>
import { searchAllComponents } from "./pagesParsing/searchComponents"
import { store } from "../builderStore"
import { searchAllComponents } from "./pagesParsing/searchComponents"; export let onComponentChosen = () => {}
import { store } from "../builderStore";
export let onComponentChosen = () => {}; let phrase = ""
let phrase = ""; components = $store.components
components = $store.components; $: filteredComponents = !phrase ? [] : searchAllComponents(components, phrase)
$: filteredComponents =
!phrase
? []
: searchAllComponents(components, phrase);
</script> </script>
<div class="root"> <div class="root">
<form class="uk-search uk-search-large"> <form class="uk-search uk-search-large">
<span uk-search-icon></span> <span uk-search-icon />
<input class="uk-search-input" <input
type="search" class="uk-search-input"
placeholder="Based on component..." type="search"
bind:value={phrase}> placeholder="Based on component..."
</form> bind:value={phrase} />
</form>
<div>
{#each filteredComponents as component} <div>
<div class="component" on:click={() => onComponentChosen(component)}> {#each filteredComponents as component}
<div class="title">{component.name}</div> <div class="component" on:click={() => onComponentChosen(component)}>
<div class="description">{component.description}</div> <div class="title">{component.name}</div>
</div> <div class="description">{component.description}</div>
{/each} </div>
</div> {/each}
</div>
</div> </div>
<style> <style>
.component {
.component { padding: 5px;
padding:5px;
border-style: solid; border-style: solid;
border-width: 0 0 1px 0; border-width: 0 0 1px 0;
border-color: var(--lightslate); border-color: var(--lightslate);
cursor: pointer; cursor: pointer;
} }
.component:hover { .component:hover {
background-color: var(--primary10); background-color: var(--primary10);
} }
.component > .title { .component > .title {
font-size: 13pt; font-size: 13pt;
color: var(--secondary100); color: var(--secondary100);
} }
.component > .description { .component > .description {
font-size: 10pt; font-size: 10pt;
color: var(--primary75); color: var(--primary75);
font-style: italic; font-style: italic;
} }
</style> </style>

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

Loading…
Cancel
Save