|
@ -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",
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
.DS_Store
|
|
||||||
node_modules
|
|
||||||
yarn.lock
|
|
||||||
package-lock.json
|
|
||||||
dist/index.js
|
|
|
@ -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.
|
|
|
@ -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"]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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"
|
|
||||||
}
|
|
|
@ -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 */
|
|
|
@ -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"
|
|
||||||
}
|
|
|
@ -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: {},
|
|
||||||
}
|
|
Before Width: | Height: | Size: 3.1 KiB |
|
@ -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;
|
|
||||||
}
|
|
|
@ -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>
|
|
|
@ -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(),
|
|
||||||
],
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -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"))
|
|
||||||
}
|
|
||||||
})()
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
import App from "./TestApp.svelte"
|
|
||||||
|
|
||||||
const app = new App({
|
|
||||||
target: document.body,
|
|
||||||
})
|
|
||||||
|
|
||||||
export default app
|
|
|
@ -1,9 +0,0 @@
|
||||||
export const buildStyle = styles => {
|
|
||||||
let str = ""
|
|
||||||
for (let s in styles) {
|
|
||||||
if (styles[s]) {
|
|
||||||
str += `${s}: ${styles[s]}; `
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
export const emptyProps = () => ({ _component: "" })
|
|
|
@ -1,2 +0,0 @@
|
||||||
export { default as form } from "./Form.svelte"
|
|
||||||
export { default as nav } from "./Nav.svelte"
|
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
|
Before Width: | Height: | Size: 12 KiB |
|
@ -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")
|
||||||
|
|
|
@ -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: [] },
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)])
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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 {
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
status,
|
status,
|
||||||
timeout: 100000
|
timeout: 100000,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -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>
|
|
|
@ -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}
|
||||||
|
|
|
@ -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 |
|
@ -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>
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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()
|
||||||
}}>
|
}}>
|
||||||
|
|
|
@ -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>
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -61,10 +61,6 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<NavItem
|
|
||||||
name="ACCESS_LEVELS"
|
|
||||||
label="User Access Levels"
|
|
||||||
href="./accesslevels" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
@ -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>`
|
|
@ -0,0 +1 @@
|
||||||
|
export { default } from "./CurrentItemPreview.svelte"
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
||||||
|
|