|
@ -20,7 +20,7 @@
|
|||
"publishdev": "lerna run publishdev",
|
||||
"publishnpm": "yarn build && lerna publish",
|
||||
"clean": "lerna clean",
|
||||
"dev": "lerna run --parallel --stream dev:builder",
|
||||
"dev": "./scripts/symlinkDev.js && lerna run --parallel --stream dev:builder",
|
||||
"test": "lerna run test",
|
||||
"lint": "eslint 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/runtime": "^7.5.5",
|
||||
"@rollup/plugin-alias": "^3.0.1",
|
||||
"@rollup/plugin-json": "^4.0.3",
|
||||
"@sveltech/routify": "1.5.0-beta.40",
|
||||
"babel-jest": "^24.8.0",
|
||||
"browser-sync": "^2.26.7",
|
||||
|
@ -81,4 +82,4 @@
|
|||
"svelte": "^3.0.0"
|
||||
},
|
||||
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,14 +9,15 @@ import builtins from "rollup-plugin-node-builtins"
|
|||
import nodeglobals from "rollup-plugin-node-globals"
|
||||
import copy from "rollup-plugin-copy"
|
||||
import replace from "rollup-plugin-replace"
|
||||
import json from '@rollup/plugin-json';
|
||||
|
||||
import json from "@rollup/plugin-json"
|
||||
|
||||
import path from "path"
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH
|
||||
|
||||
const lodash_fp_exports = [
|
||||
"flow",
|
||||
"pipe",
|
||||
"union",
|
||||
"reduce",
|
||||
"isUndefined",
|
||||
|
@ -120,7 +121,16 @@ const coreExternal = [
|
|||
]
|
||||
|
||||
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)
|
||||
|
||||
|
@ -150,7 +160,7 @@ export default {
|
|||
targets: [
|
||||
{ src: "src/index.html", 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",
|
||||
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("unhandledrejection", showErrorBanner)
|
||||
})
|
||||
|
@ -26,6 +34,8 @@
|
|||
|
||||
<AppNotification />
|
||||
|
||||
<Modal>
|
||||
<Router {routes} />
|
||||
</Modal>
|
||||
{#if $store.clientId}
|
||||
<Modal>
|
||||
<Router {routes} />
|
||||
</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) =>
|
||||
fetch(url, {
|
||||
const apiCall = method => async (url, body) => {
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
@ -7,6 +7,13 @@ const apiCall = method => (url, body) =>
|
|||
body: body && JSON.stringify(body),
|
||||
})
|
||||
|
||||
// if (response.status === 500) {
|
||||
// throw new Error("Server Error");
|
||||
// }
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
const post = apiCall("POST")
|
||||
const get = apiCall("GET")
|
||||
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 { filter, map, reduce, toPairs } from "lodash/fp"
|
||||
|
||||
const self = n => n
|
||||
const join_with = delimiter => a => a.join(delimiter)
|
||||
|
@ -88,7 +88,7 @@ const css_map = {
|
|||
}
|
||||
|
||||
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]) => {
|
||||
let tmp = []
|
||||
|
@ -113,9 +113,7 @@ const object_to_css_string = [
|
|||
export const generate_css = ({ layout, position }) => {
|
||||
let _layout = pipe(layout, object_to_css_string)
|
||||
if (_layout.length) {
|
||||
_layout += `\ndisplay: ${
|
||||
_layout.includes("flex") ? "flex" : "grid"
|
||||
} !important;`
|
||||
_layout += `\ndisplay: ${_layout.includes("flex") ? "flex" : "grid"};`
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -13,4 +13,4 @@ export const initialise = async () => {
|
|||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = {}
|
||||
|
||||
for (let lib of libsFromPages(appPackage.pages)) {
|
||||
const libModule = await import(makeLibraryUrl(appName, lib))
|
||||
allLibraries[lib] = libModule
|
||||
for (let libraryName of application.componentLibraries) {
|
||||
const LIBRARY_URL = `/${application._id}/componentlibrary?library=${libraryName}`
|
||||
const libraryModule = await import(LIBRARY_URL)
|
||||
allLibraries[libraryName] = libraryModule
|
||||
}
|
||||
|
||||
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 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 = () => {
|
||||
const INITIAL_BACKEND_UI_STATE = {
|
||||
selectedView: {
|
||||
records: [],
|
||||
name: "",
|
||||
},
|
||||
breadcrumbs: [],
|
||||
models: [],
|
||||
views: [],
|
||||
users: [],
|
||||
selectedDatabase: {},
|
||||
selectedModel: {},
|
||||
}
|
||||
|
@ -27,12 +15,19 @@ export const getBackendUiStore = () => {
|
|||
|
||||
store.actions = {
|
||||
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 => {
|
||||
state.selectedDatabase = db
|
||||
state.breadcrumbs = [db.name]
|
||||
state.models = models
|
||||
state.views = views
|
||||
return state
|
||||
}),
|
||||
})
|
||||
},
|
||||
},
|
||||
records: {
|
||||
delete: () =>
|
||||
|
@ -51,6 +46,14 @@ export const getBackendUiStore = () => {
|
|||
return state
|
||||
}),
|
||||
},
|
||||
models: {
|
||||
create: model =>
|
||||
store.update(state => {
|
||||
state.models.push(model)
|
||||
state.models = state.models
|
||||
return state
|
||||
}),
|
||||
},
|
||||
views: {
|
||||
select: view =>
|
||||
store.update(state => {
|
||||
|
@ -72,281 +75,9 @@ export const getBackendUiStore = () => {
|
|||
}
|
||||
|
||||
// Store Actions
|
||||
export const createShadowHierarchy = hierarchy =>
|
||||
constructHierarchy(JSON.parse(JSON.stringify(hierarchy)))
|
||||
|
||||
export const createDatabaseForApp = store => appInstance => {
|
||||
store.update(state => {
|
||||
state.appInstances.push(appInstance)
|
||||
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 { filter, cloneDeep, last, concat, isEmpty, values } from "lodash/fp"
|
||||
import { pipe, getNode, constructHierarchy } from "components/common/core"
|
||||
import { cloneDeep, values } from "lodash/fp"
|
||||
import { backendUiStore } from "builderStore";
|
||||
import * as backendStoreActions from "./backend"
|
||||
import { writable, get } from "svelte/store"
|
||||
import { defaultPagesObject } from "components/userInterface/pagesParsing/defaultPagesObject"
|
||||
import api from "../api"
|
||||
import { DEFAULT_PAGES_OBJECT } from "../../constants"
|
||||
import { getExactComponent } from "components/userInterface/pagesParsing/searchComponents"
|
||||
import { rename } from "components/userInterface/pagesParsing/renameScreen"
|
||||
import {
|
||||
getNewScreen,
|
||||
createProps,
|
||||
makePropsSafe,
|
||||
getBuiltin,
|
||||
} from "components/userInterface/pagesParsing/createProps"
|
||||
import { expandComponentDefinition } from "components/userInterface/pagesParsing/types"
|
||||
import { loadLibs, libUrlsForPreview } from "../loadComponentLibraries"
|
||||
import {
|
||||
fetchComponentLibModules,
|
||||
fetchComponentLibDefinitions,
|
||||
} from "../loadComponentLibraries"
|
||||
import { buildCodeForScreens } from "../buildCodeForScreens"
|
||||
import { generate_screen_css } from "../generate_css"
|
||||
import { insertCodeMetadata } from "../insertCodeMetadata"
|
||||
|
@ -24,10 +24,7 @@ export const getStore = () => {
|
|||
const initial = {
|
||||
apps: [],
|
||||
appname: "",
|
||||
hierarchy: {},
|
||||
actions: [],
|
||||
triggers: [],
|
||||
pages: defaultPagesObject(),
|
||||
pages: DEFAULT_PAGES_OBJECT,
|
||||
mainUi: {},
|
||||
unauthenticatedUi: {},
|
||||
components: [],
|
||||
|
@ -36,52 +33,27 @@ export const getStore = () => {
|
|||
currentFrontEndType: "none",
|
||||
currentPageName: "",
|
||||
currentComponentProps: null,
|
||||
currentNodeIsNew: false,
|
||||
errors: [],
|
||||
hasAppPackage: false,
|
||||
accessLevels: { version: 0, levels: [] },
|
||||
currentNode: null,
|
||||
libraries: null,
|
||||
showSettings: false,
|
||||
useAnalytics: true
|
||||
appId: "",
|
||||
}
|
||||
|
||||
const store = writable(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.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.addComponentLibrary = addComponentLibrary(store)
|
||||
store.renameScreen = renameScreen(store)
|
||||
store.deleteScreen = deleteScreen(store)
|
||||
store.setCurrentScreen = setCurrentScreen(store)
|
||||
store.setCurrentPage = setCurrentPage(store)
|
||||
store.createScreen = createScreen(store)
|
||||
store.removeComponentLibrary = removeComponentLibrary(store)
|
||||
store.addStylesheet = addStylesheet(store)
|
||||
store.removeStylesheet = removeStylesheet(store)
|
||||
store.savePage = savePage(store)
|
||||
store.showSettings = showSettings(store)
|
||||
store.useAnalytics = useAnalytics(store)
|
||||
store.createGeneratedComponents = createGeneratedComponents(store)
|
||||
store.addChildComponent = addChildComponent(store)
|
||||
store.selectComponent = selectComponent(store)
|
||||
store.setComponentProp = setComponentProp(store)
|
||||
|
@ -100,12 +72,13 @@ export const getStore = () => {
|
|||
|
||||
export default getStore
|
||||
|
||||
const setPackage = (store, initial) => async (pkg) => {
|
||||
|
||||
const setPackage = (store, initial) => async pkg => {
|
||||
const [main_screens, unauth_screens] = await Promise.all([
|
||||
api.get(`/_builder/api/${pkg.application.name}/pages/main/screens`).then(r => r.json()),
|
||||
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()),
|
||||
])
|
||||
|
||||
|
@ -120,73 +93,28 @@ const setPackage = (store, initial) => async (pkg) => {
|
|||
},
|
||||
}
|
||||
|
||||
initial.libraries = await loadLibs(pkg.application.name, pkg)
|
||||
initial.loadLibraryUrls = pageName => {
|
||||
const libs = libUrlsForPreview(pkg, pageName)
|
||||
return libs
|
||||
}
|
||||
initial.libraries = await fetchComponentLibModules(pkg.application)
|
||||
initial.components = await fetchComponentLibDefinitions(
|
||||
pkg.clientId,
|
||||
pkg.application._id
|
||||
)
|
||||
initial.appname = pkg.application.name
|
||||
initial.appId = pkg.application._id
|
||||
initial.pages = pkg.pages
|
||||
initial.hasAppPackage = true
|
||||
initial.hierarchy = pkg.appDefinition.hierarchy
|
||||
initial.accessLevels = pkg.accessLevels
|
||||
initial.screens = values(pkg.screens)
|
||||
initial.components = values(pkg.components.components).map(
|
||||
expandComponentDefinition
|
||||
)
|
||||
initial.templates = pkg.components.templates
|
||||
initial.builtins = [getBuiltin("##builtin/screenslot")]
|
||||
initial.actions = values(pkg.appDefinition.actions)
|
||||
initial.triggers = pkg.appDefinition.triggers
|
||||
initial.appInstances = pkg.application.instances
|
||||
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)
|
||||
}
|
||||
}
|
||||
initial.appId = pkg.application._id
|
||||
|
||||
store.set(initial)
|
||||
console.log(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 => {
|
||||
store.update(s => {
|
||||
return _saveScreen(store, s, screen)
|
||||
store.update(state => {
|
||||
return _saveScreen(store, state, screen)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -194,10 +122,7 @@ const _saveScreen = async (store, s, screen) => {
|
|||
const currentPageScreens = s.pages[s.currentPageName]._screens
|
||||
|
||||
await api
|
||||
.post(
|
||||
`/_builder/api/${s.appname}/pages/${s.currentPageName}/screen`,
|
||||
screen
|
||||
)
|
||||
.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen)
|
||||
.then(() => {
|
||||
if (currentPageScreens.includes(screen)) return
|
||||
|
||||
|
@ -208,10 +133,7 @@ const _saveScreen = async (store, s, screen) => {
|
|||
innerState.screens = screens
|
||||
innerState.currentPreviewItem = screen
|
||||
const safeProps = makePropsSafe(
|
||||
getComponentDefinition(
|
||||
innerState.components,
|
||||
screen.props._component
|
||||
),
|
||||
innerState.components[screen.props._component],
|
||||
screen.props
|
||||
)
|
||||
innerState.currentComponentInfo = safeProps
|
||||
|
@ -227,28 +149,30 @@ const _saveScreen = async (store, s, screen) => {
|
|||
|
||||
const _saveScreenApi = (screen, s) =>
|
||||
api
|
||||
.post(
|
||||
`/_builder/api/${s.appname}/pages/${s.currentPageName}/screen`,
|
||||
screen
|
||||
)
|
||||
.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen)
|
||||
.then(() => _savePage(s))
|
||||
|
||||
const createScreen = store => (screenName, route, layoutComponentName) => {
|
||||
store.update(s => {
|
||||
const newScreen = getNewScreen(
|
||||
s.components,
|
||||
layoutComponentName,
|
||||
screenName
|
||||
)
|
||||
store.update(state => {
|
||||
const rootComponent = state.components[layoutComponentName]
|
||||
|
||||
const newScreen = {
|
||||
name: screenName || "",
|
||||
description: "",
|
||||
url: "",
|
||||
_css: "",
|
||||
uiFunctions: "",
|
||||
props: createProps(rootComponent).props,
|
||||
}
|
||||
|
||||
newScreen.route = route
|
||||
s.currentPreviewItem = newScreen
|
||||
s.currentComponentInfo = newScreen.props
|
||||
s.currentFrontEndType = "screen"
|
||||
state.currentPreviewItem = newScreen
|
||||
state.currentComponentInfo = newScreen.props
|
||||
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"
|
||||
|
||||
const safeProps = makePropsSafe(
|
||||
getComponentDefinition(s.components, screen.props._component),
|
||||
s.components[screen.props._component],
|
||||
screen.props
|
||||
)
|
||||
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 => {
|
||||
store.update(s => {
|
||||
const components = pipe(s.components, [filter(c => c.name !== name)])
|
||||
|
||||
const screens = pipe(s.screens, [filter(c => c.name !== name)])
|
||||
const components = s.components.filter(c => c.name !== name)
|
||||
const screens = s.screens.filter(c => c.name !== name);
|
||||
|
||||
s.components = components
|
||||
s.screens = screens
|
||||
|
@ -303,7 +207,7 @@ const deleteScreen = store => name => {
|
|||
s.currentFrontEndType = ""
|
||||
}
|
||||
|
||||
api.delete(`/_builder/api/${s.appname}/screen/${name}`)
|
||||
api.delete(`/_builder/api/${s.appId}/screen/${name}`)
|
||||
|
||||
return s
|
||||
})
|
||||
|
@ -331,12 +235,12 @@ const renameScreen = store => (oldname, newname) => {
|
|||
const saveAllChanged = async () => {
|
||||
for (let screenName of changedScreens) {
|
||||
const changedScreen = getExactComponent(screens, screenName)
|
||||
await api.post(`/_builder/api/${s.appname}/screen`, changedScreen)
|
||||
await api.post(`/_builder/api/${s.appId}/screen`, changedScreen)
|
||||
}
|
||||
}
|
||||
|
||||
api
|
||||
.patch(`/_builder/api/${s.appname}/screen`, {
|
||||
.patch(`/_builder/api/${s.appId}/screen`, {
|
||||
oldname,
|
||||
newname,
|
||||
})
|
||||
|
@ -350,56 +254,14 @@ const renameScreen = store => (oldname, newname) => {
|
|||
}
|
||||
|
||||
const savePage = store => async page => {
|
||||
store.update(s => {
|
||||
store.update(state => {
|
||||
if (s.currentFrontEndType !== "page" || !s.currentPageName) {
|
||||
return s
|
||||
return state
|
||||
}
|
||||
|
||||
s.pages[s.currentPageName] = page
|
||||
_savePage(s)
|
||||
return s
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -412,17 +274,17 @@ const addStylesheet = store => stylesheet => {
|
|||
}
|
||||
|
||||
const removeStylesheet = store => stylesheet => {
|
||||
store.update(s => {
|
||||
s.pages.stylesheets = filter(s => s !== stylesheet)(s.pages.stylesheets)
|
||||
_savePage(s)
|
||||
return s
|
||||
store.update(state => {
|
||||
state.pages.stylesheets = s.pages.stylesheets.filter(s => s !== stylesheet)
|
||||
_savePage(state)
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
const _savePage = async s => {
|
||||
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 },
|
||||
uiFunctions: s.currentPageFunctions,
|
||||
screens: page._screens,
|
||||
|
@ -430,36 +292,37 @@ const _savePage = async s => {
|
|||
}
|
||||
|
||||
const setCurrentPage = store => pageName => {
|
||||
store.update(s => {
|
||||
const current_screens = s.pages[pageName]._screens
|
||||
store.update(state => {
|
||||
const current_screens = state.pages[pageName]._screens
|
||||
|
||||
s.currentFrontEndType = "page"
|
||||
s.currentPageName = pageName
|
||||
s.screens = Array.isArray(current_screens)
|
||||
const currentPage = state.pages[pageName]
|
||||
|
||||
state.currentFrontEndType = "page"
|
||||
state.currentPageName = pageName
|
||||
state.screens = Array.isArray(current_screens)
|
||||
? current_screens
|
||||
: Object.values(current_screens)
|
||||
const safeProps = makePropsSafe(
|
||||
getComponentDefinition(s.components, s.pages[pageName].props._component),
|
||||
s.pages[pageName].props
|
||||
state.components[currentPage.props._component],
|
||||
currentPage.props
|
||||
)
|
||||
s.currentComponentInfo = safeProps
|
||||
s.pages[pageName].props = safeProps
|
||||
s.currentPreviewItem = s.pages[pageName]
|
||||
s.currentPreviewItem._css = generate_screen_css([
|
||||
s.currentPreviewItem.props,
|
||||
state.currentComponentInfo = safeProps
|
||||
currentPage.props = safeProps
|
||||
state.currentPreviewItem = state.pages[pageName]
|
||||
state.currentPreviewItem._css = generate_screen_css([
|
||||
state.currentPreviewItem.props,
|
||||
])
|
||||
|
||||
for (let screen of s.screens) {
|
||||
for (let screen of state.screens) {
|
||||
screen._css = generate_screen_css([screen.props])
|
||||
}
|
||||
|
||||
setCurrentPageFunctions(s)
|
||||
return s
|
||||
setCurrentPageFunctions(state)
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
const getComponentDefinition = (components, name) =>
|
||||
components.find(c => c.name === name)
|
||||
// const getComponentDefinition = (components, name) => components.find(c => c.name === name)
|
||||
|
||||
/**
|
||||
* @param {string} componentToAdd - name of the component to add to the application
|
||||
|
@ -469,8 +332,10 @@ const addChildComponent = store => (componentToAdd, presetName) => {
|
|||
store.update(state => {
|
||||
function findSlot(component_array) {
|
||||
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
|
||||
}
|
||||
|
||||
if (component_array[i]._children) findSlot(component_array[i])
|
||||
}
|
||||
|
||||
|
@ -486,10 +351,16 @@ const addChildComponent = store => (componentToAdd, presetName) => {
|
|||
|
||||
const component = componentToAdd.startsWith("##")
|
||||
? getBuiltin(componentToAdd)
|
||||
: state.components.find(({ name }) => name === componentToAdd)
|
||||
: state.components[componentToAdd]
|
||||
|
||||
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(
|
||||
newComponent.props
|
||||
|
@ -532,7 +403,7 @@ const selectComponent = store => component => {
|
|||
store.update(state => {
|
||||
const componentDef = component._component.startsWith("##")
|
||||
? component
|
||||
: state.components.find(c => c.name === component._component)
|
||||
: state.components[component._component]
|
||||
state.currentComponentInfo = makePropsSafe(componentDef, component)
|
||||
state.currentView = "component"
|
||||
return state
|
||||
|
@ -552,28 +423,28 @@ const setComponentProp = store => (name, value) => {
|
|||
}
|
||||
|
||||
const setComponentStyle = store => (type, name, value) => {
|
||||
store.update(s => {
|
||||
if (!s.currentComponentInfo._styles) {
|
||||
s.currentComponentInfo._styles = {}
|
||||
store.update(state => {
|
||||
if (!state.currentComponentInfo._styles) {
|
||||
state.currentComponentInfo._styles = {}
|
||||
}
|
||||
s.currentComponentInfo._styles[type][name] = value
|
||||
s.currentPreviewItem._css = generate_screen_css([
|
||||
s.currentPreviewItem.props,
|
||||
state.currentComponentInfo._styles[type][name] = value
|
||||
state.currentPreviewItem._css = generate_screen_css([
|
||||
state.currentPreviewItem.props,
|
||||
])
|
||||
|
||||
// save without messing with the store
|
||||
_saveCurrentPreviewItem(s)
|
||||
return s
|
||||
_saveCurrentPreviewItem(state)
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
const setComponentCode = store => code => {
|
||||
store.update(s => {
|
||||
s.currentComponentInfo._code = code
|
||||
store.update(state => {
|
||||
state.currentComponentInfo._code = code
|
||||
|
||||
setCurrentPageFunctions(s)
|
||||
setCurrentPageFunctions(state)
|
||||
// save without messing with the store
|
||||
_saveScreenApi(s.currentPreviewItem, s)
|
||||
_saveScreenApi(state.currentPreviewItem, state)
|
||||
|
||||
return s
|
||||
})
|
||||
|
@ -587,31 +458,33 @@ const setCurrentPageFunctions = s => {
|
|||
const buildPageCode = (screens, page) => buildCodeForScreens([page, ...screens])
|
||||
|
||||
const setScreenType = store => type => {
|
||||
store.update(s => {
|
||||
s.currentFrontEndType = type
|
||||
store.update(state => {
|
||||
state.currentFrontEndType = type
|
||||
|
||||
const pageOrScreen =
|
||||
type === "page"
|
||||
? s.pages[s.currentPageName]
|
||||
: s.pages[s.currentPageName]._screens[0]
|
||||
? state.pages[state.currentPageName]
|
||||
: state.pages[state.currentPageName]._screens[0]
|
||||
|
||||
s.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null
|
||||
s.currentPreviewItem = pageOrScreen
|
||||
return s
|
||||
state.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null
|
||||
state.currentPreviewItem = pageOrScreen
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
const deleteComponent = store => component => {
|
||||
store.update(s => {
|
||||
const parent = getParent(s.currentPreviewItem.props, component)
|
||||
const deleteComponent = store => componentName => {
|
||||
store.update(state => {
|
||||
const parent = getParent(state.currentPreviewItem.props, componentName)
|
||||
|
||||
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 => {
|
||||
|
||||
// Gets all the components to needed to construct a path.
|
||||
const tempStore = get(store)
|
||||
let pathComponents = []
|
||||
let parent = component;
|
||||
let parent = component
|
||||
let root = false
|
||||
while (!root) {
|
||||
parent = getParent(tempStore.currentPreviewItem.props, parent)
|
||||
|
@ -695,7 +567,7 @@ const getPathToComponent = store => component => {
|
|||
const IdList = allComponents.map(c => c._id)
|
||||
|
||||
// Construct ID Path:
|
||||
const path = IdList.join('/')
|
||||
const path = IdList.join("/")
|
||||
|
||||
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 {
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
filter:saturate(90%);
|
||||
filter: saturate(90%);
|
||||
}
|
||||
|
||||
.button:disabled {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
</div>
|
||||
`,
|
||||
status,
|
||||
timeout: 100000
|
||||
timeout: 100000,
|
||||
})
|
||||
}
|
||||
</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}
|
||||
<div uk-alert class="uk-alert-danger">
|
||||
{#each errors as error}
|
||||
<div>{error.field ? `${error.field}: ` : ''}{error.error}</div>
|
||||
<div>{error.dataPath} {error.message}</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="100%"
|
||||
height="100%">
|
||||
<path fill="none" d="M0 0h24v24H0z"/>
|
||||
<path
|
||||
fill="rgba(0,3,51,1)"
|
||||
d="M12 14l-4-4h8z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="100%"
|
||||
height="100%">
|
||||
<path fill="none" d="M0 0h24v24H0z" />
|
||||
<path fill="rgba(0,3,51,1)" d="M12 14l-4-4h8z" />
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 228 B After Width: | Height: | Size: 196 B |
|
@ -16,6 +16,10 @@
|
|||
<div class="uk-margin">
|
||||
<label class="uk-form-label">{label}</label>
|
||||
<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>
|
||||
|
|
|
@ -1,132 +1,3 @@
|
|||
import {
|
||||
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"
|
||||
import { flow } from "lodash/fp"
|
||||
|
||||
export { canDeleteIndex } from "../../../../core/src/templateApi/canDeleteIndex"
|
||||
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,
|
||||
}
|
||||
}
|
||||
export const pipe = (arg, funcs) => flow(funcs)(arg)
|
||||
|
|
|
@ -1,21 +1,18 @@
|
|||
import { eventHandlers } from "../../../../client/src/state/eventHandlers"
|
||||
import { writable } from "svelte/store"
|
||||
export { EVENT_TYPE_MEMBER_NAME } from "../../../../client/src/state/eventHandlers"
|
||||
import { createCoreApi } from "../../../../client/src/core"
|
||||
|
||||
export const allHandlers = (appDefinition, user) => {
|
||||
const coreApi = createCoreApi(appDefinition, user)
|
||||
appDefinition.hierarchy = coreApi.templateApi.constructHierarchy(
|
||||
appDefinition.hierarchy
|
||||
)
|
||||
export const allHandlers = user => {
|
||||
const store = writable({
|
||||
_bbuser: user,
|
||||
})
|
||||
|
||||
const handlersObj = eventHandlers(store, coreApi)
|
||||
const handlersArray = []
|
||||
for (let key in handlersObj) {
|
||||
handlersArray.push({ name: key, ...handlersObj[key] })
|
||||
}
|
||||
return handlersArray
|
||||
const handlersObj = eventHandlers(store)
|
||||
|
||||
const handlers = Object.keys(handlersObj).map(name => ({
|
||||
name,
|
||||
...handlersObj[name],
|
||||
}))
|
||||
|
||||
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,
|
||||
} from "lodash/fp"
|
||||
import Select from "components/common/Select.svelte"
|
||||
import { getIndexSchema } from "components/common/core"
|
||||
import ActionButton from "components/common/ActionButton.svelte"
|
||||
import TablePagination from "./TablePagination.svelte"
|
||||
import { DeleteRecordModal, CreateEditRecordModal } from "./modals"
|
||||
|
@ -27,7 +26,7 @@
|
|||
CreateEditRecordModal,
|
||||
{
|
||||
onClosed: close,
|
||||
record: await selectRecord(row),
|
||||
record: row,
|
||||
},
|
||||
{ styleContent: { padding: "0" } }
|
||||
)
|
||||
|
@ -38,7 +37,7 @@
|
|||
DeleteRecordModal,
|
||||
{
|
||||
onClosed: close,
|
||||
record: await selectRecord(row),
|
||||
record: row,
|
||||
},
|
||||
{ styleContent: { padding: "0" } }
|
||||
)
|
||||
|
@ -53,7 +52,7 @@
|
|||
|
||||
const ITEMS_PER_PAGE = 10
|
||||
// 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 data = []
|
||||
|
@ -61,60 +60,26 @@
|
|||
let views = []
|
||||
let currentPage = 0
|
||||
|
||||
$: views = $backendUiStore.selectedRecord
|
||||
? childViewsForRecord($store.hierarchy)
|
||||
: $store.hierarchy.indexes
|
||||
$: instanceId = $backendUiStore.selectedDatabase.id
|
||||
|
||||
$: currentAppInfo = {
|
||||
appname: $store.appname,
|
||||
instanceId: $backendUiStore.selectedDatabase.id,
|
||||
$: {
|
||||
if ($backendUiStore.selectedView) {
|
||||
api
|
||||
.fetchDataForView($backendUiStore.selectedView, instanceId)
|
||||
.then(records => {
|
||||
data = records || []
|
||||
headers = Object.keys($backendUiStore.selectedModel.schema).filter(
|
||||
key => !INTERNAL_HEADERS.includes(key)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
$: fetchRecordsForView(
|
||||
$backendUiStore.selectedView,
|
||||
$backendUiStore.selectedDatabase
|
||||
).then(records => {
|
||||
data = records || []
|
||||
headers = hideInternalHeaders($backendUiStore.selectedView)
|
||||
})
|
||||
|
||||
$: paginatedData = data.slice(
|
||||
currentPage * 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(() => {
|
||||
if (views.length) {
|
||||
backendUiStore.actions.views.select(views[0])
|
||||
|
@ -124,14 +89,7 @@
|
|||
|
||||
<section>
|
||||
<div class="table-controls">
|
||||
<h2 class="title">
|
||||
{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>
|
||||
<h2 class="title">{$backendUiStore.selectedModel.name}</h2>
|
||||
</div>
|
||||
<table class="uk-table">
|
||||
<thead>
|
||||
|
@ -153,9 +111,6 @@
|
|||
<i class="ri-more-line" />
|
||||
<div uk-dropdown="mode: click">
|
||||
<ul class="uk-nav uk-dropdown-nav">
|
||||
<li>
|
||||
<div on:click={() => drillIntoRecord(row)}>View</div>
|
||||
</li>
|
||||
<li
|
||||
on:click={() => {
|
||||
editRecord(row)
|
||||
|
@ -196,10 +151,6 @@
|
|||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.select {
|
||||
background: white;
|
||||
}
|
||||
|
||||
table {
|
||||
border: 1px solid #ccc;
|
||||
background: #fff;
|
||||
|
|
|
@ -1,59 +1,36 @@
|
|||
import api from "builderStore/api"
|
||||
import { getNewRecord, getNewInstance } from "components/common/core"
|
||||
|
||||
export async function createUser(password, user, { appname, instanceId }) {
|
||||
const CREATE_USER_URL = `/_builder/instance/${appname}/${instanceId}/api/createUser`
|
||||
const response = await api.post(CREATE_USER_URL, { user, password })
|
||||
export async function createUser(user, appId, instanceId) {
|
||||
const CREATE_USER_URL = `/api/${appId}/${instanceId}/users`
|
||||
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()
|
||||
}
|
||||
|
||||
export async function createDatabase(appname, instanceName) {
|
||||
const CREATE_DATABASE_URL = `/_builder/instance/_master/0/api/record/`
|
||||
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}`
|
||||
export async function deleteRecord(record, instanceId) {
|
||||
const DELETE_RECORDS_URL = `/api/${instanceId}/records/${record._id}/${record._rev}`
|
||||
const response = await api.delete(DELETE_RECORDS_URL)
|
||||
return response
|
||||
}
|
||||
|
||||
export async function loadRecord(key, { appname, instanceId }) {
|
||||
const LOAD_RECORDS_URL = `/_builder/instance/${appname}/${instanceId}/api/record${key}`
|
||||
const response = await api.get(LOAD_RECORDS_URL)
|
||||
export async function saveRecord(record, instanceId) {
|
||||
const SAVE_RECORDS_URL = `/api/${instanceId}/records`
|
||||
const response = await api.post(SAVE_RECORDS_URL, record)
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
export async function saveRecord(record, { appname, instanceId }) {
|
||||
let recordBase = { ...record }
|
||||
|
||||
// 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}`
|
||||
export async function fetchDataForView(viewName, instanceId) {
|
||||
const FETCH_RECORDS_URL = `/api/${instanceId}/${viewName}/records`
|
||||
|
||||
const response = await api.get(FETCH_RECORDS_URL)
|
||||
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
|
||||
|
||||
async function createDatabase() {
|
||||
const response = await api.createDatabase($store.appId, databaseName)
|
||||
store.createDatabaseForApp(response)
|
||||
const response = await api.createDatabase(
|
||||
$store.clientId,
|
||||
$store.appId,
|
||||
databaseName
|
||||
)
|
||||
store.createDatabaseForApp(response.instance)
|
||||
onClosed()
|
||||
}
|
||||
</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 ActionButton from "components/common/ActionButton.svelte"
|
||||
import Select from "components/common/Select.svelte"
|
||||
import {
|
||||
getNewRecord,
|
||||
joinKey,
|
||||
getExactNodeForKey,
|
||||
} from "components/common/core"
|
||||
import RecordFieldControl from "./RecordFieldControl.svelte"
|
||||
import * as api from "../api"
|
||||
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
||||
|
||||
export let record
|
||||
const CLASS_NAME_MAP = {
|
||||
boolean: "uk-checkbox",
|
||||
}
|
||||
|
||||
export let record = {}
|
||||
export let onClosed
|
||||
|
||||
let errors = []
|
||||
let selectedModel
|
||||
|
||||
const childModelsForModel = compose(flatten, map("children"), get("children"))
|
||||
$: instanceId = $backendUiStore.selectedDatabase.id
|
||||
|
||||
$: currentAppInfo = {
|
||||
appname: $store.appname,
|
||||
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)
|
||||
)
|
||||
$: modelSchema = $backendUiStore.selectedModel
|
||||
? Object.entries($backendUiStore.selectedModel.schema)
|
||||
: []
|
||||
|
||||
function closed() {
|
||||
editingRecord = null
|
||||
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() {
|
||||
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 => {
|
||||
state.selectedView = state.selectedView
|
||||
onClosed()
|
||||
return state
|
||||
})
|
||||
closed()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -71,18 +61,14 @@
|
|||
<h4 class="budibase__title--4">Create / Edit Record</h4>
|
||||
<ErrorsBox {errors} />
|
||||
<form on:submit|preventDefault class="uk-form-stacked">
|
||||
{#if !record}
|
||||
{#each modelSchema as [key, meta]}
|
||||
<div class="uk-margin">
|
||||
<label class="uk-form-label" for="form-stacked-text">Model</label>
|
||||
<Select bind:value={selectedModel}>
|
||||
{#each models as model}
|
||||
<option value={model}>{model.name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
<RecordFieldControl
|
||||
className={CLASS_NAME_MAP[meta.type]}
|
||||
type={determineInputType(meta)}
|
||||
label={key}
|
||||
bind:value={record[key]} />
|
||||
</div>
|
||||
{/if}
|
||||
{#each modelFields || [] as field}
|
||||
<RecordFieldControl record={editingRecord} {field} {errors} />
|
||||
{/each}
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -3,50 +3,39 @@
|
|||
import CodeArea from "components/common/CodeArea.svelte"
|
||||
import Button from "components/common/Button.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 {
|
||||
hierarchy as hierarchyFunctions,
|
||||
common,
|
||||
} from "../../../../../../core/src/"
|
||||
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
||||
import ActionButton from "components/common/ActionButton.svelte"
|
||||
import api from "builderStore/api"
|
||||
|
||||
const SNIPPET_EDITORS = {
|
||||
MAP: "Map",
|
||||
FILTER: "Filter",
|
||||
SHARD: "Shard Name",
|
||||
REDUCE: "Reduce",
|
||||
}
|
||||
|
||||
let view
|
||||
let indexableModels = []
|
||||
const COUCHDB_FUNCTION = `function(doc) {
|
||||
|
||||
}`
|
||||
|
||||
export let onClosed
|
||||
export let view = {}
|
||||
|
||||
let currentSnippetEditor = SNIPPET_EDITORS.MAP
|
||||
|
||||
const indexableModelsFromIndex = compose(
|
||||
map(node => ({
|
||||
node,
|
||||
isallowed:
|
||||
view.allowedModelNodeIds &&
|
||||
view.allowedModelNodeIds.some(id => node.nodeId === id),
|
||||
})),
|
||||
filter(hierarchyFunctions.isModel),
|
||||
filter(hierarchyFunctions.isDecendant($store.currentNode.parent())),
|
||||
hierarchyFunctions.getFlattenedHierarchy
|
||||
)
|
||||
$: instanceId = $backendUiStore.selectedDatabase.id
|
||||
|
||||
store.subscribe($store => {
|
||||
view = $store.currentNode
|
||||
indexableModels = indexableModelsFromIndex($store.hierarchy)
|
||||
})
|
||||
function deleteView() {}
|
||||
|
||||
const toggleAllowedModel = model => {
|
||||
if (model.isallowed) {
|
||||
view.allowedModelNodeIds = view.allowedModelNodeIds.filter(
|
||||
id => id !== model.node.nodeId
|
||||
)
|
||||
} else {
|
||||
view.allowedModelNodeIds.push(model.node.nodeId)
|
||||
}
|
||||
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()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -63,26 +52,6 @@
|
|||
<div class="uk-width-1-2@s">
|
||||
<Textbox bind:text={view.name} label="Name" />
|
||||
</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>
|
||||
|
||||
<h4 class="budibase__label--big">Snippets</h4>
|
||||
|
@ -98,18 +67,12 @@
|
|||
<CodeArea bind:text={view.map} label="Map" />
|
||||
{:else if currentSnippetEditor === SNIPPET_EDITORS.FILTER}
|
||||
<CodeArea bind:text={view.filter} label="Filter" />
|
||||
{:else if currentSnippetEditor === SNIPPET_EDITORS.SHARD}
|
||||
<CodeArea bind:text={view.getShardName} label="Shard Name" />
|
||||
{/if}
|
||||
|
||||
<ActionButton color="secondary" on:click={store.saveCurrentNode}>
|
||||
Save
|
||||
</ActionButton>
|
||||
|
||||
{#if !$store.currentNodeIsNew}
|
||||
<ActionButton alert on:click={store.deleteCurrentNode}>Delete</ActionButton>
|
||||
{:else if currentSnippetEditor === SNIPPET_EDITORS.REDUCE}
|
||||
<CodeArea bind:text={view.reduce} label="Reduce" />
|
||||
{/if}
|
||||
|
||||
<ActionButton color="secondary" on:click={saveView}>Save</ActionButton>
|
||||
<ActionButton alert on:click={deleteView}>Delete</ActionButton>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
|
@ -118,14 +81,6 @@
|
|||
padding: 15px;
|
||||
}
|
||||
|
||||
.allowed-records {
|
||||
margin: 20px 0px;
|
||||
}
|
||||
|
||||
.allowed-records > span {
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.snippet-selector__heading {
|
||||
margin-right: 20px;
|
||||
opacity: 0.7;
|
||||
|
@ -135,10 +90,6 @@
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
.checkbox-model-label {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0 0 0 10px;
|
||||
}
|
||||
|
|
|
@ -7,39 +7,29 @@
|
|||
|
||||
let username
|
||||
let password
|
||||
let accessLevels = []
|
||||
|
||||
$: valid = username && password && accessLevels.length
|
||||
$: currentAppInfo = {
|
||||
appname: $store.appname,
|
||||
instanceId: $backendUiStore.selectedDatabase.id,
|
||||
}
|
||||
$: valid = username && password
|
||||
$: instanceId = $backendUiStore.selectedDatabase.id
|
||||
$: appId = $store.appId
|
||||
|
||||
async function createUser() {
|
||||
const user = {
|
||||
name: username,
|
||||
accessLevels,
|
||||
enabled: true,
|
||||
temporaryAccessId: "",
|
||||
}
|
||||
const response = await api.createUser(password, user, currentAppInfo)
|
||||
backendUiStore.actions.users.save(user)
|
||||
const user = { name: username, username, password }
|
||||
const response = await api.createUser(user, appId, instanceId)
|
||||
backendUiStore.actions.users.create(response)
|
||||
onClosed()
|
||||
}
|
||||
</script>
|
||||
|
||||
<form on:submit|preventDefault class="uk-form-stacked">
|
||||
<div>
|
||||
<label class="uk-form-label" for="form-stacked-text">Username</label>
|
||||
<input class="uk-input" type="text" bind:value={username} />
|
||||
<label class="uk-form-label" for="form-stacked-text">Password</label>
|
||||
<input class="uk-input" type="password" bind:value={password} />
|
||||
<label class="uk-form-label" for="form-stacked-text">Access Levels</label>
|
||||
<select multiple bind:value={accessLevels}>
|
||||
{#each $store.accessLevels.levels as level}
|
||||
<option value={level.name}>{level.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<div class="uk-margin">
|
||||
<label class="uk-form-label" for="form-stacked-text">Username</label>
|
||||
<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>
|
||||
<input class="uk-input" type="password" bind:value={password} />
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<ActionButton alert on:click={onClosed}>Cancel</ActionButton>
|
||||
|
@ -56,10 +46,4 @@
|
|||
background: #fafafa;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
select {
|
||||
width: 100%;
|
||||
}
|
||||
option {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -6,10 +6,7 @@
|
|||
export let record
|
||||
export let onClosed
|
||||
|
||||
$: currentAppInfo = {
|
||||
appname: $store.appname,
|
||||
instanceId: $backendUiStore.selectedDatabase.id,
|
||||
}
|
||||
$: instanceId = $backendUiStore.selectedDatabase.id
|
||||
</script>
|
||||
|
||||
<section>
|
||||
|
@ -28,7 +25,7 @@
|
|||
<ActionButton
|
||||
alert
|
||||
on:click={async () => {
|
||||
await api.deleteRecord(record, currentAppInfo)
|
||||
await api.deleteRecord(record, instanceId)
|
||||
backendUiStore.actions.records.delete(record)
|
||||
onClosed()
|
||||
}}>
|
||||
|
|
|
@ -1,66 +1,33 @@
|
|||
<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
|
||||
export let field
|
||||
export let errors
|
||||
let checked = type === "checkbox" ? value : false
|
||||
|
||||
$: isDropdown =
|
||||
field.type === "string" &&
|
||||
field.typeOptions.values &&
|
||||
field.typeOptions.values.length > 0
|
||||
const handleInput = event => {
|
||||
if (event.target.type === "checkbox") {
|
||||
value = event.target.checked
|
||||
return
|
||||
}
|
||||
|
||||
$: isNumber = field.type === "number"
|
||||
if (event.target.type === "number") {
|
||||
value = parseInt(event.target.value)
|
||||
return
|
||||
}
|
||||
|
||||
$: isText = field.type === "string" && !isDropdown
|
||||
|
||||
$: isCheckbox = field.type === "bool"
|
||||
|
||||
$: isError = errors && errors.some(e => e.field && e.field === field.name)
|
||||
|
||||
$: isDatetime = field.type === "datetime"
|
||||
value = event.target.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="uk-margin">
|
||||
{#if !isCheckbox}
|
||||
<label class="uk-form-label" for={field.name}>{field.label}</label>
|
||||
{/if}
|
||||
<div class="uk-form-controls">
|
||||
{#if isDropdown}
|
||||
<Select bind:value={record[field.name]}>
|
||||
<option value="" />
|
||||
{#each field.typeOptions.values as val}
|
||||
<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>
|
||||
<label>{label}</label>
|
||||
<input
|
||||
class={className}
|
||||
class:uk-form-danger={errors.length > 0}
|
||||
{checked}
|
||||
{type}
|
||||
{value}
|
||||
on:input={handleInput}
|
||||
on:change={handleInput} />
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export { default as DeleteRecordModal } from "./DeleteRecord.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 CreateDatabaseModal } from "./CreateDatabase.svelte"
|
||||
export { default as CreateUserModal } from "./CreateUser.svelte"
|
||||
|
|
|
@ -61,10 +61,6 @@
|
|||
|
||||
</div>
|
||||
{/if}
|
||||
<NavItem
|
||||
name="ACCESS_LEVELS"
|
||||
label="User Access Levels"
|
||||
href="./accesslevels" />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -6,16 +6,13 @@
|
|||
import { CheckIcon } from "../common/Icons"
|
||||
|
||||
$: instances = $store.appInstances
|
||||
$: views = $store.hierarchy.indexes
|
||||
|
||||
async function selectDatabase(database) {
|
||||
backendUiStore.actions.records.select(null)
|
||||
backendUiStore.actions.views.select(views[0])
|
||||
backendUiStore.actions.database.select(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)
|
||||
store.update(state => {
|
||||
state.appInstances = state.appInstances.filter(
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
CreateEditModelModal,
|
||||
CreateEditViewModal,
|
||||
} from "components/database/ModelDataTable/modals"
|
||||
import api from "builderStore/api"
|
||||
|
||||
const { open, close } = getContext("simple-modal")
|
||||
|
||||
export let level = 0
|
||||
export let node
|
||||
export let type
|
||||
export let onSelect
|
||||
|
||||
let navActive = ""
|
||||
|
||||
|
@ -20,47 +21,25 @@
|
|||
index: "ri-eye-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>
|
||||
|
||||
<div>
|
||||
<div
|
||||
on:click={() => selectHierarchyItem(node)}
|
||||
on:click={() => onSelect(node)}
|
||||
class="budibase__nav-item hierarchy-item"
|
||||
class:capitalized={type === 'model'}
|
||||
style="padding-left: {20 + level * 20}px"
|
||||
class:selected={navActive}>
|
||||
class:selected={$backendUiStore.selectedView === `all_${node._id}`}>
|
||||
<i class={ICON_MAP[type]} />
|
||||
<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>
|
||||
{#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>
|
||||
|
||||
<style>
|
||||
|
@ -68,6 +47,7 @@
|
|||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
margin-bottom: 10px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.capitalized {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
import { getContext, onMount } from "svelte"
|
||||
import { store, backendUiStore } from "builderStore"
|
||||
import HierarchyRow from "./HierarchyRow.svelte"
|
||||
import DropdownButton from "components/common/DropdownButton.svelte"
|
||||
import NavItem from "./NavItem.svelte"
|
||||
import getIcon from "components/common/icon"
|
||||
import api from "builderStore/api"
|
||||
import {
|
||||
CreateEditModelModal,
|
||||
CreateEditViewModal,
|
||||
|
@ -12,12 +12,18 @@
|
|||
|
||||
const { open, close } = getContext("simple-modal")
|
||||
|
||||
function editModel() {
|
||||
open(
|
||||
CreateEditModelModal,
|
||||
{
|
||||
model: node,
|
||||
onClosed: close,
|
||||
},
|
||||
{ styleContent: { padding: "0" } }
|
||||
)
|
||||
}
|
||||
|
||||
function newModel() {
|
||||
if ($store.currentNode) {
|
||||
store.newChildModel()
|
||||
} else {
|
||||
store.newRootModel()
|
||||
}
|
||||
open(
|
||||
CreateEditModelModal,
|
||||
{
|
||||
|
@ -28,7 +34,6 @@
|
|||
}
|
||||
|
||||
function newView() {
|
||||
store.newRootIndex()
|
||||
open(
|
||||
CreateEditViewModal,
|
||||
{
|
||||
|
@ -37,32 +42,66 @@
|
|||
{ 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>
|
||||
|
||||
<div class="items-root">
|
||||
<div class="hierarchy">
|
||||
<div class="components-list-container">
|
||||
<div class="nav-group-header">
|
||||
<div class="hierarchy-title">Schema</div>
|
||||
<div class="hierarchy-title">Models</div>
|
||||
<div class="uk-inline">
|
||||
<i class="ri-add-line hoverable" />
|
||||
<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>
|
||||
<i class="ri-add-line hoverable" on:click={newModel} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hierarchy-items-container">
|
||||
{#each $store.hierarchy.children as model}
|
||||
<HierarchyRow node={model} type="model" />
|
||||
{#each $backendUiStore.models as model}
|
||||
<HierarchyRow onSelect={selectModel} node={model} type="model" />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#each $store.hierarchy.indexes as index}
|
||||
<HierarchyRow node={index} type="index" />
|
||||
<div class="hierarchy">
|
||||
<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}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -10,17 +10,15 @@
|
|||
return { name, props }
|
||||
}
|
||||
|
||||
let users = []
|
||||
|
||||
$: currentAppInfo = {
|
||||
appname: $store.appname,
|
||||
instanceId: $backendUiStore.selectedDatabase.id,
|
||||
}
|
||||
|
||||
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)
|
||||
users = await response.json()
|
||||
const users = await response.json()
|
||||
backendUiStore.update(state => {
|
||||
state.users = users
|
||||
return state
|
||||
|
@ -32,7 +30,7 @@
|
|||
|
||||
<div class="root">
|
||||
<ul>
|
||||
{#each users as user}
|
||||
{#each $backendUiStore.users as user}
|
||||
<li>
|
||||
<i class="ri-user-4-line" />
|
||||
<button class:active={user.id === $store.currentUserId}>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<div>
|
||||
<h4 style="margin-bottom: 20px">Choose an Application</h4>
|
||||
{#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}
|
||||
</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>
|
||||
import PropsView from "./PropsView.svelte"
|
||||
import StateBindingControl from "./StateBindingControl.svelte"
|
||||
import { store } from "builderStore"
|
||||
import IconButton from "components/common/IconButton.svelte"
|
||||
import {
|
||||
|
@ -30,11 +29,10 @@
|
|||
? getProps($store.currentPreviewItem, ["name", "favicon"])
|
||||
: getProps($store.currentPreviewItem, ["name", "description", "route"])
|
||||
|
||||
const onPropChanged = store.setComponentProp
|
||||
const onStyleChanged = store.setComponentStyle
|
||||
|
||||
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>
|
||||
|
||||
|
@ -89,14 +87,14 @@
|
|||
on:input={({ target }) => store.setMetadataProp(k, target.value)} />
|
||||
</div>
|
||||
{/each}
|
||||
<PropsView {component} {components} {onPropChanged} />
|
||||
<PropsView {component} {components} />
|
||||
{:else}
|
||||
<PropsView {component} {components} {onPropChanged} />
|
||||
<PropsView {component} {components} />
|
||||
{/if}
|
||||
{:else if current_view === 'layout'}
|
||||
<LayoutEditor {onStyleChanged} {component} />
|
||||
{:else if current_view === 'events'}
|
||||
<EventsEditor {component} {components} {onPropChanged} />
|
||||
<EventsEditor {component} {components} />
|
||||
{/if}
|
||||
|
||||
<CodeEditor
|
||||
|
|
|
@ -13,12 +13,7 @@
|
|||
flatten,
|
||||
} from "lodash/fp"
|
||||
|
||||
import {
|
||||
getRecordNodes,
|
||||
getIndexNodes,
|
||||
getIndexSchema,
|
||||
pipe,
|
||||
} from "components/common/core"
|
||||
import { pipe } from "components/common/core"
|
||||
|
||||
import Tab from "./ItemTab/Tab.svelte"
|
||||
import { store } from "builderStore"
|
||||
|
@ -34,54 +29,10 @@
|
|||
const categories = components.categories
|
||||
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 => {
|
||||
if (component.template) {
|
||||
onTemplateChosen(component.template)
|
||||
} else {
|
||||
store.addChildComponent(component._component)
|
||||
toggleTab()
|
||||
}
|
||||
}
|
||||
|
||||
const onTemplateInstanceChosen = () => {
|
||||
selectedComponent = null
|
||||
const instance = templateInstances.find(
|
||||
i => i.name === selectedTemplateInstance
|
||||
)
|
||||
store.addTemplatedComponent(instance.props)
|
||||
store.addChildComponent(component._component)
|
||||
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>
|
||||
|
||||
<div class="root">
|
||||
|
@ -98,30 +49,10 @@
|
|||
<Tab
|
||||
list={selectedCategory}
|
||||
on:selectItem={e => onComponentChosen(e.detail)}
|
||||
{onTemplateChosen}
|
||||
{toggleTab} />
|
||||
</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>
|
||||
.tabs {
|
||||
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 isFolderSelected = (current, folder) => isInSubfolder(current, folder)
|
||||
|
||||
$: _screens = pipe(screens, [
|
||||
map(c => ({ component: c, title: lastPartOfName(c) })),
|
||||
sortBy("title"),
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { goto } from "@sveltech/routify"
|
||||
import { store } from "builderStore"
|
||||
import { last } from "lodash/fp"
|
||||
import { pipe } from "../common/core"
|
||||
import { pipe } from "components/common/core"
|
||||
import {
|
||||
XCircleIcon,
|
||||
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>
|