Browse Source

Fixing merge conflicts after pulling upstream master

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

24
.eslintrc.json

@ -0,0 +1,24 @@
{
"env": {
"browser": true,
"es6": true,
"jest": true,
"node": true
},
"parserOptions": {
"ecmaVersion": 2019,
"sourceType": "module"
},
"ignorePatterns": ["node_modules", "dist", "public"],
"plugins": ["prettier", "svelte3"],
"extends": ["eslint:recommended"],
"overrides": [
{
"files": ["**/*.svelte"],
"processor": "svelte3/svelte3"
}
],
"rules": {
"prettier/prettier": "error"
}
}

35
.github/workflows/budibase_ci.yml

@ -0,0 +1,35 @@
name: Budibase CI
on:
# Trigger the workflow on push or pull request,
# but only for the master branch
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: yarn
- run: yarn lint
- run: yarn build
- run: yarn test
env:
CI: true
name: Budibase CI

8
.prettierrc

@ -0,0 +1,8 @@
{
"tabWidth": 2,
"semi": false,
"singleQuote": false,
"trailingComma": "es5",
"plugins": ["prettier-plugin-svelte"],
"svelteSortOrder" : "scripts-markup-styles"
}

14
package.json

@ -2,7 +2,13 @@
"name": "root", "name": "root",
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"lerna": "^3.14.1" "eslint": "^6.8.0",
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-svelte3": "^2.7.3",
"lerna": "^3.14.1",
"prettier": "^1.19.1",
"prettier-plugin-svelte": "^0.7.0",
"svelte": "^3.18.1"
}, },
"dependencies": {}, "dependencies": {},
"scripts": { "scripts": {
@ -10,6 +16,10 @@
"build": "lerna run build", "build": "lerna run build",
"initialise": "lerna run initialise", "initialise": "lerna run initialise",
"clean": "lerna clean", "clean": "lerna clean",
"dev": "lerna run --parallel --stream dev:builder" "dev": "lerna run --parallel --stream dev:builder",
"test": "lerna run test",
"lint": "eslint packages",
"lint:fix": "eslint --fix packages",
"format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx,svelte}\""
} }
} }

814
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")
}) })

2
packages/builder/package.json

@ -6,6 +6,7 @@
"build": "rollup -c", "build": "rollup -c",
"start": "rollup -c -w", "start": "rollup -c -w",
"test": "jest", "test": "jest",
"test:watch": "jest --watchAll",
"dev:builder": "rollup -c -w" "dev:builder": "rollup -c -w"
}, },
"jest": { "jest": {
@ -35,6 +36,7 @@
"dependencies": { "dependencies": {
"@budibase/client": "^0.0.16", "@budibase/client": "^0.0.16",
"@nx-js/compiler-util": "^2.0.0", "@nx-js/compiler-util": "^2.0.0",
"codemirror": "^5.51.0",
"date-fns": "^1.29.0", "date-fns": "^1.29.0",
"feather-icons": "^4.21.0", "feather-icons": "^4.21.0",
"flatpickr": "^4.5.7", "flatpickr": "^4.5.7",

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"]; "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: { commonjs({
"lodash/fp": lodash_fp_exports, namedExports: {
"lodash":lodash_exports, "lodash/fp": lodash_fp_exports,
"shortid": ["generate"] lodash: lodash_exports,
} shortid: ["generate"],
}), },
url({ }),
limit: 0, url({
include: ["**/*.woff2", "**/*.png"], limit: 0,
fileName: "[dirname][name][extname]", include: ["**/*.woff2", "**/*.png"],
emitFiles: true fileName: "[dirname][name][extname]",
}), emitFiles: true,
url({ }),
limit: 0, url({
include: ["**/*.css"], limit: 0,
fileName: "[name][extname]", include: ["**/*.css"],
emitFiles: true fileName: "[name][extname]",
}), emitFiles: true,
builtins(), }),
nodeglobals(), builtins(),
nodeglobals(),
// Watch the `dist` directory and refresh the // Watch the `dist` directory and refresh the
// browser on changes when not in production // browser on changes when not in production
!production && livereload(outputpath), !production && livereload(outputpath),
!production && browsersync({ !production &&
server: outputpath, browsersync({
middleware: [apiProxy,_builderProxy] server: outputpath,
}), middleware: [apiProxy, _builderProxy],
}),
// If we're building for production (npm run build // If we're building for production (npm run build
// instead of npm run dev), minify // instead of npm run dev), minify
production && terser() production && terser(),
], ],
watch: { watch: {
clearScreen: false 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,
}

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

@ -0,0 +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
for (let child of props._children) {
walkProps(child)
}
}
walkProps(screen.props)
return code
}
export const buildCodeForScreens = screens => {
let allfunctions = ""
for (let screen of screens) {
allfunctions += buildCodeForSingleScreen(screen)
}
return `return ({ ${allfunctions} });`
}
const buildComponentCode = componentProps =>
`"${componentProps._id}" : (render, context) => {
${componentProps._code}
},
`

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

@ -1,12 +1,12 @@
import {createNewHierarchy} from "../common/core"; import { createNewHierarchy } from "../common/core"
export const createPackage = (packageInfo, store) => { 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: [] },
}); })
}; }

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

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

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

@ -1,29 +1,27 @@
import {createPackage} from "./createPackage"; import { 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)}`

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

File diff suppressed because it is too large

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

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

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

@ -0,0 +1,53 @@
<script>
export let disabled = false
export let hidden = false
export let primary = true
export let alert = false
export let warning = false
</script>
<button
on:click
class="button"
class:hidden
class:primary
class:alert
class:warning
{disabled}>
<slot />
</button>
<style>
.primary {
color: #0055ff;
background: rgb(54, 133, 249, 0.1);
}
.alert {
color: rgba(255, 0, 31, 1);
background: rgba(255, 0, 31, 0.1);
}
.button {
font-size: 18px;
font-weight: bold;
border-radius: 5px;
border: none;
width: 167px;
height: 64px;
}
.button:hover {
cursor: pointer;
}
.button:disabled {
color: rgba(22, 48, 87, 0.2);
cursor: default;
background: transparent;
}
.hidden {
visibility: hidden;
}
</style>

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>

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

@ -0,0 +1,10 @@
<svg
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="M12 13.172l4.95-4.95 1.414 1.414L12 16 5.636 9.636 7.05 8.222z" />
</svg>

After

Width:  |  Height:  |  Size: 246 B

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

@ -0,0 +1,3 @@
<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" />
</svg>

After

Width:  |  Height:  |  Size: 157 B

9
packages/builder/src/common/Icons/Events.svelte

@ -0,0 +1,9 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M13 9h8L11 24v-9H4l9-15v9zm-2 2V7.22L7.532 13H13v4.394L17.263 11H11z" />
</svg>

After

Width:  |  Height:  |  Size: 228 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

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

@ -0,0 +1,19 @@
<svg
on:click
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M15.728 9.686l-1.414-1.414L5
17.586V19h1.414l9.314-9.314zm1.414-1.414l1.414-1.414-1.414-1.414-1.414 1.414
1.414 1.414zM7.242 21H3v-4.243L16.435 3.322a1 1 0 0 1 1.414 0l2.829 2.829a1
1 0 0 1 0 1.414L7.243 21z" />
</svg>
<style>
svg:hover {
cursor: pointer;
}
</style>

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

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

@ -1,5 +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 CircleIndicator } from "./CircleIndicator.svelte"
export { default as PencilIcon } from "./Pencil.svelte"
export { default as EventsIcon } from "./Events.svelte"

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

@ -0,0 +1,27 @@
<script>
export let value = ""
</script>
<input type="text" on:change bind:value />
<style>
input {
display: block;
font-size: 14px;
font-family: sans-serif;
font-weight: 500;
color: #163057;
line-height: 1.3;
padding: 1em 2.6em 0.9em 1.4em;
width: 100%;
max-width: 100%;
box-sizing: border-box;
margin: 0;
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
background: #fff;
border: 1px solid #ccc;
height: 50px;
}
</style>

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

@ -1,20 +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 onStyleChanged = () => {}
let _values = values.map(v => v)
$: onStyleChanged(_values)
</script> </script>
<div class="inputs {size}"> <div class="inputs {size}">
{#each meta as { placeholder }, i} {#each meta as { placeholder }, i}
<input type="number" placeholder="{placeholder}" bind:value={values[i]}/> <input
{type}
{placeholder}
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 {
@ -25,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;

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

@ -1,41 +1,44 @@
<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 bind:this={ukModal} uk-modal {id}>
<div class="uk-modal-dialog uk-modal-body" uk-overflow-auto> <div class="uk-modal-dialog uk-modal-body" uk-overflow-auto>
<slot /> {#if onClosed}
</div> <button class="uk-modal-close-default" type="button" uk-close />
{/if}
<slot />
</div>
</div> </div>
<style> <style>
.uk-modal-dialog {
.uk-modal-dialog { border-radius: 0.3rem;
border-radius: .3rem; width: 60%;
} height: 80vh;
display: flex;
</style> flex-direction: column;
}
</style>

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>

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

@ -0,0 +1,23 @@
<button on:click>+</button>
<style>
button {
cursor: pointer;
outline: none;
border: none;
border-radius: 5px;
background: rgba(249, 249, 249, 1);
width: 1.8rem;
height: 1.8rem;
padding-bottom: 10px;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.2rem;
font-weight: 700;
color: rgba(22, 48, 87, 1);
}
</style>

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

@ -0,0 +1,55 @@
<script>
import getIcon from "./icon"
export let value
</script>
<div class="select-container">
<select on:change {value}>
<slot />
</select>
<span class="arrow">
{@html getIcon('chevron-down', '24')}
</span>
</div>
<style>
.select-container {
padding-bottom: 10px;
font-size: 0.9rem;
color: var(--secondary50);
font-weight: bold;
position: relative;
}
select {
display: block;
font-size: 14px;
font-family: sans-serif;
font-weight: 500;
color: #163057;
line-height: 1.3;
padding: 1em 2.6em 0.9em 1.4em;
width: 100%;
max-width: 100%;
box-sizing: border-box;
margin: 0;
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
background: #fff;
border: 1px solid #ccc;
height: 50px;
}
.arrow {
position: absolute;
right: 10px;
top: 0;
bottom: 0;
margin: auto;
width: 30px;
height: 30px;
pointer-events: none;
color: var(--primary100);
}
</style>

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>

2
packages/builder/src/index.html

@ -8,6 +8,8 @@
<link rel='icon' type='image/png' href='/_builder/favicon.png'> <link rel='icon' type='image/png' href='/_builder/favicon.png'>
<link rel='stylesheet' href='/_builder/global.css'> <link rel='stylesheet' href='/_builder/global.css'>
<link rel='stylesheet' href='/_builder/codemirror.css'>
<link rel='stylesheet' href='/_builder/monokai.css'>
<link rel='stylesheet' href='/_builder/bundle.css'> <link rel='stylesheet' href='/_builder/bundle.css'>
<link rel='stylesheet' href='/_builder/fonts.css'> <link rel='stylesheet' href='/_builder/fonts.css'>
<link rel='stylesheet' href="/_builder/uikit.min.css"> <link rel='stylesheet' href="/_builder/uikit.min.css">

32
packages/builder/src/main.js

@ -1,18 +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/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>

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

@ -1,47 +1,84 @@
<script> <script>
let snippets = []; import { store } from "../builderStore/store"
let current_snippet = 0; import UIkit from "uikit"
let snippet_text = '' import Button from "../common/Button.svelte"
let id = 0; import ButtonGroup from "../common/ButtonGroup.svelte"
import CodeMirror from "codemirror"
function save_snippet() { import "codemirror/mode/javascript/javascript.js"
if (!snippet_text) return;
export let onCodeChanged
const index = snippets.findIndex(({ id }) => current_snippet === id); export let code
export const show = () => {
UIkit.modal(codeModal).show()
}
if (index > -1) { let codeModal
snippets[index].snippet = snippet_text; let editor
} else { let cmInstance
snippets = snippets.concat({ snippet: snippet_text , id: id });
$: 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 || "")
} }
snippet_text = '';
current_snippet = ++id;
} }
function edit_snippet(id) { const cancel = () => {
const { snippet, id: _id } = snippets.find(({ id:_id }) => _id === id); UIkit.modal(codeModal).hide()
current_snippet = id currentCode = originalCode
snippet_text = snippet;
} }
</script>
<h3>Code</h3>
<p>Use the code box below to add snippets of javascript to enhance your webapp</p>
<div class="editor"> const save = () => {
<textarea class="code" bind:value={snippet_text} /> originalCode = currentCode
<button on:click={save_snippet}>Save</button> onCodeChanged(currentCode)
</div> UIkit.modal(codeModal).hide()
}
</script>
<div class="snippets"> <div bind:this={codeModal} uk-modal>
<h3>Snippets added</h3> <div class="uk-modal-dialog" uk-overflow-auto>
{#each snippets as { id, snippet } }
<div class="snippet"> <div class="uk-modal-header">
<pre class="code">{snippet}</pre> <h3>Code</h3>
<button on:click={() => edit_snippet(id)}>Edit</button> </div>
<div class="uk-modal-body uk-form-horizontal">
<p>
Use the code box below to control how this component is displayed, with
javascript.
</p>
<div>
<div class="editor-code-surround">
function(render, context, store) {'{'}
</div>
<div class="editor">
<textarea bind:this={editor} />
</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>
{/each}
</div> </div>
<style> <style>
@ -60,60 +97,13 @@
} }
.editor { .editor {
position: relative; border-style: dotted;
} border-width: 1px;
border-color: gainsboro;
.code { padding: 10px 30px;
width: 100%;
outline: none;
border: none;
background: #173157;
border-radius: 5px;
box-sizing: border-box;
white-space: pre;
color: #eee;
padding: 10px;
font-family: monospace;
overflow-y: scroll;
}
.editor textarea {
resize: none;
height: 150px;
}
button {
position: absolute;
box-shadow: 0 0 black;
color: #eee;
right: 5px;
bottom: 10px;
background: none;
border: none;
text-transform: uppercase;
font-size: 9px;
font-weight: 600;
outline: none;
cursor: pointer;
}
.snippets {
margin-top: 20px;
} }
.snippet { .editor-code-surround {
position: relative; font-family: "Courier New", Courier, monospace;
margin-top: 5px;
} }
.snippet pre {
background: #f9f9f9;
color: #333;
max-height: 150px;
}
.snippet button {
color: #ccc;
}
</style> </style>

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

@ -1,137 +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 { isRootComponent } from "./pagesParsing/searchComponents"; import {
import IconButton from "../common/IconButton.svelte"; LayoutIcon,
import Textbox from "../common/Textbox.svelte"; PaintIcon,
import { pipe } from "../common/core"; TerminalIcon,
import { CircleIndicator,
getScreenInfo EventsIcon,
} from "./pagesParsing/createProps"; } from "../common/Icons/"
import { LayoutIcon, PaintIcon, TerminalIcon } from '../common/Icons/'; import CodeEditor from "./CodeEditor.svelte"
import CodeEditor from './CodeEditor.svelte'; import LayoutEditor from "./LayoutEditor.svelte"
import LayoutEditor from './LayoutEditor.svelte'; import EventsEditor from "./EventsEditor"
import { let current_view = "props"
cloneDeep, let codeEditor
join,
split, $: component = $store.currentComponentInfo
map, $: originalName = component.name
keys, $: name = component.name
isUndefined, $: description = component.description
last $: componentInfo = $store.currentComponentInfo
} from "lodash/fp"; $: components = $store.components
import { assign } from "lodash";
const onPropChanged = store.setComponentProp
let component; const onStyleChanged = store.setComponentStyle
let name = "";
let description = "";
let tagsString = "";
let nameInvalid = "";
let componentInfo = {};
let modalElement
let propsValidationErrors = [];
let originalName="";
let components;
let ignoreStore = false;
// $: shortName = last(name.split("/"));
store.subscribe(s => {
if(ignoreStore) return;
component = s.currentComponentInfo;
if(!component) return;
originalName = component.name;
name = component.name;
description = component.description;
tagsString = join(", ")(component.tags);
componentInfo = s.currentComponentInfo;
components = s.components;
});
const onPropsChanged = store.updateComponentProp;
let current_view = 'props';
</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={() => current_view = 'code'}> <LayoutIcon />
<TerminalIcon /> </button>
</button> </li>
</li> <li>
</ul> <button
class:selected={current_view === 'code'}
{#if !componentInfo.component} on:click={() => codeEditor && codeEditor.show()}>
<div class="component-props-container"> {#if componentInfo._code && componentInfo._code.trim().length > 0}
<div class="button-indicator">
{#if current_view === 'props'} <CircleIndicator />
<PropsView {componentInfo} {components} {onPropsChanged} /> </div>
{:else if current_view === 'layout'}
<LayoutEditor />
{:else}
<CodeEditor />
{/if} {/if}
<TerminalIcon />
</div> </button>
{:else} </li>
<h1> This is a screen, this will be dealt with later</h1> <li>
{/if} <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} />
{: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;
@ -140,11 +132,18 @@ li button {
padding: 12px; padding: 12px;
outline: none; outline: none;
cursor: pointer; cursor: pointer;
} position: relative;
}
.selected { .selected {
color: var(--button-text); color: var(--button-text);
background: var(--background-button)!important; background: var(--background-button) !important;
} }
.button-indicator {
position: absolute;
top: 8px;
right: 10px;
color: var(--button-text);
}
</style> </style>

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

Loading…
Cancel
Save