Merge pull request #223 from Budibase/couchdb-api

Couchdb api
This commit is contained in:
Martin McKeaveney 2020-05-07 22:47:02 +01:00 committed by GitHub
commit 6c51c872e9
593 changed files with 6556 additions and 240861 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

7563
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,7 @@
"publishdev": "lerna run publishdev", "publishdev": "lerna run publishdev",
"publishnpm": "yarn build && lerna publish", "publishnpm": "yarn build && lerna publish",
"clean": "lerna clean", "clean": "lerna clean",
"dev": "lerna run --parallel --stream dev:builder", "dev": "./scripts/symlinkDev.js && lerna run --parallel --stream dev:builder",
"test": "lerna run test", "test": "lerna run test",
"lint": "eslint packages", "lint": "eslint packages",
"lint:fix": "eslint --fix packages", "lint:fix": "eslint --fix packages",

BIN
packages/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -1,5 +0,0 @@
.DS_Store
node_modules
yarn.lock
package-lock.json
dist/index.js

View File

@ -1,33 +0,0 @@
*Psst — looking for an app template? Go here --> [sveltejs/template](https://github.com/sveltejs/template)*
---
# component-template
A base for building shareable Svelte components. Clone it with [degit](https://github.com/Rich-Harris/degit):
```bash
npx degit sveltejs/component-template my-new-component
cd my-new-component
npm install # or yarn
```
Your component's source code lives in `src/index.html`.
TODO
* [ ] some firm opinions about the best way to test components
* [ ] update `degit` so that it automates some of the setup work
## Setting up
* Run `npm init` (or `yarn init`)
* Replace this README with your own
## Consuming components
Your package.json has a `"svelte"` field pointing to `src/index.html`, which allows Svelte apps to import the source code directly, if they are using a bundler plugin like [rollup-plugin-svelte](https://github.com/rollup/rollup-plugin-svelte) or [svelte-loader](https://github.com/sveltejs/svelte-loader) (where [`resolve.mainFields`](https://webpack.js.org/configuration/resolve/#resolve-mainfields) in your webpack config includes `"svelte"`). **This is recommended.**
For everyone else, `npm run build` will bundle your component's source code into a plain JavaScript module (`index.mjs`) and a UMD script (`index.js`). This will happen automatically when you publish your component to npm, courtesy of the `prepublishOnly` hook in package.json.

View File

@ -1,47 +0,0 @@
{
"_lib": "./dist/index.js",
"form" : {
"importPath": "Form",
"name": "Form",
"description": "A form - allgned fields with labels",
"props" : {
"containerClass": "string",
"formControls": {
"type":"array",
"elementDefinition": {
"label": "string",
"control":"component",
"controlPosition": {
"type":"options",
"options": ["Before Label","After Label"],
"default": "After Label"
}
}
}
},
"tags": ["form"]
},
"nav": {
"importPath": "Nav",
"name": "Nav",
"description": "A nav - a side bar of buttons that control the currently active component",
"props" : {
"items": {
"type": "array",
"elementDefinition" : {
"title": "string",
"component": "component"
}
},
"selectedItem":"string",
"pills":"bool",
"orientation":{"type":"options", "options": ["horizontal", "vertical"]},
"alignment":{"type":"options", "options": ["start", "center", "end"]},
"fill":"bool",
"hideNavBar":"bool",
"className": "string"
},
"tags": ["nav", "navigation", "sidebar"]
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,36 +0,0 @@
{
"name": "@budibase/bootstrap-components",
"svelte": "src/index.svelte",
"main": "dist/index.js",
"module": "dist/index.js",
"scripts": {
"build": "rollup -c",
"prepublishOnly": "npm run build",
"testbuild": "rollup -w -c rollup.testconfig.js",
"dev": "run-p start:dev testbuild",
"start:dev": "sirv public --single --dev",
"publishdev": "yarn build && node ./scripts/publishDev.js"
},
"devDependencies": {
"@budibase/client": "^0.0.32",
"fs-extra": "^8.1.0",
"lodash": "^4.17.15",
"npm-run-all": "^4.1.5",
"rollup": "^1.11.0",
"rollup-plugin-commonjs": "^10.0.2",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-livereload": "^1.0.1",
"rollup-plugin-node-resolve": "^5.0.0",
"rollup-plugin-svelte": "^5.0.0",
"rollup-plugin-terser": "^5.1.1",
"shortid": "^2.2.15",
"sirv-cli": "^0.4.4",
"svelte": "^3.12.1"
},
"keywords": [
"svelte"
],
"version": "0.0.32",
"license": "MIT",
"gitHead": "b1f4f90927d9e494e513220ef060af28d2d42455"
}

View File

@ -1,13 +0,0 @@
#current_component.svelte-1xqz9vm{height:100%;width:100%}
.root.svelte-10kw8to{display:grid}
.root.svelte-crnq0a{height:100%;display:grid;grid-template-columns:[left] 1fr [middle] auto [right] 1fr;grid-template-rows:[top] 1fr [center] auto [bottom] 1fr}.content.svelte-crnq0a{grid-column-start:middle;grid-row-start:center;width:400px}.logo-container.svelte-crnq0a{margin-bottom:20px
}.logo-container.svelte-crnq0a>img.svelte-crnq0a{max-width:100%}.login-button-container.svelte-crnq0a{text-align:right;margin-top:20px}.incorrect-details-panel.svelte-crnq0a{margin-top:30px;padding:10px;border-style:solid;border-width:1px;border-color:maroon;border-radius:1px;text-align:center;color:maroon;background-color:mistyrose}.form-root.svelte-crnq0a{display:grid;grid-template-columns:[label] auto [control] 1fr}.label.svelte-crnq0a{grid-column-start:label;padding:5px 10px;vertical-align:middle}.control.svelte-crnq0a{grid-column-start:control;padding:5px 10px}.default-input.svelte-crnq0a{font-family:inherit;font-size:inherit;padding:0.4em;margin:0 0 0.5em 0;box-sizing:border-box;border:1px solid #ccc;border-radius:2px;width:100%}.default-button.svelte-crnq0a{font-family:inherit;font-size:inherit;padding:0.4em;margin:0 0 0.5em 0;box-sizing:border-box;border:1px solid #ccc;border-radius:2px;color:#333;background-color:#f4f4f4;outline:none}.default-button.svelte-crnq0a:active{background-color:#ddd}.default-button.svelte-crnq0a:focus{border-color:#666}
.form-root.svelte-m9d6ue{display:grid;grid-template-columns:[label] auto [control] 1fr}.label.svelte-m9d6ue{grid-column-start:label;padding:5px 10px;vertical-align:middle}.control.svelte-m9d6ue{grid-column-start:control;padding:5px 10px}.overflow.svelte-m9d6ue{grid-column-start:overflow}.full-width.svelte-m9d6ue{width:100%}
.root.svelte-aihwli{height:100%;width:100%;grid-template-columns:[navbar] auto [content] 1fr;display:grid}.navbar.svelte-aihwli{grid-column:navbar;background:var(--navBarBackground);border:var(--navBarBorder);color:var(--navBarColor)}.navitem.svelte-aihwli{padding:10px 17px;cursor:pointer}.navitem.svelte-aihwli:hover{background:var(--itemHoverBackground);color:var(--itemHoverColor)}.navitem.selected.svelte-aihwli{background:var(--selectedItemBackground);border:var(--selectedItemBorder);color:var(--selectedItemColor)}.content.svelte-aihwli{grid-column:content}
.default.svelte-1ec4wqj{width:100%;font-family:inherit;font-size:inherit;padding:0.4em;margin:0 0 0.5em 0;box-sizing:border-box;border:1px solid #ccc;border-radius:2px;width:100%}.default.svelte-1ec4wqj:disabled{color:#ccc}
.panel.svelte-6yfcjx:hover{background:var(--hoverBackground);color:var(--hoverColor)}
.horizontal.svelte-osi0db{display:inline-block}.vertical.svelte-osi0db{display:block}
.table-default.svelte-h8rqk6{width:100%;margin-bottom:1rem;color:#212529;border-collapse:collapse}.table-default.svelte-h8rqk6 .thead-default .th-default.svelte-h8rqk6{vertical-align:bottom;border-bottom:2px solid #dee2e6;font-weight:bold}.table-default.svelte-h8rqk6 .th-default.svelte-h8rqk6{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6;font-weight:normal}.th-default.svelte-h8rqk6{text-align:inherit}.table-default.svelte-h8rqk6 .tbody-default .tr-default.svelte-h8rqk6:hover{color:#212529;background-color:rgba(0,0,0,.075);cursor:pointer}
.default.svelte-1q8lga0{font-family:inherit;font-size:inherit;padding:0.4em;margin:0 0 0.5em 0;box-sizing:border-box;border:1px solid #ccc;border-radius:2px;color:#333;background-color:#f4f4f4;outline:none}.default.svelte-1q8lga0:active{background-color:#ddd}.default.svelte-1q8lga0:focus{border-color:#666}
/*# sourceMappingURL=bundle.css.map */

View File

@ -1,30 +0,0 @@
{
"version": 3,
"file": "bundle.css",
"sources": [
"..\\src\\Test\\TestApp.svelte",
"..\\src\\Grid.svelte",
"..\\src\\Login.svelte",
"..\\src\\Form.svelte",
"..\\src\\Nav.svelte",
"..\\src\\Textbox.svelte",
"..\\src\\Panel.svelte",
"..\\src\\StackPanel.svelte",
"..\\src\\Table.svelte",
"..\\src\\Button.svelte"
],
"sourcesContent": [
"<script>\nimport createApp from \"./createApp\";\nimport { props } from \"./props\";\n\nlet _bb;\n\nconst _appPromise = createApp();\n_appPromise.then(a => _bb = a);\n\nconst testProps = props.hiddenNav;\n\nlet currentComponent;\n\n$: {\n if(_bb && currentComponent) {\n _bb.initialiseComponent(testProps, currentComponent);\n }\n}\n\n\n\n</script>\n\n{#await _appPromise}\nloading\n{:then _bb}\n\n<div id=\"current_component\" bind:this={currentComponent}>\n</div>\n\n{/await}\n\n\n<style>\n#current_component {\n height: 100%;\n width: 100%;\n}\n</style>\n\n",
"<script>\r\nimport { onMount } from 'svelte'\r\nimport {buildStyle} from \"./buildStyle\";\r\n\r\nexport let gridTemplateRows =\"\";\r\nexport let gridTemplateColumns =\"\";\r\nexport let children = [];\r\nexport let width = \"auto\";\r\nexport let height = \"auto\";\r\nexport let containerClass=\"\";\r\nexport let itemContainerClass=\"\";\r\n\r\n/*\"gridColumnStart\":\"string\",\r\n\"gridColumnEnd\":\"string\",\r\n\"gridRowStart\":\"string\",\r\n\"gridRowEnd\":\"string\"*/\r\n\r\n\r\nexport let _bb;\r\n\r\nlet style=\"\";\r\nlet htmlElements = {};\r\n\r\n$ : {\r\n if(_bb && htmlElements) {\r\n for(let el in htmlElements) {\r\n _bb.initialiseComponent(\r\n children[el].control,\r\n htmlElements[el]\r\n );\r\n }\r\n }\r\n}\r\n\r\nconst childStyle = child => \r\n buildStyle({\r\n \"grid-column-start\": child.gridColumnStart,\r\n \"grid-column-end\": child.gridColumnEnd,\r\n \"grid-column\": child.gridColumn,\r\n \"grid-row-start\": child.gridRowStart,\r\n \"grid-row-end\": child.gridRowStart,\r\n \"grid-row\": child.gridRow,\r\n });\r\n\r\n</script>\r\n\r\n<div class=\"root {containerClass}\"\r\n style=\"width: {width}; height: {height}; grid-template-columns: {gridTemplateColumns}; grid-template-rows: {gridTemplateRows};\">\r\n {#each children as child, index}\r\n <div class=\"{itemContainerClass}\"\r\n style={childStyle(child)}\r\n bind:this={htmlElements[index]}>\r\n </div>\r\n {/each}\r\n</div>\r\n\r\n<style>\r\n\r\n.root {\r\n display: grid;\r\n}\r\n\r\n</style>",
"<script>\n\nimport Textbox from \"./Textbox.svelte\";\nimport Form from \"./Form.svelte\";\nimport Button from \"./Button.svelte\";\n\nexport let usernameLabel = \"Username\";\nexport let passwordLabel = \"Password\";\nexport let loginButtonLabel = \"Login\";\nexport let loginRedirect = \"\";\nexport let logo = \"\";\nexport let buttonClass = \"\";\nexport let inputClass=\"\"\n\nexport let _bb;\n\nlet username = \"\";\nlet password = \"\";\nlet busy = false;\nlet incorrect = false;\nlet _logo = \"\";\nlet _buttonClass = \"\";\nlet _inputClass = \"\";\n\n$: {\n _logo = _bb.relativeUrl(logo);\n _buttonClass = buttonClass || \"default-button\";\n _inputClass = inputClass || \"default-input\";\n}\n\nconst login = () => {\n busy = true;\n _bb.api.post(\"/api/authenticate\", {username, password})\n .then(r => {\n busy = false;\n if(r.status === 200) {\n return r.json();\n } else {\n incorrect = true;\n return;\n }\n })\n .then(user => {\n if(user) {\n localStorage.setItem(\"budibase:user\", user);\n location.reload();\n }\n })\n}\n\n</script>\n\n<div class=\"root\">\n\n <div class=\"content\">\n\n {#if _logo}\n <div class=\"logo-container\">\n <img src={_logo} alt=\"logo\"/>\n </div>\n {/if}\n\n <div class=\"form-root\">\n <div class=\"label\">\n {usernameLabel}\n </div>\n <div class=\"control\">\n <input bind:value={username} type=\"text\" class={_inputClass}/>\n </div>\n <div class=\"label\">\n {passwordLabel}\n </div>\n <div class=\"control\">\n <input bind:value={password} type=\"password\" class={_inputClass}/>\n </div>\n </div>\n\n <div class=\"login-button-container\">\n <button disabled={busy} \n on:click={login}\n class={_buttonClass}>\n {loginButtonLabel}\n </button>\n </div>\n\n {#if incorrect}\n <div class=\"incorrect-details-panel\">\n Incorrect username or password\n </div>\n {/if}\n\n </div>\n\n</div>\n\n<style>\n\n.root {\n height: 100%;\n display:grid;\n grid-template-columns: [left] 1fr [middle] auto [right] 1fr;\n grid-template-rows: [top] 1fr [center] auto [bottom] 1fr;\n}\n\n.content {\n grid-column-start: middle;\n grid-row-start: center;\n width: 400px;\n}\n\n.logo-container {\n margin-bottom: 20px\n}\n\n.logo-container > img {\n max-width: 100%;\n}\n\n.login-button-container {\n text-align: right;\n margin-top: 20px;\n}\n\n.incorrect-details-panel {\n margin-top: 30px;\n padding: 10px;\n border-style: solid;\n border-width: 1px;\n border-color: maroon;\n border-radius: 1px;\n text-align: center;\n color: maroon;\n background-color: mistyrose;\n}\n\n.form-root {\n display: grid;\n grid-template-columns: [label] auto [control] 1fr; /* [overflow] auto;*/\n}\n\n.label {\n grid-column-start: label;\n padding: 5px 10px;\n vertical-align: middle;\n}\n.control {\n grid-column-start: control;\n padding: 5px 10px;\n}\n\n.default-input {\n\tfont-family: inherit;\n\tfont-size: inherit;\n\tpadding: 0.4em;\n\tmargin: 0 0 0.5em 0;\n\tbox-sizing: border-box;\n\tborder: 1px solid #ccc;\n border-radius: 2px;\n width: 100%;\n}\n\n.default-button {\n\tfont-family: inherit;\n\tfont-size: inherit;\n\tpadding: 0.4em;\n\tmargin: 0 0 0.5em 0;\n\tbox-sizing: border-box;\n\tborder: 1px solid #ccc;\n\tborder-radius: 2px;\n\tcolor: #333;\n\tbackground-color: #f4f4f4;\n\toutline: none;\n}\n\n.default-button:active {\n\tbackground-color: #ddd;\n}\n\n.default-button:focus {\n\tborder-color: #666;\n}\n\n</style>",
"<script>\nexport let containerClass = \"\";\nexport let formControls = [];\n\nexport let _bb;\n\nlet htmlElements = {};\nlet labels = {};\n\n$ : {\n let cIndex = 0;\n for(let c of formControls) {\n labels[cIndex] = c.label;\n cIndex++;\n }\n\n if(_bb && htmlElements) {\n for(let el in htmlElements) {\n _bb.initialiseComponent(\n formControls[el].control,\n htmlElements[el]\n );\n }\n }\n}\n\n</script>\n\n<div class=\"form-root {containerClass}\">\n {#each formControls as child, index}\n <div class=\"label\">{labels[index]}</div>\n <div class=\"control\"\n bind:this={htmlElements[index]}>\n </div>\n {/each}\n</div>\n\n<style>\n.form-root {\n display: grid;\n grid-template-columns: [label] auto [control] 1fr; /* [overflow] auto;*/\n}\n\n.label {\n grid-column-start: label;\n padding: 5px 10px;\n vertical-align: middle;\n}\n.control {\n grid-column-start: control;\n padding: 5px 10px;\n}\n.overflow {\n grid-column-start: overflow;\n}\n.full-width {\n width: 100%;\n}\n</style>",
"<script>\r\nimport cssVars from \"./cssVars\";\r\n\r\nexport let navBarBackground = \"\";\r\nexport let navBarBorder=\"\";\r\nexport let navBarColor=\"\";\r\nexport let selectedItemBackground=\"\";\r\nexport let selectedItemColor=\"\";\r\nexport let selectedItemBorder=\"\";\r\nexport let itemHoverBackground=\"\";\r\nexport let itemHoverColor=\"\";\r\nexport let items = [];\r\nexport let hideNavBar=false;\r\nexport let selectedItem=\"\";\r\n\r\nexport let _bb;\r\n\r\nlet selectedIndex = -1;\r\nlet styleVars={};\r\nlet components = {};\r\nlet componentElements = {}\r\n\r\n\r\nconst hasComponentElements = () => \r\n Object.getOwnPropertyNames(componentElements).length > 0;\r\n\r\n$: {\r\n styleVars = {\r\n navBarBackground, navBarBorder,\r\n navBarColor, selectedItemBackground,\r\n selectedItemColor, selectedItemBorder,\r\n itemHoverBackground, itemHoverColor\r\n };\r\n\r\n if(items && items.length > 0 && hasComponentElements()) {\r\n const currentSelectedItem = selectedIndex > 0\r\n ? items[selectedIndex].title\r\n : \"\";\r\n if(selectedItem && currentSelectedItem !== selectedItem) {\r\n let i=0;\r\n for(let item of items) {\r\n if(item.title === selectedItem) {\r\n onSelectItem(i)();\r\n }\r\n i++;\r\n }\r\n } else if(!currentSelectedItem) {\r\n onSelectItem(0);\r\n }\r\n }\r\n}\r\n\r\nconst onSelectItem = (index) => () => {\r\n selectedIndex = index;\r\n if(!components[index]) {\r\n const comp = _bb.initialiseComponent(\r\n items[index].component, componentElements[index]);\r\n components[index] = comp; \r\n }\r\n}\r\n\r\n\r\n</script>\r\n\r\n<div class=\"root\" use:cssVars={styleVars}>\r\n {#if !hideNavBar}\r\n <div class=\"navbar\">\r\n {#each items as navItem, index}\r\n <div class=\"navitem\"\r\n on:click={onSelectItem(index)}\r\n class:selected={selectedIndex === index}>\r\n {navItem.title}\r\n </div>\r\n {/each}\r\n </div>\r\n {/if}\r\n {#each items as navItem, index}\r\n\r\n <div class=\"content\"\r\n bind:this={componentElements[index]}>\r\n </div>\r\n {/each}\r\n</div>\r\n\r\n<style>\r\n\r\n.root {\r\n height: 100%;\r\n width:100%;\r\n grid-template-columns: [navbar] auto [content] 1fr;\r\n display: grid;\r\n}\r\n\r\n.navbar {\r\n grid-column: navbar;\r\n background: var(--navBarBackground);\r\n border: var(--navBarBorder);\r\n color: var(--navBarColor);\r\n}\r\n\r\n.navitem {\r\n padding: 10px 17px;\r\n cursor: pointer;\r\n}\r\n\r\n.navitem:hover {\r\n background: var(--itemHoverBackground);\r\n color: var(--itemHoverColor);\r\n}\r\n\r\n.navitem.selected {\r\n background: var(--selectedItemBackground);\r\n border: var(--selectedItemBorder);\r\n color: var(--selectedItemColor);\r\n}\r\n\r\n.content {\r\n grid-column: content;\r\n}\r\n\r\n</style>\r\n\r\n",
"<script>\n\nexport let value=\"\";\nexport let hideValue = false;\nexport let className = \"default\";\n\nexport let _bb;\n\nlet actualValue = \"\";\n$: {\n\tif(_bb && value._isstate) {\n\t\t_bb.store.subscribe(s => {\n\t\t\tactualValue = _bb.store.getValue(s, value);\n\t\t});\n\t}\n}\n\nconst onchange = (ev) => {\n\tif(_bb && value._isstate) {\n\t\t_bb.store.setValue(value, ev.target.value);\n\t} else if(!value._isstate) {\n\t\tactualValue = ev.target.value;\n\t}\n}\n\n</script>\n\n{#if hideValue}\n<input class={className} \n\t type=\"password\" \n\t value={actualValue} on:change/>\n{:else}\n<input class={className} type=\"text\" value={actualValue}/>\n{/if}\n\n<style>\n.default {\n width: 100%;\n\tfont-family: inherit;\n\tfont-size: inherit;\n\tpadding: 0.4em;\n\tmargin: 0 0 0.5em 0;\n\tbox-sizing: border-box;\n\tborder: 1px solid #ccc;\n border-radius: 2px;\n width: 100%;\n}\n\n.default:disabled {\n\tcolor: #ccc;\n}\n\n</style>",
"<script>\r\nimport {buildStyle} from \"./buildStyle\";\r\nimport cssVars from \"./cssVars\";\r\n\r\nexport let component=\"\";\r\nexport let text=\"\";\r\nexport let containerClass=\"\";\r\nexport let background=\"\";\r\nexport let border=\"\";\r\nexport let borderRadius=\"\";\r\nexport let font=\"\";\r\nexport let display=\"\";\r\nexport let textAlign=\"\";\r\nexport let color=\"\";\r\nexport let padding=\"\";\r\nexport let margin=\"\";\r\nexport let hoverBackground=\"\";\r\nexport let hoverColor=\"\";\r\nexport let onClick;\r\nexport let height;\r\nexport let width;\r\n\r\nexport let _bb;\r\n\r\nlet styleVars;\r\nlet style=\"\";\r\nlet componentElement;\r\n\r\n$: {\r\n style=buildStyle({\r\n border, background, font, margin,\r\n padding, display, color, height, width,\r\n \"text-align\": textAlign,\r\n \"border-radius\":borderRadius,\r\n cursor: onClick ? \"pointer\" : \"none\"\r\n });\r\n\r\n if(_bb && component) {\r\n _bb.initialiseComponent(component, componentElement);\r\n }\r\n\r\n styleVars = {\r\n hoverBackground:hoverBackground || background, \r\n hoverColor:hoverColor || color\r\n }\r\n}\r\n\r\nconst clickHandler = () => {\r\n if(onClick) onClick();\r\n}\r\n\r\n</script>\r\n\r\n<div class=\"{containerClass} panel\" \r\n style={style}\r\n use:cssVars={styleVars}\r\n this:bind={componentElement}\r\n on:click={clickHandler}>\r\n {component && component._component ? \"\" : text}\r\n</div>\r\n\r\n<style>\r\n\r\n.panel:hover {\r\n background: var(--hoverBackground);\r\n color: var(--hoverColor);\r\n\r\n}\r\n\r\n</style>\r\n",
"<script>\n\nimport { emptyProps } from \"./emptyProps\";\n\nexport let direction = \"horizontal\";\nexport let children = [];\nexport let width = \"auto\";\nexport let height = \"auto\";\nexport let containerClass=\"\";\nexport let itemContainerClass=\"\";\nexport let onLoad;\n\nexport let data=[];\nexport let dataItemComponent;\n\nexport let _bb;\n\nlet staticHtmlElements = {};\nlet staticComponents = {};\nlet dataBoundElements = {};\nlet dataBoundComponents = {};\n\nlet onLoadCalled = false;\n\nconst hasDataBoundComponents = () => \n Object.getOwnPropertyNames(dataBoundComponents).length > 0;\n\nconst hasData = () => \n Array.isArray(data) && data.length > 0;\n\nconst hasStaticComponents = () => {\n return Object.getOwnPropertyNames(staticComponents).length > 0;\n}\n\n$: {\n\n if(staticHtmlElements) {\n if(hasStaticComponents()) {\n for(let c in staticComponents) {\n staticComponents[c].$destroy();\n }\n staticComponents = {};\n }\n\n for(let el in staticHtmlElements) {\n staticComponents[el] = _bb.initialiseComponent(\n children[el].control,\n staticHtmlElements[el]\n );\n }\n }\n \n\n if(hasDataBoundComponents()) {\n for(let c in dataBoundComponents) {\n dataBoundComponents[c].$destroy();\n }\n dataBoundComponents = {};\n }\n\n if(hasData()) {\n let index = 0;\n for(let d in dataBoundElements) {\n _bb.initialiseComponent(\n dataItemComponent,\n dataBoundElements[d],\n data[parseInt(d)]\n );\n }\n }\n\n if(!onLoadCalled && onLoad && !onLoad.isPlaceholder) {\n onLoad();\n onLoadCalled = true;\n }\n}\n\n\n</script>\n\n<div class=\"root {containerClass}\"\n style=\"width: {width}; height: {height}\">\n\n {#if children}\n {#each children as child, index}\n <div class={direction}>\n <div class=\"{itemContainerClass}\"\n bind:this={staticHtmlElements[index]}>\n </div>\n </div>\n {/each}\n {/if}\n\n {#if data && data.length > 0}\n {#each data as child, index}\n <div class={direction}>\n <div class=\"{itemContainerClass}\"\n bind:this={dataBoundElements[index]}>\n </div>\n </div>\n {/each}\n {/if}\n</div>\n\n<style>\n\n.horizontal {\n display:inline-block;\n}\n\n.vertical {\n display: block;\n}\n\n</style>",
"<script>\r\n\r\nexport let columns=[];\r\nexport let data=\"\";\r\nexport let tableClass=\"\";\r\nexport let theadClass=\"\";\r\nexport let tbodyClass=\"\";\r\nexport let trClass=\"\";\r\nexport let thClass=\"\";\r\nexport let onRowClick;\r\n\r\nexport let _bb;\r\n\r\nconst rowClickHandler = (row) => () => {\r\n onRowClick(row);\r\n}\r\n\r\n</script>\r\n\r\n <table class={tableClass}>\r\n <thead class={theadClass}>\r\n <tr class={trClass}>\r\n {#each columns as col}\r\n <th class={thClass}>{col.title}</th>\r\n {/each}\r\n </tr>\r\n </thead>\r\n <tbody class={tbodyClass}>\r\n {#each data as row}\r\n <tr class={trClass}\r\n on:click={rowClickHandler(row)} >\r\n {#each columns as col}\r\n <th class={thClass}>{_bb.getStateOrValue(col.value, row)}</th>\r\n {/each}\r\n </tr>\r\n {/each}\r\n </tbody>\r\n</table> \r\n\r\n<style>\r\n\r\n.table-default {\r\n width: 100%;\r\n margin-bottom: 1rem;\r\n color: #212529;\r\n border-collapse: collapse;\r\n}\r\n\r\n.table-default .thead-default .th-default {\r\n vertical-align: bottom;\r\n border-bottom: 2px solid #dee2e6;\r\n font-weight: bold;\r\n}\r\n\r\n.table-default .th-default {\r\n padding: .75rem;\r\n vertical-align: top;\r\n border-top: 1px solid #dee2e6;\r\n font-weight: normal;\r\n}\r\n\r\n.th-default {\r\n text-align: inherit;\r\n}\r\n\r\n.table-default .tbody-default .tr-default:hover {\r\n color: #212529;\r\n background-color: rgba(0,0,0,.075);\r\n cursor: pointer;\r\n}\r\n\r\n</style>",
"<script>\nexport let className = \"default\";\nexport let disabled = false;\nexport let contentText;\nexport let contentComponent;\nexport let onClick = () => {};\n\nexport let _bb;\nlet contentComponentContainer;\n\n$:{\n\tif(_bb && contentComponentContainer && contentComponent._component)\n\t\t_bb.initialiseComponent(contentComponent, contentComponentContainer);\n}\n\n\nconst clickHandler = () => {\n\tif(onClick) onClick();\n}\n\n</script>\n\n\n<button class={className} {disabled} on:click={clickHandler}>\n {#if contentComponent && contentComponent._component}\n\t<div bind:this={contentComponentContainer}>\n\t</div>\n {:else if contentText}\n {contentText}\n {:else}\n <slot />\n {/if}\n</button>\n\n\n<style>\n\n.default {\n\tfont-family: inherit;\n\tfont-size: inherit;\n\tpadding: 0.4em;\n\tmargin: 0 0 0.5em 0;\n\tbox-sizing: border-box;\n\tborder: 1px solid #ccc;\n\tborder-radius: 2px;\n\tcolor: #333;\n\tbackground-color: #f4f4f4;\n\toutline: none;\n}\n\n.default:active {\n\tbackground-color: #ddd;\n}\n\n.default:focus {\n\tborder-color: #666;\n}\n\n</style>"
],
"names": [],
"mappings": "AAkCA,kBAAkB,eAAC,CAAC,AAChB,MAAM,CAAE,IAAI,CACZ,KAAK,CAAE,IAAI,AACf,CAAC;ACqBD,KAAK,eAAC,CAAC,AACH,OAAO,CAAE,IAAI,AACjB,CAAC;ACqCD,KAAK,cAAC,CAAC,AACH,MAAM,CAAE,IAAI,CACZ,QAAQ,IAAI,CACZ,qBAAqB,CAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAC3D,kBAAkB,CAAE,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,AAC5D,CAAC,AAED,QAAQ,cAAC,CAAC,AACN,iBAAiB,CAAE,MAAM,CACzB,cAAc,CAAE,MAAM,CACtB,KAAK,CAAE,KAAK,AAChB,CAAC,AAED,eAAe,cAAC,CAAC,AACb,aAAa,CAAE,IAAI;AACvB,CAAC,AAED,6BAAe,CAAG,GAAG,cAAC,CAAC,AACnB,SAAS,CAAE,IAAI,AACnB,CAAC,AAED,uBAAuB,cAAC,CAAC,AACrB,UAAU,CAAE,KAAK,CACjB,UAAU,CAAE,IAAI,AACpB,CAAC,AAED,wBAAwB,cAAC,CAAC,AACtB,UAAU,CAAE,IAAI,CAChB,OAAO,CAAE,IAAI,CACb,YAAY,CAAE,KAAK,CACnB,YAAY,CAAE,GAAG,CACjB,YAAY,CAAE,MAAM,CACpB,aAAa,CAAE,GAAG,CAClB,UAAU,CAAE,MAAM,CAClB,KAAK,CAAE,MAAM,CACb,gBAAgB,CAAE,SAAS,AAC/B,CAAC,AAED,UAAU,cAAC,CAAC,AACR,OAAO,CAAE,IAAI,CACb,qBAAqB,CAAE,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,AACrD,CAAC,AAED,MAAM,cAAC,CAAC,AACJ,iBAAiB,CAAE,KAAK,CACxB,OAAO,CAAE,GAAG,CAAC,IAAI,CACjB,cAAc,CAAE,MAAM,AAC1B,CAAC,AACD,QAAQ,cAAC,CAAC,AACN,iBAAiB,CAAE,OAAO,CAC1B,OAAO,CAAE,GAAG,CAAC,IAAI,AACrB,CAAC,AAED,cAAc,cAAC,CAAC,AACf,WAAW,CAAE,OAAO,CACpB,SAAS,CAAE,OAAO,CAClB,OAAO,CAAE,KAAK,CACd,MAAM,CAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CACnB,UAAU,CAAE,UAAU,CACtB,MAAM,CAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CACnB,aAAa,CAAE,GAAG,CAClB,KAAK,CAAE,IAAI,AACf,CAAC,AAED,eAAe,cAAC,CAAC,AAChB,WAAW,CAAE,OAAO,CACpB,SAAS,CAAE,OAAO,CAClB,OAAO,CAAE,KAAK,CACd,MAAM,CAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CACnB,UAAU,CAAE,UAAU,CACtB,MAAM,CAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CACtB,aAAa,CAAE,GAAG,CAClB,KAAK,CAAE,IAAI,CACX,gBAAgB,CAAE,OAAO,CACzB,OAAO,CAAE,IAAI,AACd,CAAC,AAED,6BAAe,OAAO,AAAC,CAAC,AACvB,gBAAgB,CAAE,IAAI,AACvB,CAAC,AAED,6BAAe,MAAM,AAAC,CAAC,AACtB,YAAY,CAAE,IAAI,AACnB,CAAC;AC9ID,UAAU,cAAC,CAAC,AACR,OAAO,CAAE,IAAI,CACb,qBAAqB,CAAE,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,AACrD,CAAC,AAED,MAAM,cAAC,CAAC,AACJ,iBAAiB,CAAE,KAAK,CACxB,OAAO,CAAE,GAAG,CAAC,IAAI,CACjB,cAAc,CAAE,MAAM,AAC1B,CAAC,AACD,QAAQ,cAAC,CAAC,AACN,iBAAiB,CAAE,OAAO,CAC1B,OAAO,CAAE,GAAG,CAAC,IAAI,AACrB,CAAC,AACD,SAAS,cAAC,CAAC,AACP,iBAAiB,CAAE,QAAQ,AAC/B,CAAC,AACD,WAAW,cAAC,CAAC,AACT,KAAK,CAAE,IAAI,AACf,CAAC;AC6BD,KAAK,cAAC,CAAC,AACH,MAAM,CAAE,IAAI,CACZ,MAAM,IAAI,CACV,qBAAqB,CAAE,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAClD,OAAO,CAAE,IAAI,AACjB,CAAC,AAED,OAAO,cAAC,CAAC,AACL,WAAW,CAAE,MAAM,CACnB,UAAU,CAAE,IAAI,kBAAkB,CAAC,CACnC,MAAM,CAAE,IAAI,cAAc,CAAC,CAC3B,KAAK,CAAE,IAAI,aAAa,CAAC,AAC7B,CAAC,AAED,QAAQ,cAAC,CAAC,AACN,OAAO,CAAE,IAAI,CAAC,IAAI,CAClB,MAAM,CAAE,OAAO,AACnB,CAAC,AAED,sBAAQ,MAAM,AAAC,CAAC,AACZ,UAAU,CAAE,IAAI,qBAAqB,CAAC,CACtC,KAAK,CAAE,IAAI,gBAAgB,CAAC,AAChC,CAAC,AAED,QAAQ,SAAS,cAAC,CAAC,AACf,UAAU,CAAE,IAAI,wBAAwB,CAAC,CACzC,MAAM,CAAE,IAAI,oBAAoB,CAAC,CACjC,KAAK,CAAE,IAAI,mBAAmB,CAAC,AACnC,CAAC,AAED,QAAQ,cAAC,CAAC,AACN,WAAW,CAAE,OAAO,AACxB,CAAC;AClFD,QAAQ,eAAC,CAAC,AACN,KAAK,CAAE,IAAI,CACd,WAAW,CAAE,OAAO,CACpB,SAAS,CAAE,OAAO,CAClB,OAAO,CAAE,KAAK,CACd,MAAM,CAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CACnB,UAAU,CAAE,UAAU,CACtB,MAAM,CAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CACnB,aAAa,CAAE,GAAG,CAClB,KAAK,CAAE,IAAI,AACf,CAAC,AAED,uBAAQ,SAAS,AAAC,CAAC,AAClB,KAAK,CAAE,IAAI,AACZ,CAAC;ACaD,oBAAM,MAAM,AAAC,CAAC,AACV,UAAU,CAAE,IAAI,iBAAiB,CAAC,CAClC,KAAK,CAAE,IAAI,YAAY,CAAC,AAE5B,CAAC;ACuCD,WAAW,cAAC,CAAC,AACT,QAAQ,YAAY,AACxB,CAAC,AAED,SAAS,cAAC,CAAC,AACP,OAAO,CAAE,KAAK,AAClB,CAAC;ACvED,cAAc,cAAC,CAAC,AACZ,KAAK,CAAE,IAAI,CACX,aAAa,CAAE,IAAI,CACnB,KAAK,CAAE,OAAO,CACd,eAAe,CAAE,QAAQ,AAC7B,CAAC,AAED,4BAAc,CAAC,cAAc,CAAC,WAAW,cAAC,CAAC,AACvC,cAAc,CAAE,MAAM,CACtB,aAAa,CAAE,GAAG,CAAC,KAAK,CAAC,OAAO,CAChC,WAAW,CAAE,IAAI,AACrB,CAAC,AAED,4BAAc,CAAC,WAAW,cAAC,CAAC,AACxB,OAAO,CAAE,MAAM,CACf,cAAc,CAAE,GAAG,CACnB,UAAU,CAAE,GAAG,CAAC,KAAK,CAAC,OAAO,CAC7B,WAAW,CAAE,MAAM,AACvB,CAAC,AAED,WAAW,cAAC,CAAC,AACT,UAAU,CAAE,OAAO,AACvB,CAAC,AAED,4BAAc,CAAC,cAAc,CAAC,yBAAW,MAAM,AAAC,CAAC,AAC7C,KAAK,CAAE,OAAO,CACd,gBAAgB,CAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAClC,MAAM,CAAE,OAAO,AACnB,CAAC;AChCD,QAAQ,eAAC,CAAC,AACT,WAAW,CAAE,OAAO,CACpB,SAAS,CAAE,OAAO,CAClB,OAAO,CAAE,KAAK,CACd,MAAM,CAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CACnB,UAAU,CAAE,UAAU,CACtB,MAAM,CAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CACtB,aAAa,CAAE,GAAG,CAClB,KAAK,CAAE,IAAI,CACX,gBAAgB,CAAE,OAAO,CACzB,OAAO,CAAE,IAAI,AACd,CAAC,AAED,uBAAQ,OAAO,AAAC,CAAC,AAChB,gBAAgB,CAAE,IAAI,AACvB,CAAC,AAED,uBAAQ,MAAM,AAAC,CAAC,AACf,YAAY,CAAE,IAAI,AACnB,CAAC"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,103 +0,0 @@
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: [],
allowedModelNodeIds: [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: [],
allowedModelNodeIds: [1],
nodeId: 4,
},
{
name: "everyones_invoices",
type: "index",
map: "return {...record};",
filter: "",
indexType: "ancestor",
getShardName: "",
getSortKey: "record.id",
aggregateGroups: [],
allowedModelNodeIds: [2],
nodeId: 6,
},
],
nodeId: 0,
},
componentLibraries: ["budibase-standard-components"],
appRootPath: "/testApp2",
props: {},
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -1,62 +0,0 @@
html, body {
position: relative;
width: 100%;
height: 100%;
}
body {
color: #333;
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
a {
color: rgb(0,100,200);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a:visited {
color: rgb(0,80,160);
}
label {
display: block;
}
input, button, select, textarea {
font-family: inherit;
font-size: inherit;
padding: 0.4em;
margin: 0 0 0.5em 0;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 2px;
}
input:disabled {
color: #ccc;
}
input[type="range"] {
height: 0;
}
button {
color: #333;
background-color: #f4f4f4;
outline: none;
}
button:active {
background-color: #ddd;
}
button:focus {
border-color: #666;
}

View File

@ -1,18 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset='utf8'>
<meta name='viewport' content='width=device-width'>
<title>Svelte app</title>
<link rel='icon' type='image/png' href='/favicon.png'>
<link rel='stylesheet' href='/global.css'>
<link rel='stylesheet' href='/bundle.css'>
</head>
<body>
<script src='/bundle.js'></script>
</body>
</html>

View File

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

View File

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

View File

@ -1,86 +0,0 @@
const { readdir, stat, copyFile } = require("fs-extra")
const { constants } = require("fs")
const { join, basename } = require("path")
const packagesFolder = ".."
const jsFile = dir => join(dir, "index.js")
const jsMapFile = dir => join(dir, "index.js.map")
const sourceJs = jsFile("dist")
const sourceJsMap = jsMapFile("dist")
const componentsFile = "components.json"
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 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 copyComponentsJson(nodeModules(app))
await copySourceJs(join(publicMain(app), "dist"))
await copySourceJsMap(join(publicMain(app), "dist"))
await copySourceJs(join(publicUnauth(app), "dist"))
await copySourceJsMap(join(publicUnauth(app), "dist"))
}
})()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -59,6 +59,7 @@
"@babel/preset-env": "^7.5.5", "@babel/preset-env": "^7.5.5",
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"@rollup/plugin-alias": "^3.0.1", "@rollup/plugin-alias": "^3.0.1",
"@rollup/plugin-json": "^4.0.3",
"@sveltech/routify": "1.5.0-beta.40", "@sveltech/routify": "1.5.0-beta.40",
"babel-jest": "^24.8.0", "babel-jest": "^24.8.0",
"browser-sync": "^2.26.7", "browser-sync": "^2.26.7",

View File

@ -9,14 +9,15 @@ 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 replace from "rollup-plugin-replace" import replace from "rollup-plugin-replace"
import json from '@rollup/plugin-json'; import json from "@rollup/plugin-json"
import path from "path" import path from "path"
const production = !process.env.ROLLUP_WATCH const production = !process.env.ROLLUP_WATCH
const lodash_fp_exports = [ const lodash_fp_exports = [
"flow",
"pipe",
"union", "union",
"reduce", "reduce",
"isUndefined", "isUndefined",
@ -120,7 +121,16 @@ const coreExternal = [
] ]
const customResolver = resolve({ const customResolver = resolve({
extensions: [".mjs", ".js", ".jsx", ".json", ".sass", ".scss", ".svelte"] extensions: [
".mjs",
".js",
".jsx",
".json",
".sass",
".scss",
".svelte",
".css",
],
}) })
const projectRootDir = path.resolve(__dirname) const projectRootDir = path.resolve(__dirname)
@ -150,7 +160,7 @@ export default {
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: "assets", dest: outputpath },
{ {
src: "node_modules/@budibase/client/dist/budibase-client.esm.mjs", src: "node_modules/@budibase/client/dist/budibase-client.esm.mjs",
dest: outputpath, dest: outputpath,

View File

@ -16,7 +16,15 @@
}) })
} }
onMount(() => { onMount(async () => {
const res = await fetch(`/api/client/id`)
const json = await res.json()
store.update(state => {
state.clientId = json
return state
})
window.addEventListener("error", showErrorBanner) window.addEventListener("error", showErrorBanner)
window.addEventListener("unhandledrejection", showErrorBanner) window.addEventListener("unhandledrejection", showErrorBanner)
}) })
@ -26,6 +34,8 @@
<AppNotification /> <AppNotification />
<Modal> {#if $store.clientId}
<Modal>
<Router {routes} /> <Router {routes} />
</Modal> </Modal>
{/if}

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,5 +1,5 @@
const apiCall = method => (url, body) => const apiCall = method => async (url, body) => {
fetch(url, { const response = await fetch(url, {
method: method, method: method,
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@ -7,6 +7,13 @@ const apiCall = method => (url, body) =>
body: body && JSON.stringify(body), body: body && JSON.stringify(body),
}) })
// if (response.status === 500) {
// throw new Error("Server Error");
// }
return response
}
const post = apiCall("POST") const post = apiCall("POST")
const get = apiCall("GET") const get = apiCall("GET")
const patch = apiCall("PATCH") const patch = apiCall("PATCH")

View File

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

View File

@ -1,5 +1,5 @@
import { filter, map, reduce, toPairs } from "lodash/fp"
import { pipe } from "components/common/core" import { pipe } from "components/common/core"
import { filter, map, reduce, toPairs } from "lodash/fp"
const self = n => n const self = n => n
const join_with = delimiter => a => a.join(delimiter) const join_with = delimiter => a => a.join(delimiter)
@ -88,7 +88,7 @@ const css_map = {
} }
export const generate_rule = ([name, values]) => export const generate_rule = ([name, values]) =>
`${css_map[name].name}: ${css_map[name].generate(values)} !important;` `${css_map[name].name}: ${css_map[name].generate(values)};`
const handle_grid = (acc, [name, value]) => { const handle_grid = (acc, [name, value]) => {
let tmp = [] let tmp = []
@ -113,9 +113,7 @@ const object_to_css_string = [
export const generate_css = ({ layout, position }) => { export const generate_css = ({ layout, position }) => {
let _layout = pipe(layout, object_to_css_string) let _layout = pipe(layout, object_to_css_string)
if (_layout.length) { if (_layout.length) {
_layout += `\ndisplay: ${ _layout += `\ndisplay: ${_layout.includes("flex") ? "flex" : "grid"};`
_layout.includes("flex") ? "flex" : "grid"
} !important;`
} }
return { return {

View File

@ -1,55 +1,31 @@
import { flatten, values, uniq, map } from "lodash/fp" /**
import { pipe } from "components/common/core" * Fetches the definitions for component library components. This includes
* their props and other metadata from components.json.
* @param {string} clientId - ID of the current client
* @param {string} appId - ID of the currently running app
*/
export const fetchComponentLibDefinitions = async (clientId, appId) => {
const LIB_DEFINITION_URL = `/${clientId}/${appId}/components/definitions`
try {
const libDefinitionResponse = await fetch(LIB_DEFINITION_URL)
return await libDefinitionResponse.json()
} catch (err) {
console.error(`Error fetching component definitions for ${appId}`, err)
}
}
export const loadLibs = async (appName, appPackage) => { /**
* Loads the JavaScript files for app component libraries and returns a map of their modules.
* @param {object} application - package definition
*/
export const fetchComponentLibModules = async application => {
const allLibraries = {} const allLibraries = {}
for (let lib of libsFromPages(appPackage.pages)) { for (let libraryName of application.componentLibraries) {
const libModule = await import(makeLibraryUrl(appName, lib)) const LIBRARY_URL = `/${application._id}/componentlibrary?library=${libraryName}`
allLibraries[lib] = libModule const libraryModule = await import(LIBRARY_URL)
allLibraries[libraryName] = libraryModule
} }
return allLibraries return allLibraries
} }
export const loadLibUrls = (appName, appPackage) => {
const allLibraries = []
for (let lib of libsFromPages(appPackage.pages)) {
const libUrl = makeLibraryUrl(appName, lib)
allLibraries.push({ libName: lib, importPath: libUrl })
}
return allLibraries
}
export const loadLib = async (appName, lib, allLibs) => {
allLibs[lib] = await import(makeLibraryUrl(appName, lib))
return allLibs
}
export const makeLibraryUrl = (appName, lib) =>
`/_builder/${appName}/componentlibrary?lib=${encodeURI(lib)}`
export const libsFromPages = pages =>
pipe(pages, [values, map(p => p.componentLibraries), flatten, uniq])
export const libUrlsForPreview = (appPackage, pageName) => {
const resolve = path => {
let file = appPackage.components.libraryPaths[path]
if (file.startsWith("./")) file = file.substring(2)
if (file.startsWith("/")) file = file.substring(1)
let newPath = path
if (!newPath.startsWith("./") && !newPath.startsWith("/")) {
newPath = `/node_modules/${path}`
}
return {
importPath: `/lib${newPath}/${file}`,
libName: path,
}
}
return pipe([appPackage.pages[pageName]], [libsFromPages, map(resolve)])
}

View File

@ -1,24 +1,12 @@
import { writable } from "svelte/store" import { writable } from "svelte/store"
import api from "../api" import api from "../api"
import { cloneDeep, sortBy, find, remove } from "lodash/fp"
import { hierarchy as hierarchyFunctions } from "../../../../core/src"
import {
getNode,
validate,
constructHierarchy,
templateApi,
isIndex,
canDeleteIndex,
canDeleteModel,
} from "components/common/core"
export const getBackendUiStore = () => { export const getBackendUiStore = () => {
const INITIAL_BACKEND_UI_STATE = { const INITIAL_BACKEND_UI_STATE = {
selectedView: {
records: [],
name: "",
},
breadcrumbs: [], breadcrumbs: [],
models: [],
views: [],
users: [],
selectedDatabase: {}, selectedDatabase: {},
selectedModel: {}, selectedModel: {},
} }
@ -27,12 +15,19 @@ export const getBackendUiStore = () => {
store.actions = { store.actions = {
database: { database: {
select: db => select: async db => {
const modelsResponse = await api.get(`/api/${db.id}/models`)
const viewsResponse = await api.get(`/api/${db.id}/views`)
const models = await modelsResponse.json()
const views = await viewsResponse.json()
store.update(state => { store.update(state => {
state.selectedDatabase = db state.selectedDatabase = db
state.breadcrumbs = [db.name] state.breadcrumbs = [db.name]
state.models = models
state.views = views
return state return state
}), })
},
}, },
records: { records: {
delete: () => delete: () =>
@ -51,6 +46,14 @@ export const getBackendUiStore = () => {
return state return state
}), }),
}, },
models: {
create: model =>
store.update(state => {
state.models.push(model)
state.models = state.models
return state
}),
},
views: { views: {
select: view => select: view =>
store.update(state => { store.update(state => {
@ -72,281 +75,9 @@ export const getBackendUiStore = () => {
} }
// Store Actions // Store Actions
export const createShadowHierarchy = hierarchy =>
constructHierarchy(JSON.parse(JSON.stringify(hierarchy)))
export const createDatabaseForApp = store => appInstance => { export const createDatabaseForApp = store => appInstance => {
store.update(state => { store.update(state => {
state.appInstances.push(appInstance) state.appInstances.push(appInstance)
return state return state
}) })
} }
export const saveBackend = async state => {
await api.post(`/_builder/api/${state.appname}/backend`, {
appDefinition: {
hierarchy: state.hierarchy,
actions: state.actions,
triggers: state.triggers,
},
accessLevels: state.accessLevels,
})
const instances_currentFirst = state.selectedDatabase
? [
state.appInstances.find(i => i.id === state.selectedDatabase.id),
...state.appInstances.filter(i => i.id !== state.selectedDatabase.id),
]
: state.appInstances
for (let instance of instances_currentFirst) {
await api.post(
`/_builder/instance/${state.appname}/${instance.id}/api/upgradeData`,
{ newHierarchy: state.hierarchy, accessLevels: state.accessLevels }
)
}
}
export const newModel = (store, useRoot) => () => {
store.update(state => {
state.currentNodeIsNew = true
const shadowHierarchy = createShadowHierarchy(state.hierarchy)
const parent = useRoot
? shadowHierarchy
: getNode(shadowHierarchy, state.currentNode.nodeId)
state.errors = []
state.currentNode = templateApi(shadowHierarchy).getNewModelTemplate(
parent,
"",
true
)
return state
})
}
export const selectExistingNode = store => nodeId => {
store.update(state => {
state.currentNode = getNode(state.hierarchy, nodeId)
state.currentNodeIsNew = false
state.errors = []
return state
})
}
export const newIndex = (store, useRoot) => () => {
store.update(state => {
state.shadowHierarchy = createShadowHierarchy(state.hierarchy)
state.currentNodeIsNew = true
state.errors = []
const parent = useRoot
? state.shadowHierarchy
: getNode(state.shadowHierarchy, state.currentNode.nodeId)
state.currentNode = templateApi(state.shadowHierarchy).getNewIndexTemplate(
parent
)
return state
})
}
export const saveCurrentNode = store => () => {
store.update(state => {
const errors = validate.node(state.currentNode)
state.errors = errors
if (errors.length > 0) {
return state
}
const parentNode = getNode(
state.hierarchy,
state.currentNode.parent().nodeId
)
const existingNode = getNode(state.hierarchy, state.currentNode.nodeId)
let index = parentNode.children.length
if (existingNode) {
// remove existing
index = existingNode.parent().children.indexOf(existingNode)
if (isIndex(existingNode)) {
parentNode.indexes = parentNode.indexes.filter(
node => node.nodeId !== existingNode.nodeId
)
} else {
parentNode.children = parentNode.children.filter(
node => node.nodeId !== existingNode.nodeId
)
}
}
// should add node into existing hierarchy
const cloned = cloneDeep(state.currentNode)
templateApi(state.hierarchy).constructNode(parentNode, cloned)
if (isIndex(existingNode)) {
parentNode.children = sortBy("name", parentNode.children)
} else {
parentNode.indexes = sortBy("name", parentNode.indexes)
}
if (!existingNode && state.currentNode.type === "record") {
const defaultIndex = templateApi(state.hierarchy).getNewIndexTemplate(
cloned.parent()
)
defaultIndex.name = hierarchyFunctions.isTopLevelIndex(cloned)
? `all_${cloned.name}s`
: `${cloned.parent().name}_${cloned.name}s`
defaultIndex.allowedModelNodeIds = [cloned.nodeId]
}
state.currentNodeIsNew = false
saveBackend(state)
return state
})
}
export const deleteCurrentNode = store => () => {
store.update(state => {
const nodeToDelete = getNode(state.hierarchy, state.currentNode.nodeId)
state.currentNode = hierarchyFunctions.isRoot(nodeToDelete.parent())
? state.hierarchy.children.find(node => node !== state.currentNode)
: nodeToDelete.parent()
const isModel = hierarchyFunctions.isModel(nodeToDelete)
const check = isModel
? canDeleteModel(nodeToDelete)
: canDeleteIndex(nodeToDelete)
if (!check.canDelete) {
state.errors = check.errors.map(e => ({ error: e }))
return state
}
const recordOrIndexKey = isModel ? "children" : "indexes"
// remove the selected record or index
const newCollection = remove(
node => node.nodeId === nodeToDelete.nodeId,
nodeToDelete.parent()[recordOrIndexKey]
)
nodeToDelete.parent()[recordOrIndexKey] = newCollection
state.errors = []
saveBackend(state)
return state
})
}
export const saveField = store => field => {
store.update(state => {
state.currentNode.fields = state.currentNode.fields.filter(
f => f.id !== field.id
)
templateApi(state.hierarchy).addField(state.currentNode, field)
return state
})
}
export const deleteField = store => field => {
store.update(state => {
state.currentNode.fields = state.currentNode.fields.filter(
f => f.name !== field.name
)
return state
})
}
const incrementAccessLevelsVersion = state => {
state.accessLevels.version = state.accessLevels.version
? state.accessLevels.version + 1
: 1
return state
}
export const saveLevel = store => (newLevel, isNew, oldLevel = null) => {
store.update(state => {
const levels = state.accessLevels.levels
const existingLevel = isNew
? null
: find(a => a.name === oldLevel.name)(levels)
if (existingLevel) {
state.accessLevels.levels = levels.map(level =>
level === existingLevel ? newLevel : level
)
} else {
state.accessLevels.levels.push(newLevel)
}
incrementAccessLevelsVersion(state)
saveBackend(state)
return state
})
}
export const deleteLevel = store => level => {
store.update(state => {
state.accessLevels.levels = state.accessLevels.levels.filter(
t => t.name !== level.name
)
incrementAccessLevelsVersion(state)
saveBackend(state)
return state
})
}
export const saveAction = store => (newAction, isNew, oldAction = null) => {
store.update(s => {
const existingAction = isNew
? null
: find(a => a.name === oldAction.name)(s.actions)
if (existingAction) {
s.actions = s.actions.map(action =>
action === existingAction ? newAction : action
)
} else {
s.actions.push(newAction)
}
saveBackend(s)
return s
})
}
export const deleteAction = store => action => {
store.update(state => {
state.actions = state.actions.filter(a => a.name !== action.name)
saveBackend(state)
return state
})
}
export const saveTrigger = store => (newTrigger, isNew, oldTrigger = null) => {
store.update(s => {
const existingTrigger = isNew
? null
: s.triggers.find(a => a.name === oldTrigger.name)
if (existingTrigger) {
s.triggers = s.triggers.map(a => (a === existingTrigger ? newTrigger : a))
} else {
s.triggers.push(newTrigger)
}
saveBackend(s)
return s
})
}
export const deleteTrigger = store => trigger => {
store.update(s => {
s.triggers = s.triggers.filter(t => t.name !== trigger.name)
return s
})
}

View File

@ -1,20 +1,20 @@
// import { cloneDeep, values } from "lodash/fp"
import { filter, cloneDeep, last, concat, isEmpty, values } from "lodash/fp" import { backendUiStore } from "builderStore";
import { pipe, getNode, constructHierarchy } from "components/common/core"
import * as backendStoreActions from "./backend" import * as backendStoreActions from "./backend"
import { writable, get } from "svelte/store" import { writable, get } from "svelte/store"
import { defaultPagesObject } from "components/userInterface/pagesParsing/defaultPagesObject"
import api from "../api" import api from "../api"
import { DEFAULT_PAGES_OBJECT } from "../../constants"
import { getExactComponent } from "components/userInterface/pagesParsing/searchComponents" import { getExactComponent } from "components/userInterface/pagesParsing/searchComponents"
import { rename } from "components/userInterface/pagesParsing/renameScreen" import { rename } from "components/userInterface/pagesParsing/renameScreen"
import { import {
getNewScreen,
createProps, createProps,
makePropsSafe, makePropsSafe,
getBuiltin, getBuiltin,
} from "components/userInterface/pagesParsing/createProps" } from "components/userInterface/pagesParsing/createProps"
import { expandComponentDefinition } from "components/userInterface/pagesParsing/types" import {
import { loadLibs, libUrlsForPreview } from "../loadComponentLibraries" fetchComponentLibModules,
fetchComponentLibDefinitions,
} from "../loadComponentLibraries"
import { buildCodeForScreens } from "../buildCodeForScreens" import { buildCodeForScreens } from "../buildCodeForScreens"
import { generate_screen_css } from "../generate_css" import { generate_screen_css } from "../generate_css"
import { insertCodeMetadata } from "../insertCodeMetadata" import { insertCodeMetadata } from "../insertCodeMetadata"
@ -24,10 +24,7 @@ export const getStore = () => {
const initial = { const initial = {
apps: [], apps: [],
appname: "", appname: "",
hierarchy: {}, pages: DEFAULT_PAGES_OBJECT,
actions: [],
triggers: [],
pages: defaultPagesObject(),
mainUi: {}, mainUi: {},
unauthenticatedUi: {}, unauthenticatedUi: {},
components: [], components: [],
@ -36,52 +33,27 @@ export const getStore = () => {
currentFrontEndType: "none", currentFrontEndType: "none",
currentPageName: "", currentPageName: "",
currentComponentProps: null, currentComponentProps: null,
currentNodeIsNew: false,
errors: [], errors: [],
hasAppPackage: false, hasAppPackage: false,
accessLevels: { version: 0, levels: [] },
currentNode: null,
libraries: null, libraries: null,
showSettings: false, appId: "",
useAnalytics: true
} }
const store = writable(initial) const store = writable(initial)
store.setPackage = setPackage(store, initial) store.setPackage = setPackage(store, initial)
store.newChildModel = backendStoreActions.newModel(store, false)
store.newRootModel = backendStoreActions.newModel(store, true)
store.selectExistingNode = backendStoreActions.selectExistingNode(store)
store.newChildIndex = backendStoreActions.newIndex(store, false)
store.newRootIndex = backendStoreActions.newIndex(store, true)
store.saveCurrentNode = backendStoreActions.saveCurrentNode(store)
store.deleteCurrentNode = backendStoreActions.deleteCurrentNode(store)
store.saveField = backendStoreActions.saveField(store)
store.deleteField = backendStoreActions.deleteField(store)
store.saveLevel = backendStoreActions.saveLevel(store)
store.deleteLevel = backendStoreActions.deleteLevel(store)
store.createDatabaseForApp = backendStoreActions.createDatabaseForApp(store) store.createDatabaseForApp = backendStoreActions.createDatabaseForApp(store)
store.saveAction = backendStoreActions.saveAction(store)
store.deleteAction = backendStoreActions.deleteAction(store)
store.saveTrigger = backendStoreActions.saveTrigger(store)
store.deleteTrigger = backendStoreActions.deleteTrigger(store)
store.importAppDefinition = importAppDefinition(store)
store.saveScreen = saveScreen(store) store.saveScreen = saveScreen(store)
store.addComponentLibrary = addComponentLibrary(store)
store.renameScreen = renameScreen(store) store.renameScreen = renameScreen(store)
store.deleteScreen = deleteScreen(store) store.deleteScreen = deleteScreen(store)
store.setCurrentScreen = setCurrentScreen(store) store.setCurrentScreen = setCurrentScreen(store)
store.setCurrentPage = setCurrentPage(store) store.setCurrentPage = setCurrentPage(store)
store.createScreen = createScreen(store) store.createScreen = createScreen(store)
store.removeComponentLibrary = removeComponentLibrary(store)
store.addStylesheet = addStylesheet(store) store.addStylesheet = addStylesheet(store)
store.removeStylesheet = removeStylesheet(store) store.removeStylesheet = removeStylesheet(store)
store.savePage = savePage(store) store.savePage = savePage(store)
store.showSettings = showSettings(store)
store.useAnalytics = useAnalytics(store)
store.createGeneratedComponents = createGeneratedComponents(store)
store.addChildComponent = addChildComponent(store) store.addChildComponent = addChildComponent(store)
store.selectComponent = selectComponent(store) store.selectComponent = selectComponent(store)
store.setComponentProp = setComponentProp(store) store.setComponentProp = setComponentProp(store)
@ -100,12 +72,13 @@ export const getStore = () => {
export default getStore export default getStore
const setPackage = (store, initial) => async (pkg) => { const setPackage = (store, initial) => async pkg => {
const [main_screens, unauth_screens] = await Promise.all([ const [main_screens, unauth_screens] = await Promise.all([
api.get(`/_builder/api/${pkg.application.name}/pages/main/screens`).then(r => r.json()),
api api
.get(`/_builder/api/${pkg.application.name}/pages/unauthenticated/screens`) .get(`/_builder/api/${pkg.application._id}/pages/main/screens`)
.then(r => r.json()),
api
.get(`/_builder/api/${pkg.application._id}/pages/unauthenticated/screens`)
.then(r => r.json()), .then(r => r.json()),
]) ])
@ -120,73 +93,28 @@ const setPackage = (store, initial) => async (pkg) => {
}, },
} }
initial.libraries = await loadLibs(pkg.application.name, pkg) initial.libraries = await fetchComponentLibModules(pkg.application)
initial.loadLibraryUrls = pageName => { initial.components = await fetchComponentLibDefinitions(
const libs = libUrlsForPreview(pkg, pageName) pkg.clientId,
return libs pkg.application._id
} )
initial.appname = pkg.application.name initial.appname = pkg.application.name
initial.appId = pkg.application._id
initial.pages = pkg.pages initial.pages = pkg.pages
initial.hasAppPackage = true initial.hasAppPackage = true
initial.hierarchy = pkg.appDefinition.hierarchy
initial.accessLevels = pkg.accessLevels
initial.screens = values(pkg.screens) initial.screens = values(pkg.screens)
initial.components = values(pkg.components.components).map(
expandComponentDefinition
)
initial.templates = pkg.components.templates
initial.builtins = [getBuiltin("##builtin/screenslot")] initial.builtins = [getBuiltin("##builtin/screenslot")]
initial.actions = values(pkg.appDefinition.actions)
initial.triggers = pkg.appDefinition.triggers
initial.appInstances = pkg.application.instances initial.appInstances = pkg.application.instances
initial.appId = pkg.application.id initial.appId = pkg.application._id
if (!!initial.hierarchy && !isEmpty(initial.hierarchy)) {
initial.hierarchy = constructHierarchy(initial.hierarchy)
const shadowHierarchy = createShadowHierarchy(initial.hierarchy)
if (initial.currentNode !== null) {
initial.currentNode = getNode(shadowHierarchy, initial.currentNode.nodeId)
}
}
store.set(initial) store.set(initial)
console.log(initial)
return initial return initial
} }
const showSettings = store => () => {
store.update(state => {
state.showSettings = !state.showSettings
return state
})
}
const useAnalytics = store => () => {
store.update(state => {
state.useAnalytics = !state.useAnalytics
return state
})
}
const importAppDefinition = store => appDefinition => {
store.update(s => {
s.hierarchy = appDefinition.hierarchy
s.currentNode =
appDefinition.hierarchy.children.length > 0
? appDefinition.hierarchy.children[0]
: null
s.actions = appDefinition.actions
s.triggers = appDefinition.triggers
s.currentNodeIsNew = false
return s
})
}
const createShadowHierarchy = hierarchy =>
constructHierarchy(JSON.parse(JSON.stringify(hierarchy)))
const saveScreen = store => screen => { const saveScreen = store => screen => {
store.update(s => { store.update(state => {
return _saveScreen(store, s, screen) return _saveScreen(store, state, screen)
}) })
} }
@ -194,10 +122,7 @@ const _saveScreen = async (store, s, screen) => {
const currentPageScreens = s.pages[s.currentPageName]._screens const currentPageScreens = s.pages[s.currentPageName]._screens
await api await api
.post( .post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen)
`/_builder/api/${s.appname}/pages/${s.currentPageName}/screen`,
screen
)
.then(() => { .then(() => {
if (currentPageScreens.includes(screen)) return if (currentPageScreens.includes(screen)) return
@ -208,10 +133,7 @@ const _saveScreen = async (store, s, screen) => {
innerState.screens = screens innerState.screens = screens
innerState.currentPreviewItem = screen innerState.currentPreviewItem = screen
const safeProps = makePropsSafe( const safeProps = makePropsSafe(
getComponentDefinition( innerState.components[screen.props._component],
innerState.components,
screen.props._component
),
screen.props screen.props
) )
innerState.currentComponentInfo = safeProps innerState.currentComponentInfo = safeProps
@ -227,28 +149,30 @@ const _saveScreen = async (store, s, screen) => {
const _saveScreenApi = (screen, s) => const _saveScreenApi = (screen, s) =>
api api
.post( .post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen)
`/_builder/api/${s.appname}/pages/${s.currentPageName}/screen`,
screen
)
.then(() => _savePage(s)) .then(() => _savePage(s))
const createScreen = store => (screenName, route, layoutComponentName) => { const createScreen = store => (screenName, route, layoutComponentName) => {
store.update(s => { store.update(state => {
const newScreen = getNewScreen( const rootComponent = state.components[layoutComponentName]
s.components,
layoutComponentName, const newScreen = {
screenName name: screenName || "",
) description: "",
url: "",
_css: "",
uiFunctions: "",
props: createProps(rootComponent).props,
}
newScreen.route = route newScreen.route = route
s.currentPreviewItem = newScreen state.currentPreviewItem = newScreen
s.currentComponentInfo = newScreen.props state.currentComponentInfo = newScreen.props
s.currentFrontEndType = "screen" state.currentFrontEndType = "screen"
_saveScreen(store, s, newScreen) _saveScreen(store, state, newScreen)
return s return state
}) })
} }
@ -261,7 +185,7 @@ const setCurrentScreen = store => screenName => {
s.currentView = "detail" s.currentView = "detail"
const safeProps = makePropsSafe( const safeProps = makePropsSafe(
getComponentDefinition(s.components, screen.props._component), s.components[screen.props._component],
screen.props screen.props
) )
screen.props = safeProps screen.props = safeProps
@ -271,30 +195,10 @@ const setCurrentScreen = store => screenName => {
}) })
} }
const createGeneratedComponents = store => components => {
store.update(s => {
s.components = [...s.components, ...components]
s.screens = [...s.screens, ...components]
const doCreate = async () => {
for (let c of components) {
await api.post(`/_builder/api/${s.appname}/screen`, c)
}
await _savePage(s)
}
doCreate()
return s
})
}
const deleteScreen = store => name => { const deleteScreen = store => name => {
store.update(s => { store.update(s => {
const components = pipe(s.components, [filter(c => c.name !== name)]) const components = s.components.filter(c => c.name !== name)
const screens = s.screens.filter(c => c.name !== name);
const screens = pipe(s.screens, [filter(c => c.name !== name)])
s.components = components s.components = components
s.screens = screens s.screens = screens
@ -303,7 +207,7 @@ const deleteScreen = store => name => {
s.currentFrontEndType = "" s.currentFrontEndType = ""
} }
api.delete(`/_builder/api/${s.appname}/screen/${name}`) api.delete(`/_builder/api/${s.appId}/screen/${name}`)
return s return s
}) })
@ -331,12 +235,12 @@ const renameScreen = store => (oldname, newname) => {
const saveAllChanged = async () => { const saveAllChanged = async () => {
for (let screenName of changedScreens) { for (let screenName of changedScreens) {
const changedScreen = getExactComponent(screens, screenName) const changedScreen = getExactComponent(screens, screenName)
await api.post(`/_builder/api/${s.appname}/screen`, changedScreen) await api.post(`/_builder/api/${s.appId}/screen`, changedScreen)
} }
} }
api api
.patch(`/_builder/api/${s.appname}/screen`, { .patch(`/_builder/api/${s.appId}/screen`, {
oldname, oldname,
newname, newname,
}) })
@ -350,56 +254,14 @@ const renameScreen = store => (oldname, newname) => {
} }
const savePage = store => async page => { const savePage = store => async page => {
store.update(s => { store.update(state => {
if (s.currentFrontEndType !== "page" || !s.currentPageName) { if (s.currentFrontEndType !== "page" || !s.currentPageName) {
return s return state
} }
s.pages[s.currentPageName] = page s.pages[s.currentPageName] = page
_savePage(s) _savePage(s)
return s return state
})
}
const addComponentLibrary = store => async lib => {
const response = await api.get(
`/_builder/api/${s.appname}/componentlibrary?lib=${encodeURI(lib)}`,
undefined,
false
)
const success = response.status === 200
const components = success ? await response.json() : []
store.update(s => {
if (success) {
const componentsArray = []
for (let c in components) {
componentsArray.push(expandComponentDefinition(components[c]))
}
s.components = pipe(s.components, [
filter(c => !c.name.startsWith(`${lib}/`)),
concat(componentsArray),
])
s.pages.componentLibraries.push(lib)
_savePage(s)
}
return s
})
}
const removeComponentLibrary = store => lib => {
store.update(s => {
s.pages.componentLibraries = filter(l => l !== lib)(
s.pages.componentLibraries
)
_savePage(s)
return s
}) })
} }
@ -412,17 +274,17 @@ const addStylesheet = store => stylesheet => {
} }
const removeStylesheet = store => stylesheet => { const removeStylesheet = store => stylesheet => {
store.update(s => { store.update(state => {
s.pages.stylesheets = filter(s => s !== stylesheet)(s.pages.stylesheets) state.pages.stylesheets = s.pages.stylesheets.filter(s => s !== stylesheet)
_savePage(s) _savePage(state)
return s return state
}) })
} }
const _savePage = async s => { const _savePage = async s => {
const page = s.pages[s.currentPageName] const page = s.pages[s.currentPageName]
await api.post(`/_builder/api/${s.appname}/pages/${s.currentPageName}`, { await api.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}`, {
page: { componentLibraries: s.pages.componentLibraries, ...page }, page: { componentLibraries: s.pages.componentLibraries, ...page },
uiFunctions: s.currentPageFunctions, uiFunctions: s.currentPageFunctions,
screens: page._screens, screens: page._screens,
@ -430,36 +292,37 @@ const _savePage = async s => {
} }
const setCurrentPage = store => pageName => { const setCurrentPage = store => pageName => {
store.update(s => { store.update(state => {
const current_screens = s.pages[pageName]._screens const current_screens = state.pages[pageName]._screens
s.currentFrontEndType = "page" const currentPage = state.pages[pageName]
s.currentPageName = pageName
s.screens = Array.isArray(current_screens) state.currentFrontEndType = "page"
state.currentPageName = pageName
state.screens = Array.isArray(current_screens)
? current_screens ? current_screens
: Object.values(current_screens) : Object.values(current_screens)
const safeProps = makePropsSafe( const safeProps = makePropsSafe(
getComponentDefinition(s.components, s.pages[pageName].props._component), state.components[currentPage.props._component],
s.pages[pageName].props currentPage.props
) )
s.currentComponentInfo = safeProps state.currentComponentInfo = safeProps
s.pages[pageName].props = safeProps currentPage.props = safeProps
s.currentPreviewItem = s.pages[pageName] state.currentPreviewItem = state.pages[pageName]
s.currentPreviewItem._css = generate_screen_css([ state.currentPreviewItem._css = generate_screen_css([
s.currentPreviewItem.props, state.currentPreviewItem.props,
]) ])
for (let screen of s.screens) { for (let screen of state.screens) {
screen._css = generate_screen_css([screen.props]) screen._css = generate_screen_css([screen.props])
} }
setCurrentPageFunctions(s) setCurrentPageFunctions(state)
return s return state
}) })
} }
const getComponentDefinition = (components, name) => // const getComponentDefinition = (components, name) => components.find(c => c.name === name)
components.find(c => c.name === name)
/** /**
* @param {string} componentToAdd - name of the component to add to the application * @param {string} componentToAdd - name of the component to add to the application
@ -469,8 +332,10 @@ const addChildComponent = store => (componentToAdd, presetName) => {
store.update(state => { store.update(state => {
function findSlot(component_array) { function findSlot(component_array) {
for (let i = 0; i < component_array.length; i += 1) { for (let i = 0; i < component_array.length; i += 1) {
if (component_array[i]._component === "##builtin/screenslot") if (component_array[i]._component === "##builtin/screenslot") {
return true return true
}
if (component_array[i]._children) findSlot(component_array[i]) if (component_array[i]._children) findSlot(component_array[i])
} }
@ -486,10 +351,16 @@ const addChildComponent = store => (componentToAdd, presetName) => {
const component = componentToAdd.startsWith("##") const component = componentToAdd.startsWith("##")
? getBuiltin(componentToAdd) ? getBuiltin(componentToAdd)
: state.components.find(({ name }) => name === componentToAdd) : state.components[componentToAdd]
const presetProps = presetName ? component.presets[presetName] : {} const presetProps = presetName ? component.presets[presetName] : {}
const newComponent = createProps(component, presetProps)
const instanceId = get(backendUiStore).selectedDatabase.id;
const newComponent = createProps(component, {
...presetProps,
instanceId
}, state)
state.currentComponentInfo._children = state.currentComponentInfo._children.concat( state.currentComponentInfo._children = state.currentComponentInfo._children.concat(
newComponent.props newComponent.props
@ -532,7 +403,7 @@ const selectComponent = store => component => {
store.update(state => { store.update(state => {
const componentDef = component._component.startsWith("##") const componentDef = component._component.startsWith("##")
? component ? component
: state.components.find(c => c.name === component._component) : state.components[component._component]
state.currentComponentInfo = makePropsSafe(componentDef, component) state.currentComponentInfo = makePropsSafe(componentDef, component)
state.currentView = "component" state.currentView = "component"
return state return state
@ -552,28 +423,28 @@ const setComponentProp = store => (name, value) => {
} }
const setComponentStyle = store => (type, name, value) => { const setComponentStyle = store => (type, name, value) => {
store.update(s => { store.update(state => {
if (!s.currentComponentInfo._styles) { if (!state.currentComponentInfo._styles) {
s.currentComponentInfo._styles = {} state.currentComponentInfo._styles = {}
} }
s.currentComponentInfo._styles[type][name] = value state.currentComponentInfo._styles[type][name] = value
s.currentPreviewItem._css = generate_screen_css([ state.currentPreviewItem._css = generate_screen_css([
s.currentPreviewItem.props, state.currentPreviewItem.props,
]) ])
// save without messing with the store // save without messing with the store
_saveCurrentPreviewItem(s) _saveCurrentPreviewItem(state)
return s return state
}) })
} }
const setComponentCode = store => code => { const setComponentCode = store => code => {
store.update(s => { store.update(state => {
s.currentComponentInfo._code = code state.currentComponentInfo._code = code
setCurrentPageFunctions(s) setCurrentPageFunctions(state)
// save without messing with the store // save without messing with the store
_saveScreenApi(s.currentPreviewItem, s) _saveScreenApi(state.currentPreviewItem, state)
return s return s
}) })
@ -587,31 +458,33 @@ const setCurrentPageFunctions = s => {
const buildPageCode = (screens, page) => buildCodeForScreens([page, ...screens]) const buildPageCode = (screens, page) => buildCodeForScreens([page, ...screens])
const setScreenType = store => type => { const setScreenType = store => type => {
store.update(s => { store.update(state => {
s.currentFrontEndType = type state.currentFrontEndType = type
const pageOrScreen = const pageOrScreen =
type === "page" type === "page"
? s.pages[s.currentPageName] ? state.pages[state.currentPageName]
: s.pages[s.currentPageName]._screens[0] : state.pages[state.currentPageName]._screens[0]
s.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null state.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null
s.currentPreviewItem = pageOrScreen state.currentPreviewItem = pageOrScreen
return s return state
}) })
} }
const deleteComponent = store => component => { const deleteComponent = store => componentName => {
store.update(s => { store.update(state => {
const parent = getParent(s.currentPreviewItem.props, component) const parent = getParent(state.currentPreviewItem.props, componentName)
if (parent) { if (parent) {
parent._children = parent._children.filter(c => c !== component) parent._children = parent._children.filter(
component => component !== componentName
)
} }
_saveCurrentPreviewItem(s) _saveCurrentPreviewItem(state)
return s return state
}) })
} }
@ -669,11 +542,10 @@ const copyComponent = store => component => {
} }
const getPathToComponent = store => component => { const getPathToComponent = store => component => {
// Gets all the components to needed to construct a path. // Gets all the components to needed to construct a path.
const tempStore = get(store) const tempStore = get(store)
let pathComponents = [] let pathComponents = []
let parent = component; let parent = component
let root = false let root = false
while (!root) { while (!root) {
parent = getParent(tempStore.currentPreviewItem.props, parent) parent = getParent(tempStore.currentPreviewItem.props, parent)
@ -695,7 +567,7 @@ const getPathToComponent = store => component => {
const IdList = allComponents.map(c => c._id) const IdList = allComponents.map(c => c._id)
// Construct ID Path: // Construct ID Path:
const path = IdList.join('/') const path = IdList.join("/")
return path return path
} }

View File

@ -1,123 +0,0 @@
<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 ActionButton from "../common/ActionButton.svelte"
import { validateAccessLevels, nodeNameFromNodeKey } from "../common/core"
import ErrorsBox from "../common/ErrorsBox.svelte"
export let level
export let allPermissions
export let onFinished
export let isNew
export let allLevels
export let hierarchy
export let actions
export let close
export let title
let errors = []
let clonedLevel = cloneDeep(level)
const matchPermissions = (p1, p2) =>
p1.type === p2.type &&
((!p2.nodeKey && !p1.nodeKey) || p2.nodeKey === p1.nodeKey)
const hasPermission = hasPerm =>
clonedLevel.permissions.some(permission =>
matchPermissions(permission, hasPerm)
)
$: permissionMatrix = allPermissions.map(permission => ({
permission,
hasPermission: hasPermission(permission),
}))
$: allPermissionsSelected = permissionMatrix.every(
permission => permission.hasPermission
)
const getPermissionName = perm =>
perm.nodeKey
? `${perm.type} - ${nodeNameFromNodeKey(hierarchy, perm.nodeKey)}`
: perm.type
const save = () => {
const newLevels = isNew
? [...allLevels, clonedLevel]
: [...allLevels.filter(l => l.name !== level.name), clonedLevel]
errors = validateAccessLevels(hierarchy, actions, newLevels)
if (errors.length > 0) return
onFinished(clonedLevel)
close()
}
const permissionChanged = perm => ev => {
const hasPermission = ev.target.checked
if (hasPermission) {
clonedLevel.permissions.push(perm)
} else {
clonedLevel.permissions = filter(p => !matchPermissions(p, perm))(
clonedLevel.permissions
)
}
allPermissions = allPermissions
}
</script>
<div>
<div class="uk-modal-header">
<h4 class="budibase__title--4">{title}</h4>
</div>
<ErrorsBox {errors} />
<form on:submit|preventDefault class="uk-form-horizontal">
<Textbox label="Access Level Name" bind:text={clonedLevel.name} />
<h4 class="budibase__title--4">Permissions</h4>
<Checkbox
label={'Select All'}
checked={allPermissionsSelected}
on:change={ev => {
permissionMatrix.forEach(permission =>
permissionChanged(permission.permission)(ev)
)
}} />
{#each permissionMatrix as permission}
<div class="permission-container">
<Checkbox
label={getPermissionName(permission.permission)}
checked={permission.hasPermission}
on:change={permissionChanged(permission.permission)} />
</div>
{/each}
</form>
<div class="uk-modal-footer uk-text-right">
<ButtonGroup>
<ActionButton primary grouped on:click={save}>Save</ActionButton>
<ActionButton alert grouped on:click={() => onFinished()}>
Cancel
</ActionButton>
</ButtonGroup>
</div>
</div>
<style>
.permission-container {
margin-top: 10px;
margin-bottom: 10px;
}
</style>

View File

@ -1,133 +0,0 @@
<script>
import Textbox from "components/common/Textbox.svelte"
import Button from "components/common/Button.svelte"
import ActionButton from "components/common/ActionButton.svelte"
import ButtonGroup from "components/common/ButtonGroup.svelte"
import { cloneDeep, filter, keys, map, isUndefined } from "lodash/fp"
import ErrorsBox from "components/common/ErrorsBox.svelte"
import { validateActions, pipe } from "components/common/core"
import getIcon from "components/common/icon"
export let action
export let onFinished = action => {}
export let allActions
let optKey = ""
let optValue = ""
let clonedAction = cloneDeep(action)
let initialOptions = pipe(action.initialOptions, [
keys,
map(k => ({ key: k, value: action.initialOptions[k] })),
])
let errors = []
const addNewOption = () => {
if (
optKey &&
optValue &&
isUndefined(clonedAction.initialOptions[optKey])
) {
clonedAction.initialOptions[optKey] = optValue
initialOptions = [
...initialOptions,
{
key: optKey,
value: optValue,
},
]
optKey = ""
optValue = ""
}
}
const removeOption = opt => {
if (opt) {
delete clonedAction.initialOptions[opt.key]
initialOptions = pipe(initialOptions, [filter(o => o.key !== opt.key)])
}
}
const save = () => {
const newActionsList = [
...pipe(allActions, [filter(a => a !== action)]),
clonedAction,
]
errors = pipe(newActionsList, [validateActions, map(e => e.error)])
if (errors.length === 0) onFinished(clonedAction)
}
const cancel = () => {
onFinished()
}
</script>
<div class="root">
<ErrorsBox {errors} />
<form on:submit|preventDefault class="uk-form-horizontal">
<Textbox label="Name" bind:text={clonedAction.name} />
<Textbox
label="Behaviour Source"
bind:text={clonedAction.behaviourSource} />
<Textbox label="Behaviour" bind:text={clonedAction.behaviourName} />
</form>
<div class=" uk-form-stacked" style="margin-bottom: 20px">
<label class="uk-form-label">Default Options</label>
<div class="uk-grid-small" uk-grid>
<input
class="uk-input uk-width-1-4 uk-margin-right"
placeholder="key"
bind:value={optKey} />
<input
class="uk-input uk-width-1-4 uk-margin-right"
placeholder="value"
bind:value={optValue} />
<ActionButton primary on:click={addNewOption}>Add</ActionButton>
</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 class="uk-modal-footer uk-text-right">
<ButtonGroup>
<ActionButton primary grouped on:click={save}>Save</ActionButton>
<ActionButton alert grouped on:click={cancel}>Cancel</ActionButton>
</ButtonGroup>
</div>
</div>
<style>
.root {
padding: 2rem;
border-radius: 2rem;
}
.uk-grid-small {
padding: 1rem;
}
.option-container {
border-style: dotted;
border-width: 1px;
border-color: var(--primary75);
padding: 3px;
margin-right: 5px;
}
</style>

View File

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

View File

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

View File

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

View File

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

View File

@ -47,7 +47,7 @@
.button:hover { .button:hover {
cursor: pointer; cursor: pointer;
font-weight: 600; font-weight: 600;
filter:saturate(90%); filter: saturate(90%);
} }
.button:disabled { .button:disabled {

View File

@ -13,7 +13,7 @@
</div> </div>
`, `,
status, status,
timeout: 100000 timeout: 100000,
}) })
} }
</script> </script>

View File

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

View File

@ -7,7 +7,7 @@
{#if hasErrors} {#if hasErrors}
<div uk-alert class="uk-alert-danger"> <div uk-alert class="uk-alert-danger">
{#each errors as error} {#each errors as error}
<div>{error.field ? `${error.field}: ` : ''}{error.error}</div> <div>{error.dataPath} {error.message}</div>
{/each} {/each}
</div> </div>
{/if} {/if}

View File

@ -3,8 +3,6 @@
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="100%" width="100%"
height="100%"> height="100%">
<path fill="none" d="M0 0h24v24H0z"/> <path fill="none" d="M0 0h24v24H0z" />
<path <path fill="rgba(0,3,51,1)" d="M12 14l-4-4h8z" />
fill="rgba(0,3,51,1)"
d="M12 14l-4-4h8z" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 228 B

After

Width:  |  Height:  |  Size: 196 B

View File

@ -16,6 +16,10 @@
<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="budibase__input" {value} on:change={inputChanged} /> <input
class="budibase__input"
type="number"
{value}
on:change={inputChanged} />
</div> </div>
</div> </div>

View File

@ -1,132 +1,3 @@
import { import { flow } from "lodash/fp"
hierarchy as hierarchyFunctions,
common,
getTemplateApi,
getAuthApi,
} from "../../../../core/src"
import { _getNew } from "../../../../core/src/recordApi/getNew"
import { find, filter, keyBy, flatten, map } from "lodash/fp"
import { generateSchema } from "../../../../core/src/indexing/indexSchemaCreator"
import { generate } from "shortid"
export { canDeleteIndex } from "../../../../core/src/templateApi/canDeleteIndex" export const pipe = (arg, funcs) => flow(funcs)(arg)
export { canDeleteModel } from "../../../../core/src/templateApi/canDeleteModel"
export { userWithFullAccess } from "../../../../core/src/index"
export { joinKey } from "../../../../core/src/common"
export { getExactNodeForKey } from "../../../../core/src/templateApi/hierarchy"
export const pipe = common.$
export const events = common.eventsList
export const getNode = (hierarchy, nodeId) =>
pipe(hierarchy, [
hierarchyFunctions.getFlattenedHierarchy,
find(n => n.nodeId === nodeId || n.nodeKey() === nodeId),
])
export const constructHierarchy = node => {
if (!node) return node
return templateApi(node).constructHierarchy(node)
}
export const createNewHierarchy = () => {
return templateApi().getNewRootLevel()
}
export const templateApi = hierarchy => getTemplateApi({ hierarchy })
export const authApi = (hierarchy, actions) =>
getAuthApi({
hierarchy,
actions: keyBy("name")(actions),
publish: () => { },
})
export const allTypes = templateApi({}).allTypes
export const validate = {
all: templateApi({}).validateAll,
node: templateApi({}).validateNode,
field: templateApi({}).validateField,
}
export const getPotentialReverseReferenceIndexes = (hierarchy, refIndex) => {
const res = pipe(hierarchy, [
hierarchyFunctions.getFlattenedHierarchy,
filter(
n =>
hierarchyFunctions.isAncestor(refIndex)(n) ||
hierarchyFunctions.isAncestor(refIndex)(n.parent())
),
map(n => n.indexes),
flatten,
filter(hierarchyFunctions.isReferenceIndex),
])
return res
}
export const getPotentialReferenceIndexes = (hierarchy, model) =>
pipe(hierarchy, [
hierarchyFunctions.getFlattenedHierarchy,
filter(hierarchyFunctions.isAncestorIndex),
filter(
i =>
hierarchyFunctions.isAncestor(model)(i.parent()) ||
i.parent().nodeId === model.parent().nodeId ||
hierarchyFunctions.isRoot(i.parent())
),
])
export const isIndex = hierarchyFunctions.isIndex
export const isModel = hierarchyFunctions.isModel
export const nodeNameFromNodeKey = hierarchyFunctions.nodeNameFromNodeKey
export const getDefaultTypeOptions = type =>
!type ? {} : allTypes[type].getDefaultOptions()
export const getNewAction = () => templateApi({}).createAction()
export const getNewTrigger = () => templateApi({}).createTrigger()
export const validateActions = actions =>
templateApi({}).validateActions(actions)
export const validateTriggers = (triggers, actions) =>
templateApi({}).validateTriggers(triggers, actions)
export const generateFullPermissions = (hierarchy, actions) =>
authApi(hierarchy, actions).generateFullPermissions()
export const getNewAccessLevel = () => authApi().getNewAccessLevel()
export const validateAccessLevels = (hierarchy, actions, accessLevels) =>
authApi(hierarchy, actions).validateAccessLevels(accessLevels)
export const getIndexNodes = hierarchy =>
pipe(hierarchy, [
hierarchyFunctions.getFlattenedHierarchy,
filter(hierarchyFunctions.isIndex),
])
export const getRecordNodes = hierarchy =>
pipe(hierarchy, [
hierarchyFunctions.getFlattenedHierarchy,
filter(hierarchyFunctions.isModel),
])
export const getIndexSchema = hierarchy => index =>
generateSchema(hierarchy, index)
export const getNewRecord = _getNew
export const getNewInstance = (appId, name) => {
const id = `2-${generate()}`
return {
key: `/applications/${appId}/instances/${id}`,
active: true,
version: { key: "" },
isNew: true,
type: "instance",
datastoreconfig: "",
id,
name,
}
}

View File

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

View File

@ -1,156 +0,0 @@
<script>
import Dropdown from "../common/Dropdown.svelte"
import Textbox from "../common/Textbox.svelte"
import Button from "../common/Button.svelte"
import ButtonGroup from "../common/ButtonGroup.svelte"
import NumberBox from "../common/NumberBox.svelte"
import ValuesList from "../common/ValuesList.svelte"
import ErrorsBox from "../common/ErrorsBox.svelte"
import Checkbox from "../common/Checkbox.svelte"
import ActionButton from "../common/ActionButton.svelte"
import DatePicker from "../common/DatePicker.svelte"
import {
cloneDeep,
keys,
isNumber,
includes,
map,
isBoolean,
} from "lodash/fp"
import {
allTypes,
validate,
getPotentialReferenceIndexes,
getDefaultTypeOptions,
getNode,
getPotentialReverseReferenceIndexes,
} from "../common/core"
export let field
export let allFields
export let onFinished = () => {}
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)
$: possibleReverseReferenceIndexes = !selectedReverseRefIndex
? []
: getPotentialReverseReferenceIndexes(
store.hierarchy,
selectedReverseRefIndex
)
const typeChanged = ev =>
(clonedField.typeOptions = getDefaultTypeOptions(ev.detail))
const save = () => {
errors = validate.field(allFields)(clonedField)
if (errors.length > 0) return
field.typeOptions = cloneDeep(clonedField.typeOptions)
onFinished({ ...field, ...clonedField })
}
</script>
<div class="root">
<ErrorsBox {errors} />
<form on:submit|preventDefault class="uk-form-stacked">
<Textbox label="Name" bind:text={clonedField.name} />
<Dropdown
label="Type"
bind:selected={clonedField.type}
options={keys(allTypes)}
on:change={typeChanged} />
<Textbox label="Label" bind:text={clonedField.label} />
{#if clonedField.type === 'string'}
<NumberBox
label="Max Length"
bind:value={clonedField.typeOptions.maxLength} />
<ValuesList
label="Categories"
bind:values={clonedField.typeOptions.values} />
<Checkbox
label="Declared Values Only"
bind:checked={clonedField.typeOptions.allowDeclaredValuesOnly} />
{:else if clonedField.type === 'bool'}
<Checkbox
label="Allow Null"
bind:checked={clonedField.typeOptions.allowNulls} />
{:else if clonedField.type === 'datetime'}
<DatePicker
label="Min Value"
bind:value={clonedField.typeOptions.minValue} />
<DatePicker
label="Max Value"
bind:value={clonedField.typeOptions.maxValue} />
{:else if clonedField.type === 'number'}
<NumberBox
label="Min Value"
bind:value={clonedField.typeOptions.minValue} />
<NumberBox
label="Max Value"
bind:value={clonedField.typeOptions.maxValue} />
<NumberBox
label="Decimal Places"
bind:value={clonedField.typeOptions.decimalPlaces} />
{:else if clonedField.type === 'reference'}
<Dropdown
label="Lookup Index"
options={possibleReferenceIndexes}
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>
</div>
<footer>
<ActionButton primary on:click={save}>Save</ActionButton>
<ActionButton alert on:click={() => onFinished(false)}>Cancel</ActionButton>
</footer>
<style>
.root {
margin: 20px;
}
footer {
padding: 20px;
border-radius: 0 0 5px 5px;
bottom: 0;
left: 0;
background: #fafafa;
}
</style>

View File

@ -14,7 +14,6 @@
takeRight, takeRight,
} from "lodash/fp" } from "lodash/fp"
import Select from "components/common/Select.svelte" import Select from "components/common/Select.svelte"
import { getIndexSchema } from "components/common/core"
import ActionButton from "components/common/ActionButton.svelte" import ActionButton from "components/common/ActionButton.svelte"
import TablePagination from "./TablePagination.svelte" import TablePagination from "./TablePagination.svelte"
import { DeleteRecordModal, CreateEditRecordModal } from "./modals" import { DeleteRecordModal, CreateEditRecordModal } from "./modals"
@ -27,7 +26,7 @@
CreateEditRecordModal, CreateEditRecordModal,
{ {
onClosed: close, onClosed: close,
record: await selectRecord(row), record: row,
}, },
{ styleContent: { padding: "0" } } { styleContent: { padding: "0" } }
) )
@ -38,7 +37,7 @@
DeleteRecordModal, DeleteRecordModal,
{ {
onClosed: close, onClosed: close,
record: await selectRecord(row), record: row,
}, },
{ styleContent: { padding: "0" } } { styleContent: { padding: "0" } }
) )
@ -53,7 +52,7 @@
const ITEMS_PER_PAGE = 10 const ITEMS_PER_PAGE = 10
// Internal headers we want to hide from the user // Internal headers we want to hide from the user
const INTERNAL_HEADERS = ["key", "sortKey", "type", "id", "isNew"] const INTERNAL_HEADERS = ["_id", "_rev", "modelId", "type"]
let modalOpen = false let modalOpen = false
let data = [] let data = []
@ -61,60 +60,26 @@
let views = [] let views = []
let currentPage = 0 let currentPage = 0
$: views = $backendUiStore.selectedRecord $: instanceId = $backendUiStore.selectedDatabase.id
? childViewsForRecord($store.hierarchy)
: $store.hierarchy.indexes
$: currentAppInfo = { $: {
appname: $store.appname, if ($backendUiStore.selectedView) {
instanceId: $backendUiStore.selectedDatabase.id, api
} .fetchDataForView($backendUiStore.selectedView, instanceId)
.then(records => {
$: fetchRecordsForView(
$backendUiStore.selectedView,
$backendUiStore.selectedDatabase
).then(records => {
data = records || [] data = records || []
headers = hideInternalHeaders($backendUiStore.selectedView) headers = Object.keys($backendUiStore.selectedModel.schema).filter(
key => !INTERNAL_HEADERS.includes(key)
)
}) })
}
}
$: paginatedData = data.slice( $: paginatedData = data.slice(
currentPage * ITEMS_PER_PAGE, currentPage * ITEMS_PER_PAGE,
currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE
) )
const getSchema = getIndexSchema($store.hierarchy)
const childViewsForRecord = compose(flatten, map("indexes"), get("children"))
const hideInternalHeaders = compose(
remove(headerName => INTERNAL_HEADERS.includes(headerName)),
map(get("name")),
getSchema
)
async function fetchRecordsForView(view, instance) {
if (!view || !view.name) return
const viewName = $backendUiStore.selectedRecord
? `${$backendUiStore.selectedRecord.key}/${view.name}`
: view.name
return await api.fetchDataForView(viewName, {
appname: $store.appname,
instanceId: instance.id,
})
}
function drillIntoRecord(record) {
backendUiStore.update(state => {
state.breadcrumbs = [...state.breadcrumbs, record.type, record.id]
state.selectedRecord = record
state.selectedView = childViewsForRecord($store.hierarchy)[0]
return state
})
}
onMount(() => { onMount(() => {
if (views.length) { if (views.length) {
backendUiStore.actions.views.select(views[0]) backendUiStore.actions.views.select(views[0])
@ -124,14 +89,7 @@
<section> <section>
<div class="table-controls"> <div class="table-controls">
<h2 class="title"> <h2 class="title">{$backendUiStore.selectedModel.name}</h2>
{takeRight(2, $backendUiStore.breadcrumbs).join(' / ')}
</h2>
<Select icon="ri-eye-line" bind:value={$backendUiStore.selectedView}>
{#each views as view}
<option value={view}>{view.name}</option>
{/each}
</Select>
</div> </div>
<table class="uk-table"> <table class="uk-table">
<thead> <thead>
@ -153,9 +111,6 @@
<i class="ri-more-line" /> <i class="ri-more-line" />
<div uk-dropdown="mode: click"> <div uk-dropdown="mode: click">
<ul class="uk-nav uk-dropdown-nav"> <ul class="uk-nav uk-dropdown-nav">
<li>
<div on:click={() => drillIntoRecord(row)}>View</div>
</li>
<li <li
on:click={() => { on:click={() => {
editRecord(row) editRecord(row)
@ -196,10 +151,6 @@
text-transform: capitalize; text-transform: capitalize;
} }
.select {
background: white;
}
table { table {
border: 1px solid #ccc; border: 1px solid #ccc;
background: #fff; background: #fff;

View File

@ -1,59 +1,36 @@
import api from "builderStore/api" import api from "builderStore/api"
import { getNewRecord, getNewInstance } from "components/common/core"
export async function createUser(password, user, { appname, instanceId }) { export async function createUser(user, appId, instanceId) {
const CREATE_USER_URL = `/_builder/instance/${appname}/${instanceId}/api/createUser` const CREATE_USER_URL = `/api/${appId}/${instanceId}/users`
const response = await api.post(CREATE_USER_URL, { user, password }) const response = await api.post(CREATE_USER_URL, user)
const json = await response.json()
return json.user
}
export async function createDatabase(clientId, appname, instanceName) {
const CREATE_DATABASE_URL = `/api/${clientId}/${appname}/instances`
const response = await api.post(CREATE_DATABASE_URL, {
name: instanceName,
})
return await response.json() return await response.json()
} }
export async function createDatabase(appname, instanceName) { export async function deleteRecord(record, instanceId) {
const CREATE_DATABASE_URL = `/_builder/instance/_master/0/api/record/` const DELETE_RECORDS_URL = `/api/${instanceId}/records/${record._id}/${record._rev}`
const database = getNewInstance(appname, instanceName)
const response = await api.post(CREATE_DATABASE_URL, database)
return await response.json()
}
export async function deleteRecord(record, { appname, instanceId }) {
const DELETE_RECORDS_URL = `/_builder/instance/${appname}/${instanceId}/api/record${record.key}`
const response = await api.delete(DELETE_RECORDS_URL) const response = await api.delete(DELETE_RECORDS_URL)
return response return response
} }
export async function loadRecord(key, { appname, instanceId }) { export async function saveRecord(record, instanceId) {
const LOAD_RECORDS_URL = `/_builder/instance/${appname}/${instanceId}/api/record${key}` const SAVE_RECORDS_URL = `/api/${instanceId}/records`
const response = await api.get(LOAD_RECORDS_URL) const response = await api.post(SAVE_RECORDS_URL, record)
return await response.json() return await response.json()
} }
export async function saveRecord(record, { appname, instanceId }) { export async function fetchDataForView(viewName, instanceId) {
let recordBase = { ...record } const FETCH_RECORDS_URL = `/api/${instanceId}/${viewName}/records`
// brand new record
// car-model-id or name/specific-car-id/manus
if (record.collectionName) {
const collectionKey = `/${record.collectionName}`
recordBase = getNewRecord(recordBase, collectionKey)
recordBase = overwritePresentProperties(recordBase, record)
}
const SAVE_RECORDS_URL = `/_builder/instance/${appname}/${instanceId}/api/record/`
const response = await api.post(SAVE_RECORDS_URL, recordBase)
return await response.json()
}
export async function fetchDataForView(viewName, { appname, instanceId }) {
const FETCH_RECORDS_URL = `/_builder/instance/${appname}/${instanceId}/api/listRecords/${viewName}`
const response = await api.get(FETCH_RECORDS_URL) const response = await api.get(FETCH_RECORDS_URL)
return await response.json() return await response.json()
} }
function overwritePresentProperties(baseObj, overwrites) {
const base = { ...baseObj }
for (let key in base) {
if (overwrites[key]) base[key] = overwrites[key]
}
return base
}

View File

@ -9,8 +9,12 @@
let databaseName let databaseName
async function createDatabase() { async function createDatabase() {
const response = await api.createDatabase($store.appId, databaseName) const response = await api.createDatabase(
store.createDatabaseForApp(response) $store.clientId,
$store.appId,
databaseName
)
store.createDatabaseForApp(response.instance)
onClosed() onClosed()
} }
</script> </script>

View File

@ -1,231 +0,0 @@
<script>
import { tick } from "svelte"
import Textbox from "components/common/Textbox.svelte"
import Button from "components/common/Button.svelte"
import Select from "components/common/Select.svelte"
import ActionButton from "components/common/ActionButton.svelte"
import getIcon from "components/common/icon"
import FieldView from "../../FieldView.svelte"
import {
get,
compose,
map,
join,
filter,
some,
find,
keys,
isDate,
} from "lodash/fp"
import { store, backendUiStore } from "builderStore"
import { common, hierarchy } from "../../../../../../core/src/"
import { getNode } from "components/common/core"
import { templateApi, pipe, validate } from "components/common/core"
import ErrorsBox from "components/common/ErrorsBox.svelte"
let model
let editingField = false
let fieldToEdit
let isNewField = false
let newField
let editField
let deleteField
let onFinishedFieldEdit
let editIndex
$: parent = model && model.parent()
$: isChildModel = parent && parent.name !== "root"
$: modelExistsInHierarchy =
$store.currentNode && getNode($store.hierarchy, $store.currentNode.nodeId)
store.subscribe($store => {
model = $store.currentNode
const flattened = hierarchy.getFlattenedHierarchy($store.hierarchy)
newField = () => {
isNewField = true
fieldToEdit = templateApi($store.hierarchy).getNewField("string")
editingField = true
}
onFinishedFieldEdit = field => {
if (field) {
store.saveField(field)
}
editingField = false
}
editField = field => {
isNewField = false
fieldToEdit = field
editingField = true
}
deleteField = field => {
store.deleteField(field)
}
editIndex = index => {
store.selectExistingNode(index.nodeId)
}
})
let getTypeOptionsValueText = value => {
if (
value === Number.MAX_SAFE_INTEGER ||
value === Number.MIN_SAFE_INTEGER ||
new Date(value).getTime() === new Date(8640000000000000).getTime() ||
new Date(value).getTime() === new Date(-8640000000000000).getTime()
)
return "(any)"
if (value === null) return "(not set)"
return value
}
const nameChanged = ev => {
const pluralName = n => `${n}s`
if (model.collectionName === "") {
model.collectionName = pluralName(ev.target.value)
}
}
</script>
<heading>
{#if !editingField}
<i class="ri-list-settings-line button--toggled" />
<h3 class="budibase__title--3">Create / Edit Model</h3>
{:else}
<i class="ri-file-list-line button--toggled" />
<h3 class="budibase__title--3">Create / Edit Field</h3>
{/if}
</heading>
{#if !editingField}
<div class="padding">
<h4 class="budibase__label--big">Settings</h4>
{#if $store.errors && $store.errors.length > 0}
<ErrorsBox errors={$store.errors} />
{/if}
<form on:submit|preventDefault class="uk-form-stacked">
<Textbox label="Name" bind:text={model.name} on:change={nameChanged} />
{#if isChildModel}
<div>
<label class="uk-form-label">Parent</label>
<div class="uk-form-controls parent-name">{parent.name}</div>
</div>
{/if}
</form>
<div class="table-controls">
<span class="budibase__label--big">Fields</span>
<h4 class="hoverable new-field" on:click={newField}>Add new field</h4>
</div>
<table class="uk-table fields-table budibase__table">
<thead>
<tr>
<th>Edit</th>
<th>Name</th>
<th>Type</th>
<th>Values</th>
<th />
</tr>
</thead>
<tbody>
{#each model ? model.fields : [] as field}
<tr>
<td>
<i class="ri-more-line" on:click={() => editField(field)} />
</td>
<td>
<div>{field.name}</div>
</td>
<td>{field.type}</td>
<td>{field.typeOptions.values || ''}</td>
<td>
<i
class="ri-delete-bin-6-line hoverable"
on:click={() => deleteField(field)} />
</td>
</tr>
{/each}
</tbody>
</table>
<div class="uk-margin">
<ActionButton color="secondary" on:click={store.saveCurrentNode}>
Save
</ActionButton>
{#if modelExistsInHierarchy}
<ActionButton color="primary" on:click={store.newChildModel}>
Create Child Model on {model.name}
</ActionButton>
<ActionButton
color="primary"
on:click={async () => {
backendUiStore.actions.modals.show('VIEW')
await tick()
store.newChildIndex()
}}>
Create Child View on {model.name}
</ActionButton>
<ActionButton alert on:click={store.deleteCurrentNode}>
Delete
</ActionButton>
{/if}
</div>
</div>
{:else}
<FieldView
field={fieldToEdit}
onFinished={onFinishedFieldEdit}
allFields={model.fields}
store={$store} />
{/if}
<style>
.padding {
padding: 20px;
}
.new-field {
font-size: 16px;
font-weight: bold;
color: var(--button-text);
}
.fields-table {
margin: 1rem 1rem 0rem 0rem;
border-collapse: collapse;
}
tbody > tr:hover {
background-color: var(--primary10);
}
.table-controls {
display: flex;
justify-content: space-between;
align-items: center;
}
.ri-more-line:hover {
cursor: pointer;
}
heading {
padding: 20px 20px 0 20px;
display: flex;
align-items: center;
}
h3 {
margin: 0 0 0 10px;
}
.parent-name {
font-weight: bold;
}
</style>

View File

@ -0,0 +1,144 @@
<script>
import { tick } from "svelte"
import Textbox from "components/common/Textbox.svelte"
import Button from "components/common/Button.svelte"
import Select from "components/common/Select.svelte"
import ActionButton from "components/common/ActionButton.svelte"
import getIcon from "components/common/icon"
import FieldView from "./FieldView.svelte"
import api from "builderStore/api"
import { store, backendUiStore } from "builderStore"
import { pipe } from "components/common/core"
import ErrorsBox from "components/common/ErrorsBox.svelte"
export let model = { schema: {} }
export let onClosed
let showFieldView = false
let fieldToEdit
$: modelFields = model.schema ? Object.entries(model.schema) : []
$: instanceId = $backendUiStore.selectedDatabase.id
function editField() {}
function deleteField() {}
function onFinishedFieldEdit() {}
async function saveModel() {
const SAVE_MODEL_URL = `/api/${instanceId}/models`
const response = await api.post(SAVE_MODEL_URL, model)
const newModel = await response.json()
backendUiStore.actions.models.create(newModel.model)
onClosed()
}
</script>
<heading>
{#if !showFieldView}
<i class="ri-list-settings-line button--toggled" />
<h3 class="budibase__title--3">Create / Edit Model</h3>
{:else}
<i class="ri-file-list-line button--toggled" />
<h3 class="budibase__title--3">Create / Edit Field</h3>
{/if}
</heading>
{#if !showFieldView}
<div class="padding">
<h4 class="budibase__label--big">Settings</h4>
{#if $store.errors && $store.errors.length > 0}
<ErrorsBox errors={$store.errors} />
{/if}
<Textbox label="Name" bind:text={model.name} />
<div class="table-controls">
<span class="budibase__label--big">Fields</span>
<h4 class="hoverable new-field" on:click={() => (showFieldView = true)}>
Add new field
</h4>
</div>
<table class="uk-table fields-table budibase__table">
<thead>
<tr>
<th>Edit</th>
<th>Name</th>
<th>Type</th>
<th>Values</th>
<th />
</tr>
</thead>
<tbody>
{#each modelFields as [key, meta]}
<tr>
<td>
<i class="ri-more-line" on:click={() => editField(meta)} />
</td>
<td>
<div>{key}</div>
</td>
<td>{meta.type}</td>
<td>
<i
class="ri-delete-bin-6-line hoverable"
on:click={() => deleteField(meta)} />
</td>
</tr>
{/each}
</tbody>
</table>
<div class="uk-margin">
<ActionButton color="secondary" on:click={saveModel}>Save</ActionButton>
</div>
</div>
{:else}
<FieldView
field={fieldToEdit}
onFinished={onFinishedFieldEdit}
schema={model.schema}
goBack={() => (showFieldView = false)} />
{/if}
<style>
.padding {
padding: 20px;
}
.new-field {
font-size: 16px;
font-weight: bold;
color: var(--button-text);
}
.fields-table {
margin: 1rem 1rem 0rem 0rem;
border-collapse: collapse;
}
tbody > tr:hover {
background-color: var(--primary10);
}
.table-controls {
display: flex;
justify-content: space-between;
align-items: center;
}
.ri-more-line:hover {
cursor: pointer;
}
heading {
padding: 20px 20px 0 20px;
display: flex;
align-items: center;
}
h3 {
margin: 0 0 0 10px;
}
</style>

View File

@ -0,0 +1,80 @@
<script>
import Dropdown from "components/common/Dropdown.svelte"
import Textbox from "components/common/Textbox.svelte"
import Button from "components/common/Button.svelte"
import ButtonGroup from "components/common/ButtonGroup.svelte"
import NumberBox from "components/common/NumberBox.svelte"
import ValuesList from "components/common/ValuesList.svelte"
import ErrorsBox from "components/common/ErrorsBox.svelte"
import Checkbox from "components/common/Checkbox.svelte"
import ActionButton from "components/common/ActionButton.svelte"
import DatePicker from "components/common/DatePicker.svelte"
import { keys, cloneDeep } from "lodash/fp"
const FIELD_TYPES = ["string", "number", "boolean"]
export let field = { type: "string" }
export let schema
export let goBack
let errors = []
let draftField = cloneDeep(field)
const save = () => {
schema[field.name] = draftField
goBack()
}
</script>
<div class="root">
<ErrorsBox {errors} />
<form on:submit|preventDefault class="uk-form-stacked">
<Textbox label="Name" bind:text={field.name} />
<Dropdown
label="Type"
bind:selected={draftField.type}
options={FIELD_TYPES} />
{#if field.type === 'string'}
<NumberBox label="Max Length" bind:value={draftField.maxLength} />
<ValuesList label="Categories" bind:values={draftField.values} />
{:else if field.type === 'boolean'}
<!-- TODO: revisit and fix with JSON schema -->
<Checkbox label="Allow Null" bind:checked={draftField.allowNulls} />
{:else if field.format === 'datetime'}
<!-- TODO: revisit and fix with JSON schema -->
<DatePicker label="Min Value" bind:value={draftField.minValue} />
<DatePicker label="Max Value" bind:value={draftField.maxValue} />
{:else if field.type === 'number'}
<NumberBox label="Min Value" bind:value={draftField.minimum} />
<NumberBox label="Max Value" bind:value={draftField.maximum} />
{:else if draftField.type.startsWith('array')}
<!-- TODO: revisit and fix with JSON schema -->
<NumberBox
label="Min Length"
bind:value={draftField.typeOptions.minLength} />
<NumberBox
label="Max Length"
bind:value={draftField.typeOptions.maxLength} />
{/if}
</form>
</div>
<footer>
<ActionButton primary on:click={save}>Save</ActionButton>
<ActionButton alert on:click={goBack}>Cancel</ActionButton>
</footer>
<style>
.root {
margin: 20px;
}
footer {
padding: 20px;
border-radius: 0 0 5px 5px;
bottom: 0;
left: 0;
background: #fafafa;
}
</style>

View File

@ -4,66 +4,56 @@
import { compose, map, get, flatten } from "lodash/fp" import { compose, map, get, flatten } from "lodash/fp"
import ActionButton from "components/common/ActionButton.svelte" import ActionButton from "components/common/ActionButton.svelte"
import Select from "components/common/Select.svelte" import Select from "components/common/Select.svelte"
import {
getNewRecord,
joinKey,
getExactNodeForKey,
} from "components/common/core"
import RecordFieldControl from "./RecordFieldControl.svelte" import RecordFieldControl from "./RecordFieldControl.svelte"
import * as api from "../api" import * as api from "../api"
import ErrorsBox from "components/common/ErrorsBox.svelte" import ErrorsBox from "components/common/ErrorsBox.svelte"
export let record const CLASS_NAME_MAP = {
boolean: "uk-checkbox",
}
export let record = {}
export let onClosed export let onClosed
let errors = [] let errors = []
let selectedModel let selectedModel
const childModelsForModel = compose(flatten, map("children"), get("children")) $: instanceId = $backendUiStore.selectedDatabase.id
$: currentAppInfo = { $: modelSchema = $backendUiStore.selectedModel
appname: $store.appname, ? Object.entries($backendUiStore.selectedModel.schema)
instanceId: $backendUiStore.selectedDatabase.id, : []
}
$: models = $backendUiStore.selectedRecord
? childModelsForModel($store.hierarchy)
: $store.hierarchy.children
$: {
if (record) {
selectedModel = getExactNodeForKey($store.hierarchy)(record.key)
} else {
selectedModel = selectedModel || models[0]
}
}
$: modelFields = selectedModel ? selectedModel.fields : []
function getCurrentCollectionKey(selectedRecord) {
return selectedRecord
? joinKey(selectedRecord.key, selectedModel.collectionName)
: joinKey(selectedModel.collectionName)
}
$: editingRecord =
record ||
getNewRecord(
selectedModel,
getCurrentCollectionKey($backendUiStore.selectedRecord)
)
function closed() { function closed() {
editingRecord = null
onClosed() onClosed()
} }
function determineInputType(meta) {
if (meta.type === "datetime") return "date"
if (meta.type === "number") return "number"
if (meta.type === "boolean") return "checkbox"
return "text"
}
async function saveRecord() { async function saveRecord() {
const recordResponse = await api.saveRecord(editingRecord, currentAppInfo) const recordResponse = await api.saveRecord(
{
...record,
modelId: $backendUiStore.selectedModel._id,
},
instanceId
)
if (recordResponse.errors) {
errors = recordResponse.errors
return
}
backendUiStore.update(state => { backendUiStore.update(state => {
state.selectedView = state.selectedView state.selectedView = state.selectedView
onClosed()
return state return state
}) })
closed()
} }
</script> </script>
@ -71,18 +61,14 @@
<h4 class="budibase__title--4">Create / Edit Record</h4> <h4 class="budibase__title--4">Create / Edit Record</h4>
<ErrorsBox {errors} /> <ErrorsBox {errors} />
<form on:submit|preventDefault class="uk-form-stacked"> <form on:submit|preventDefault class="uk-form-stacked">
{#if !record} {#each modelSchema as [key, meta]}
<div class="uk-margin"> <div class="uk-margin">
<label class="uk-form-label" for="form-stacked-text">Model</label> <RecordFieldControl
<Select bind:value={selectedModel}> className={CLASS_NAME_MAP[meta.type]}
{#each models as model} type={determineInputType(meta)}
<option value={model}>{model.name}</option> label={key}
{/each} bind:value={record[key]} />
</Select>
</div> </div>
{/if}
{#each modelFields || [] as field}
<RecordFieldControl record={editingRecord} {field} {errors} />
{/each} {/each}
</form> </form>
</div> </div>

View File

@ -3,50 +3,39 @@
import CodeArea from "components/common/CodeArea.svelte" import CodeArea from "components/common/CodeArea.svelte"
import Button from "components/common/Button.svelte" import Button from "components/common/Button.svelte"
import Dropdown from "components/common/Dropdown.svelte" import Dropdown from "components/common/Dropdown.svelte"
import { store } from "builderStore" import { store, backendUiStore } from "builderStore"
import { filter, some, map, compose } from "lodash/fp" import { filter, some, map, compose } from "lodash/fp"
import {
hierarchy as hierarchyFunctions,
common,
} from "../../../../../../core/src/"
import ErrorsBox from "components/common/ErrorsBox.svelte" import ErrorsBox from "components/common/ErrorsBox.svelte"
import ActionButton from "components/common/ActionButton.svelte" import ActionButton from "components/common/ActionButton.svelte"
import api from "builderStore/api"
const SNIPPET_EDITORS = { const SNIPPET_EDITORS = {
MAP: "Map", MAP: "Map",
FILTER: "Filter", FILTER: "Filter",
SHARD: "Shard Name", REDUCE: "Reduce",
} }
let view const COUCHDB_FUNCTION = `function(doc) {
let indexableModels = []
}`
export let onClosed
export let view = {}
let currentSnippetEditor = SNIPPET_EDITORS.MAP let currentSnippetEditor = SNIPPET_EDITORS.MAP
const indexableModelsFromIndex = compose( $: instanceId = $backendUiStore.selectedDatabase.id
map(node => ({
node,
isallowed:
view.allowedModelNodeIds &&
view.allowedModelNodeIds.some(id => node.nodeId === id),
})),
filter(hierarchyFunctions.isModel),
filter(hierarchyFunctions.isDecendant($store.currentNode.parent())),
hierarchyFunctions.getFlattenedHierarchy
)
store.subscribe($store => { function deleteView() {}
view = $store.currentNode
indexableModels = indexableModelsFromIndex($store.hierarchy) async function saveView() {
const SAVE_VIEW_URL = `/api/${instanceId}/views`
const response = await api.post(SAVE_VIEW_URL, view)
backendUiStore.update(state => {
state.views = [...state.views, response.view]
return state
}) })
onClosed()
const toggleAllowedModel = model => {
if (model.isallowed) {
view.allowedModelNodeIds = view.allowedModelNodeIds.filter(
id => id !== model.node.nodeId
)
} else {
view.allowedModelNodeIds.push(model.node.nodeId)
}
} }
</script> </script>
@ -63,26 +52,6 @@
<div class="uk-width-1-2@s"> <div class="uk-width-1-2@s">
<Textbox bind:text={view.name} label="Name" /> <Textbox bind:text={view.name} label="Name" />
</div> </div>
<div class="uk-width-1-2@s">
<Dropdown
label="View Type"
bind:selected={view.indexType}
options={['ancestor', 'reference']} />
</div>
</div>
<div class="allowed-records">
<div class="budibase__label--big">
Which models would you like to add to this view?
</div>
{#each indexableModels as model}
<input
class="uk-checkbox"
type="checkbox"
checked={model.isallowed}
on:change={() => toggleAllowedModel(model)} />
<span class="checkbox-model-label">{model.node.name}</span>
{/each}
</div> </div>
<h4 class="budibase__label--big">Snippets</h4> <h4 class="budibase__label--big">Snippets</h4>
@ -98,18 +67,12 @@
<CodeArea bind:text={view.map} label="Map" /> <CodeArea bind:text={view.map} label="Map" />
{:else if currentSnippetEditor === SNIPPET_EDITORS.FILTER} {:else if currentSnippetEditor === SNIPPET_EDITORS.FILTER}
<CodeArea bind:text={view.filter} label="Filter" /> <CodeArea bind:text={view.filter} label="Filter" />
{:else if currentSnippetEditor === SNIPPET_EDITORS.SHARD} {:else if currentSnippetEditor === SNIPPET_EDITORS.REDUCE}
<CodeArea bind:text={view.getShardName} label="Shard Name" /> <CodeArea bind:text={view.reduce} label="Reduce" />
{/if}
<ActionButton color="secondary" on:click={store.saveCurrentNode}>
Save
</ActionButton>
{#if !$store.currentNodeIsNew}
<ActionButton alert on:click={store.deleteCurrentNode}>Delete</ActionButton>
{/if} {/if}
<ActionButton color="secondary" on:click={saveView}>Save</ActionButton>
<ActionButton alert on:click={deleteView}>Delete</ActionButton>
</form> </form>
<style> <style>
@ -118,14 +81,6 @@
padding: 15px; padding: 15px;
} }
.allowed-records {
margin: 20px 0px;
}
.allowed-records > span {
margin-right: 30px;
}
.snippet-selector__heading { .snippet-selector__heading {
margin-right: 20px; margin-right: 20px;
opacity: 0.7; opacity: 0.7;
@ -135,10 +90,6 @@
opacity: 1; opacity: 1;
} }
.checkbox-model-label {
text-transform: capitalize;
}
h3 { h3 {
margin: 0 0 0 10px; margin: 0 0 0 10px;
} }

View File

@ -7,39 +7,29 @@
let username let username
let password let password
let accessLevels = []
$: valid = username && password && accessLevels.length $: valid = username && password
$: currentAppInfo = { $: instanceId = $backendUiStore.selectedDatabase.id
appname: $store.appname, $: appId = $store.appId
instanceId: $backendUiStore.selectedDatabase.id,
}
async function createUser() { async function createUser() {
const user = { const user = { name: username, username, password }
name: username, const response = await api.createUser(user, appId, instanceId)
accessLevels, backendUiStore.actions.users.create(response)
enabled: true,
temporaryAccessId: "",
}
const response = await api.createUser(password, user, currentAppInfo)
backendUiStore.actions.users.save(user)
onClosed() onClosed()
} }
</script> </script>
<form on:submit|preventDefault class="uk-form-stacked"> <form on:submit|preventDefault class="uk-form-stacked">
<div> <div>
<div class="uk-margin">
<label class="uk-form-label" for="form-stacked-text">Username</label> <label class="uk-form-label" for="form-stacked-text">Username</label>
<input class="uk-input" type="text" bind:value={username} /> <input class="uk-input" type="text" bind:value={username} />
</div>
<div class="uk-margin">
<label class="uk-form-label" for="form-stacked-text">Password</label> <label class="uk-form-label" for="form-stacked-text">Password</label>
<input class="uk-input" type="password" bind:value={password} /> <input class="uk-input" type="password" bind:value={password} />
<label class="uk-form-label" for="form-stacked-text">Access Levels</label> </div>
<select multiple bind:value={accessLevels}>
{#each $store.accessLevels.levels as level}
<option value={level.name}>{level.name}</option>
{/each}
</select>
</div> </div>
<footer> <footer>
<ActionButton alert on:click={onClosed}>Cancel</ActionButton> <ActionButton alert on:click={onClosed}>Cancel</ActionButton>
@ -56,10 +46,4 @@
background: #fafafa; background: #fafafa;
border-radius: 0.5rem; border-radius: 0.5rem;
} }
select {
width: 100%;
}
option {
padding: 10px;
}
</style> </style>

View File

@ -6,10 +6,7 @@
export let record export let record
export let onClosed export let onClosed
$: currentAppInfo = { $: instanceId = $backendUiStore.selectedDatabase.id
appname: $store.appname,
instanceId: $backendUiStore.selectedDatabase.id,
}
</script> </script>
<section> <section>
@ -28,7 +25,7 @@
<ActionButton <ActionButton
alert alert
on:click={async () => { on:click={async () => {
await api.deleteRecord(record, currentAppInfo) await api.deleteRecord(record, instanceId)
backendUiStore.actions.records.delete(record) backendUiStore.actions.records.delete(record)
onClosed() onClosed()
}}> }}>

View File

@ -1,66 +1,33 @@
<script> <script>
import Select from "../../../common/Select.svelte" export let type = "text"
export let value = ""
export let label
export let errors = []
export let className = "uk-input"
export let record let checked = type === "checkbox" ? value : false
export let field
export let errors
$: isDropdown = const handleInput = event => {
field.type === "string" && if (event.target.type === "checkbox") {
field.typeOptions.values && value = event.target.checked
field.typeOptions.values.length > 0 return
}
$: isNumber = field.type === "number" if (event.target.type === "number") {
value = parseInt(event.target.value)
return
}
$: isText = field.type === "string" && !isDropdown value = event.target.value
}
$: isCheckbox = field.type === "bool"
$: isError = errors && errors.some(e => e.field && e.field === field.name)
$: isDatetime = field.type === "datetime"
</script> </script>
<div class="uk-margin"> <label>{label}</label>
{#if !isCheckbox} <input
<label class="uk-form-label" for={field.name}>{field.label}</label> class={className}
{/if} class:uk-form-danger={errors.length > 0}
<div class="uk-form-controls"> {checked}
{#if isDropdown} {type}
<Select bind:value={record[field.name]}> {value}
<option value="" /> on:input={handleInput}
{#each field.typeOptions.values as val} on:change={handleInput} />
<option value={val}>{val}</option>
{/each}
</Select>
{:else if isText}
<input
class="uk-input"
class:uk-form-danger={isError}
id={field.name}
type="text"
bind:value={record[field.name]} />
{:else if isNumber}
<input
class="uk-input"
class:uk-form-danger={isError}
type="number"
bind:value={record[field.name]} />
{:else if isDatetime}
<input
class="uk-input"
class:uk-form-danger={isError}
type="date"
bind:value={record[field.name]} />
{:else if isCheckbox}
<label>
<input
class="uk-checkbox"
class:uk-form-danger={isError}
type="checkbox"
bind:checked={record[field.name]} />
{field.label}
</label>
{/if}
</div>
</div>

View File

@ -1,6 +1,6 @@
export { default as DeleteRecordModal } from "./DeleteRecord.svelte" export { default as DeleteRecordModal } from "./DeleteRecord.svelte"
export { default as CreateEditRecordModal } from "./CreateEditRecord.svelte" export { default as CreateEditRecordModal } from "./CreateEditRecord.svelte"
export { default as CreateEditModelModal } from "./CreateEditModel.svelte" export { default as CreateEditModelModal } from "./CreateEditModel/CreateEditModel.svelte"
export { default as CreateEditViewModal } from "./CreateEditView.svelte" export { default as CreateEditViewModal } from "./CreateEditView.svelte"
export { default as CreateDatabaseModal } from "./CreateDatabase.svelte" export { default as CreateDatabaseModal } from "./CreateDatabase.svelte"
export { default as CreateUserModal } from "./CreateUser.svelte" export { default as CreateUserModal } from "./CreateUser.svelte"

View File

@ -61,10 +61,6 @@
</div> </div>
{/if} {/if}
<NavItem
name="ACCESS_LEVELS"
label="User Access Levels"
href="./accesslevels" />
</div> </div>
<style> <style>

View File

@ -6,16 +6,13 @@
import { CheckIcon } from "../common/Icons" import { CheckIcon } from "../common/Icons"
$: instances = $store.appInstances $: instances = $store.appInstances
$: views = $store.hierarchy.indexes
async function selectDatabase(database) { async function selectDatabase(database) {
backendUiStore.actions.records.select(null)
backendUiStore.actions.views.select(views[0])
backendUiStore.actions.database.select(database) backendUiStore.actions.database.select(database)
} }
async function deleteDatabase(database) { async function deleteDatabase(database) {
const DELETE_DATABASE_URL = `/_builder/instance/_master/0/api/record/applications/${$store.appId}/instances/${database.id}` const DELETE_DATABASE_URL = `/api/instances/${database.name}`
const response = await api.delete(DELETE_DATABASE_URL) const response = await api.delete(DELETE_DATABASE_URL)
store.update(state => { store.update(state => {
state.appInstances = state.appInstances.filter( state.appInstances = state.appInstances.filter(

View File

@ -7,12 +7,13 @@
CreateEditModelModal, CreateEditModelModal,
CreateEditViewModal, CreateEditViewModal,
} from "components/database/ModelDataTable/modals" } from "components/database/ModelDataTable/modals"
import api from "builderStore/api"
const { open, close } = getContext("simple-modal") const { open, close } = getContext("simple-modal")
export let level = 0
export let node export let node
export let type export let type
export let onSelect
let navActive = "" let navActive = ""
@ -20,47 +21,25 @@
index: "ri-eye-line", index: "ri-eye-line",
model: "ri-list-settings-line", model: "ri-list-settings-line",
} }
store.subscribe(state => {
if (state.currentNode) {
navActive = node.nodeId === state.currentNode.nodeId
}
})
function selectHierarchyItem(node) {
store.selectExistingNode(node.nodeId)
const modalType =
node.type === "index" ? CreateEditViewModal : CreateEditModelModal
open(
modalType,
{
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
}
</script> </script>
<div> <div>
<div <div
on:click={() => selectHierarchyItem(node)} on:click={() => onSelect(node)}
class="budibase__nav-item hierarchy-item" class="budibase__nav-item hierarchy-item"
class:capitalized={type === 'model'} class:capitalized={type === 'model'}
style="padding-left: {20 + level * 20}px" class:selected={$backendUiStore.selectedView === `all_${node._id}`}>
class:selected={navActive}>
<i class={ICON_MAP[type]} /> <i class={ICON_MAP[type]} />
<span style="margin-left: 1rem">{node.name}</span> <span style="margin-left: 1rem">{node.name}</span>
<!-- <i
class="ri-edit-line hoverable"
on:click={editModel}
/>
<i
class="ri-delete-bin-7-line hoverable"
on:click={deleteModel}
/> -->
</div> </div>
{#if node.children}
{#each node.children as child}
<svelte:self node={child} level={level + 1} type="model" />
{/each}
{/if}
{#if node.indexes}
{#each node.indexes as index}
<svelte:self node={index} level={level + 1} type="index" />
{/each}
{/if}
</div> </div>
<style> <style>
@ -68,6 +47,7 @@
font-size: 13px; font-size: 13px;
font-weight: 400; font-weight: 400;
margin-bottom: 10px; margin-bottom: 10px;
padding-left: 20px;
} }
.capitalized { .capitalized {

View File

@ -1,10 +1,10 @@
<script> <script>
import { getContext } from "svelte" import { getContext, onMount } from "svelte"
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import HierarchyRow from "./HierarchyRow.svelte" import HierarchyRow from "./HierarchyRow.svelte"
import DropdownButton from "components/common/DropdownButton.svelte"
import NavItem from "./NavItem.svelte" import NavItem from "./NavItem.svelte"
import getIcon from "components/common/icon" import getIcon from "components/common/icon"
import api from "builderStore/api"
import { import {
CreateEditModelModal, CreateEditModelModal,
CreateEditViewModal, CreateEditViewModal,
@ -12,12 +12,18 @@
const { open, close } = getContext("simple-modal") const { open, close } = getContext("simple-modal")
function newModel() { function editModel() {
if ($store.currentNode) { open(
store.newChildModel() CreateEditModelModal,
} else { {
store.newRootModel() model: node,
onClosed: close,
},
{ styleContent: { padding: "0" } }
)
} }
function newModel() {
open( open(
CreateEditModelModal, CreateEditModelModal,
{ {
@ -28,7 +34,6 @@
} }
function newView() { function newView() {
store.newRootIndex()
open( open(
CreateEditViewModal, CreateEditViewModal,
{ {
@ -37,32 +42,66 @@
{ styleContent: { padding: "0" } } { styleContent: { padding: "0" } }
) )
} }
function selectModel(model) {
backendUiStore.update(state => {
state.selectedModel = model
state.selectedView = `all_${model._id}`
return state
})
}
async function deleteModel(modelToDelete) {
const DELETE_MODEL_URL = `/api/${instanceId}/models/${node._id}/${node._rev}`
const response = await api.delete(DELETE_MODEL_URL)
backendUiStore.update(state => {
state.models = state.models.filter(
model => model._id !== modelToDelete._id
)
state.selectedView = {}
return state
})
}
function selectView(view) {
backendUiStore.update(state => {
state.selectedView = view.name
return state
})
}
</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 class="hierarchy-title">Schema</div> <div class="hierarchy-title">Models</div>
<div class="uk-inline"> <div class="uk-inline">
<i class="ri-add-line hoverable" /> <i class="ri-add-line hoverable" on:click={newModel} />
<div uk-dropdown="mode: click;">
<ul class="uk-nav uk-dropdown-nav">
<li class="hoverable" on:click={newModel}>Model</li>
<li class="hoverable" on:click={newView}>View</li>
</ul>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="hierarchy-items-container"> <div class="hierarchy-items-container">
{#each $store.hierarchy.children as model} {#each $backendUiStore.models as model}
<HierarchyRow node={model} type="model" /> <HierarchyRow onSelect={selectModel} node={model} type="model" />
{/each} {/each}
</div>
</div>
{#each $store.hierarchy.indexes as index} <div class="hierarchy">
<HierarchyRow node={index} type="index" /> <div class="components-list-container">
<div class="nav-group-header">
<div class="hierarchy-title">Views</div>
<div class="uk-inline">
<i class="ri-add-line hoverable" on:click={newView} />
</div>
</div>
</div>
<div class="hierarchy-items-container">
{#each $backendUiStore.views as view}
<HierarchyRow onSelect={selectView} node={view} type="view" />
{/each} {/each}
</div> </div>
</div> </div>

View File

@ -10,17 +10,15 @@
return { name, props } return { name, props }
} }
let users = []
$: currentAppInfo = { $: currentAppInfo = {
appname: $store.appname, appname: $store.appname,
instanceId: $backendUiStore.selectedDatabase.id, instanceId: $backendUiStore.selectedDatabase.id,
} }
async function fetchUsers() { async function fetchUsers() {
const FETCH_USERS_URL = `/_builder/instance/${currentAppInfo.appname}/${currentAppInfo.instanceId}/api/users` const FETCH_USERS_URL = `/api/${currentAppInfo.instanceId}/users`
const response = await api.get(FETCH_USERS_URL) const response = await api.get(FETCH_USERS_URL)
users = await response.json() const users = await response.json()
backendUiStore.update(state => { backendUiStore.update(state => {
state.users = users state.users = users
return state return state
@ -32,7 +30,7 @@
<div class="root"> <div class="root">
<ul> <ul>
{#each users as user} {#each $backendUiStore.users as user}
<li> <li>
<i class="ri-user-4-line" /> <i class="ri-user-4-line" />
<button class:active={user.id === $store.currentUserId}> <button class:active={user.id === $store.currentUserId}>

View File

@ -13,7 +13,7 @@
<div> <div>
<h4 style="margin-bottom: 20px">Choose an Application</h4> <h4 style="margin-bottom: 20px">Choose an Application</h4>
{#each apps as app} {#each apps as app}
<a href={`/_builder/${app}`} class="app-link">{app}</a> <a href={`/_builder/${app._id}`} class="app-link">{app.name}</a>
{/each} {/each}
</div> </div>
</div> </div>

View File

@ -0,0 +1,127 @@
<script>
import { store, backendUiStore } from "builderStore"
import { map, join } from "lodash/fp"
import iframeTemplate from "./iframeTemplate"
import { pipe } from "components/common/core"
let iframe
let styles = ""
function transform_component(comp) {
const props = comp.props || comp
if (props && props._children && props._children.length) {
props._children = props._children.map(transform_component)
}
return props
}
$: iframe &&
console.log(
iframe.contentDocument.head.insertAdjacentHTML(
"beforeend",
`<\style></style>`
)
)
$: hasComponent = !!$store.currentPreviewItem
$: {
// Apply the CSS from the currently selected page and its screens
const currentPage = $store.pages[$store.currentPageName]
styles += currentPage._css
for (let screen of currentPage._screens) {
styles += screen._css
}
styles = styles
}
$: stylesheetLinks = pipe($store.pages.stylesheets, [
map(s => `<link rel="stylesheet" href="${s}"/>`),
join("\n"),
])
$: screensExist =
$store.currentPreviewItem._screens &&
$store.currentPreviewItem._screens.length > 0
$: frontendDefinition = {
appId: $store.appId,
libraries: Object.keys($store.libraries),
page: $store.currentPreviewItem,
screens: screensExist
? $store.currentPreviewItem._screens
: [
{
name: "Screen Placeholder",
route: "*",
props: {
_component: "@budibase/standard-components/container",
type: "div",
_children: [
{
_component: "@budibase/standard-components/container",
_styles: { position: {}, layout: {} },
_id: "__screenslot__text",
_code: "",
className: "",
onLoad: [],
type: "div",
_children: [
{
_component: "@budibase/standard-components/text",
_styles: { position: {}, layout: {} },
_id: "__screenslot__text_2",
_code: "",
text: "content",
font: "",
color: "",
textAlign: "inline",
verticalAlign: "inline",
formattingTag: "none",
},
],
},
],
},
},
],
appRootPath: "",
}
$: selectedComponentId = $store.currentComponentInfo
? $store.currentComponentInfo._id
: ""
</script>
<div class="component-container">
{#if hasComponent && $store.currentPreviewItem}
<iframe
style="height: 100%; width: 100%"
title="componentPreview"
bind:this={iframe}
srcdoc={iframeTemplate({
styles,
stylesheetLinks,
selectedComponentId,
frontendDefinition: JSON.stringify(frontendDefinition),
currentPageFunctions: $store.currentPageFunctions,
})} />
{/if}
</div>
<style>
.component-container {
grid-row-start: middle;
grid-column-start: middle;
position: relative;
overflow: hidden;
margin: auto;
height: 100%;
}
.component-container iframe {
border: 0;
left: 0;
top: 0;
width: 100%;
}
</style>

View File

@ -0,0 +1,48 @@
export default ({
styles,
stylesheetLinks,
selectedComponentId,
frontendDefinition,
currentPageFunctions,
}) => `<html>
<head>
${stylesheetLinks}
<style>
${styles || ""}
.pos-${selectedComponentId} {
border: 2px solid #0055ff;
}
body, html {
height: 100%!important;
}
.lay-__screenslot__text {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 20px 0;
border: dashed 2px #ccc;
font-family: sans-serif;
font-size: 1.2rem;
color: #999;
text-transform: uppercase;
font-weight: bold;
}
<\/style>
<\script>
window["##BUDIBASE_FRONTEND_DEFINITION##"] = ${frontendDefinition};
window["##BUDIBASE_FRONTEND_FUNCTIONS##"] = ${currentPageFunctions};
import('/_builder/budibase-client.esm.mjs')
.then(module => {
module.loadBudibase({ window, localStorage });
})
<\/script>
</head>
<body>
</body>
</html>`

View File

@ -0,0 +1 @@
export { default } from "./CurrentItemPreview.svelte"

View File

@ -1,6 +1,5 @@
<script> <script>
import PropsView from "./PropsView.svelte" import PropsView from "./PropsView.svelte"
import StateBindingControl from "./StateBindingControl.svelte"
import { store } from "builderStore" import { store } from "builderStore"
import IconButton from "components/common/IconButton.svelte" import IconButton from "components/common/IconButton.svelte"
import { import {
@ -30,11 +29,10 @@
? getProps($store.currentPreviewItem, ["name", "favicon"]) ? getProps($store.currentPreviewItem, ["name", "favicon"])
: getProps($store.currentPreviewItem, ["name", "description", "route"]) : getProps($store.currentPreviewItem, ["name", "description", "route"])
const onPropChanged = store.setComponentProp
const onStyleChanged = store.setComponentStyle const onStyleChanged = store.setComponentStyle
function getProps(obj, keys) { function getProps(obj, keys) {
return keys.map((k, i) => [k, obj[k], obj.props._id + i]) return keys.map((key, i) => [key, obj[key], obj.props._id + i])
} }
</script> </script>
@ -89,14 +87,14 @@
on:input={({ target }) => store.setMetadataProp(k, target.value)} /> on:input={({ target }) => store.setMetadataProp(k, target.value)} />
</div> </div>
{/each} {/each}
<PropsView {component} {components} {onPropChanged} /> <PropsView {component} {components} />
{:else} {:else}
<PropsView {component} {components} {onPropChanged} /> <PropsView {component} {components} />
{/if} {/if}
{:else if current_view === 'layout'} {:else if current_view === 'layout'}
<LayoutEditor {onStyleChanged} {component} /> <LayoutEditor {onStyleChanged} {component} />
{:else if current_view === 'events'} {:else if current_view === 'events'}
<EventsEditor {component} {components} {onPropChanged} /> <EventsEditor {component} {components} />
{/if} {/if}
<CodeEditor <CodeEditor

View File

@ -13,12 +13,7 @@
flatten, flatten,
} from "lodash/fp" } from "lodash/fp"
import { import { pipe } from "components/common/core"
getRecordNodes,
getIndexNodes,
getIndexSchema,
pipe,
} from "components/common/core"
import Tab from "./ItemTab/Tab.svelte" import Tab from "./ItemTab/Tab.svelte"
import { store } from "builderStore" import { store } from "builderStore"
@ -34,54 +29,10 @@
const categories = components.categories const categories = components.categories
let selectedCategory = categories[0] let selectedCategory = categories[0]
const onTemplateChosen = template => {
selectedComponent = null
const { componentName, libName } = splitName(template.name)
const templateOptions = {
records: getRecordNodes(hierarchy),
indexes: getIndexNodes(hierarchy),
helpers: {
indexSchema: getIndexSchema(hierarchy),
},
}
templateInstances = libraryModules[libName][componentName](templateOptions)
if (!templateInstances || templateInstances.length === 0) return
selectedTemplateInstance = templateInstances[0].name
selectTemplateDialog.show()
}
const onComponentChosen = component => { const onComponentChosen = component => {
if (component.template) {
onTemplateChosen(component.template)
} else {
store.addChildComponent(component._component) store.addChildComponent(component._component)
toggleTab() toggleTab()
} }
}
const onTemplateInstanceChosen = () => {
selectedComponent = null
const instance = templateInstances.find(
i => i.name === selectedTemplateInstance
)
store.addTemplatedComponent(instance.props)
toggleTab()
}
$: templatesByComponent = groupBy(t => t.component)($store.templates)
$: hierarchy = $store.hierarchy
$: libraryModules = $store.libraries
$: standaloneTemplates = pipe(
templatesByComponent,
[
values,
flatten,
filter(t => !$store.components.some(c => c.name === t.component)),
map(t => ({ name: splitName(t.component).componentName, template: t })),
uniqBy(t => t.name),
]
)
</script> </script>
<div class="root"> <div class="root">
@ -98,30 +49,10 @@
<Tab <Tab
list={selectedCategory} list={selectedCategory}
on:selectItem={e => onComponentChosen(e.detail)} on:selectItem={e => onComponentChosen(e.detail)}
{onTemplateChosen}
{toggleTab} /> {toggleTab} />
</div> </div>
</div> </div>
<ConfirmDialog
bind:this={selectTemplateDialog}
title="Choose Template"
onCancel={() => (selectedComponent = null)}
onOk={onTemplateInstanceChosen}>
{#each templateInstances.map(i => i.name) as instance}
<div class="uk-margin uk-grid-small uk-child-width-auto uk-grid">
<label>
<input
class="uk-radio"
type="radio"
bind:group={selectedTemplateInstance}
value={instance} />
<span class="template-instance-label">{instance}</span>
</label>
</div>
{/each}
</ConfirmDialog>
<style> <style>
.tabs { .tabs {
display: flex; display: flex;

View File

@ -1,138 +0,0 @@
<script>
import { isRootComponent } from "./pagesParsing/searchComponents"
import { splitName } from "./pagesParsing/splitRootComponentName.js"
import { store } from "../builderStore"
import { find, sortBy } from "lodash/fp"
export let onComponentChosen
export let onGeneratorChosen
export let allowGenerators
let screens = []
let componentLibraries = []
const addRootComponent = (c, all, isGenerator) => {
const { libName } = splitName(c.name)
let group = find(r => r.libName === libName)(all)
if (!group) {
group = {
libName,
components: [],
generators: [],
}
all.push(group)
}
if (isGenerator) {
group.generators.push(c)
} else {
group.components.push(c)
}
}
$: {
const newComponentLibraries = []
const newscreens = []
for (let comp of sortBy(["name"])($store.components)) {
if (isRootComponent(comp)) {
addRootComponent(comp, newComponentLibraries, false)
} else {
newscreens.push(comp)
}
}
for (let generator of $store.generators) {
addRootComponent(generator, newComponentLibraries, true)
}
screens = sortBy(["name"])(newscreens)
componentLibraries = newComponentLibraries
}
</script>
{#each componentLibraries as lib}
<div class="library-header">{lib.libName}</div>
<div class="library-container">
{#if allowGenerators}
<div class="inner-header">Generators</div>
{#each lib.generators as generator}
<div class="component" on:click={() => onGeneratorChosen(generator)}>
<div class="name">{splitName(generator.name).componentName}</div>
<div class="description">{generator.description}</div>
</div>
{/each}
{/if}
<div class="inner-header">Components</div>
{#each lib.components as component}
<div class="component" on:click={() => onComponentChosen(component)}>
<div class="name">{splitName(component.name).componentName}</div>
<div class="description">{component.description}</div>
</div>
{/each}
</div>
{/each}
<div class="library-header">My Components</div>
<div class="library-container">
{#each screens as component}
<div class="component" on:click={() => onComponentChosen(component)}>
<div class="name">{component.name}</div>
<div class="description">{component.description}</div>
</div>
{/each}
</div>
<style>
.library-header {
font-size: 1.1em;
border-color: var(--primary25);
border-width: 1px 0px;
border-style: solid;
background-color: var(--primary10);
padding: 5px 0;
}
.library-container {
padding: 0 0 10px 10px;
}
.inner-header {
font-size: 0.9em;
font-weight: bold;
margin-top: 7px;
margin-bottom: 3px;
}
.component {
padding: 2px 0px;
cursor: pointer;
}
.component:hover {
background-color: var(--lightslate);
}
.component > .name {
color: var(--secondary100);
display: inline-block;
}
.component > .description {
font-size: 0.8em;
color: var(--secondary75);
display: inline-block;
margin-left: 10px;
}
</style>

View File

@ -31,8 +31,6 @@
const isComponentSelected = (current, comp) => current === comp const isComponentSelected = (current, comp) => current === comp
const isFolderSelected = (current, folder) => isInSubfolder(current, folder)
$: _screens = pipe(screens, [ $: _screens = pipe(screens, [
map(c => ({ component: c, title: lastPartOfName(c) })), map(c => ({ component: c, title: lastPartOfName(c) })),
sortBy("title"), sortBy("title"),

View File

@ -2,7 +2,7 @@
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { store } from "builderStore" import { store } from "builderStore"
import { last } from "lodash/fp" import { last } from "lodash/fp"
import { pipe } from "../common/core" import { pipe } from "components/common/core"
import { import {
XCircleIcon, XCircleIcon,
ChevronUpIcon, ChevronUpIcon,

View File

@ -1,146 +0,0 @@
<script>
import { store, backendUiStore } from "builderStore"
import { map, join } from "lodash/fp"
import { pipe } from "components/common/core"
let iframe
function transform_component(comp) {
const props = comp.props || comp
if (props && props._children && props._children.length) {
props._children = props._children.map(transform_component)
}
return props
}
$: iframe &&
console.log(
iframe.contentDocument.head.insertAdjacentHTML(
"beforeend",
`<\style></style>`
)
)
$: hasComponent = !!$store.currentPreviewItem
$: styles = hasComponent ? $store.currentPreviewItem._css : ""
$: stylesheetLinks = pipe(
$store.pages.stylesheets,
[map(s => `<link rel="stylesheet" href="${s}"/>`), join("\n")]
)
$: screensExist = $store.currentPreviewItem._screens && $store.currentPreviewItem._screens.length > 0
$: frontendDefinition = {
componentLibraries: $store.loadLibraryUrls($store.currentPageName),
page: $store.currentPreviewItem,
screens: screensExist ? $store.currentPreviewItem._screens : [{
name: "Screen Placeholder",
route: "*",
props: {
_component: "@budibase/standard-components/container",
type: "div",
_children: [
{
_component: "@budibase/standard-components/container",
_styles: { "position": {},"layout": {} },
_id: "__screenslot__text",
_code: "",
className: "",
onLoad: [],
type: "div",
_children: [{
_component:"@budibase/standard-components/text",
_styles: { "position": {}, "layout": {} },
_id: "__screenslot__text_2",
_code: "",
text: "content",
font: "",
color: "",
textAlign: "inline",
verticalAlign: "inline",
formattingTag: "none"
}]
}
]
}
}],
appRootPath: `/_builder/instance/${$store.appname}/${$backendUiStore.selectedDatabase.id}/`,
}
$: backendDefinition = {
hierarchy: $store.hierarchy,
}
$: selectedComponentId = $store.currentComponentInfo ? $store.currentComponentInfo._id : ""
</script>
<div class="component-container">
{#if hasComponent && $store.currentPreviewItem}
<iframe
style="height: 100%; width: 100%"
title="componentPreview"
bind:this={iframe}
srcdoc={`<html>
<head>
${stylesheetLinks}
<style>
${styles || ''}
.pos-${selectedComponentId} {
border: 2px solid #0055ff;
}
body, html {
height: 100%!important;
}
.lay-__screenslot__text {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 20px 0;
border: dashed 2px #ccc;
font-family: sans-serif;
font-size: 1.2rem;
color: #999;
text-transform: uppercase;
font-weight: bold;
}
<\/style>
<\script>
window["##BUDIBASE_FRONTEND_DEFINITION##"] = ${JSON.stringify(frontendDefinition)};
window["##BUDIBASE_BACKEND_DEFINITION##"] = ${JSON.stringify(backendDefinition)};
window["##BUDIBASE_FRONTEND_FUNCTIONS##"] = ${$store.currentPageFunctions};
import('/_builder/budibase-client.esm.mjs')
.then(module => {
module.loadBudibase({ window, localStorage });
})
<\/script>
</head>
<body>
</body>
</html>`} />
{/if}
</div>
<style>
.component-container {
grid-row-start: middle;
grid-column-start: middle;
position: relative;
overflow: hidden;
margin: auto;
height: 100%;
}
.component-container iframe {
border: 0;
left: 0;
top: 0;
width: 100%;
}
</style>

View File

@ -1,4 +1,5 @@
<script> <script>
import { store } from "builderStore";
import Modal from "../../common/Modal.svelte" import Modal from "../../common/Modal.svelte"
import HandlerSelector from "./HandlerSelector.svelte" import HandlerSelector from "./HandlerSelector.svelte"
import IconButton from "../../common/IconButton.svelte" import IconButton from "../../common/IconButton.svelte"
@ -14,7 +15,6 @@
export let eventOptions = [] export let eventOptions = []
export let open export let open
export let onClose export let onClose
export let onPropChanged
let eventType = "" let eventType = ""
let draftEventHandler = { parameters: [] } let draftEventHandler = { parameters: [] }
@ -52,12 +52,12 @@
} }
const deleteEvent = () => { const deleteEvent = () => {
onPropChanged(eventType, []) store.setComponentProp(eventType, [])
closeModal() closeModal()
} }
const saveEventData = () => { const saveEventData = () => {
onPropChanged(eventType, eventData.handlers) store.setComponentProp(eventType, eventData.handlers)
closeModal() closeModal()
} }
</script> </script>

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