@ -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 |
@ -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,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,55 +1,31 @@ |
|||
import { flatten, values, uniq, map } from "lodash/fp" |
|||
import { pipe } from "components/common/core" |
|||
|
|||
export const loadLibs = async (appName, appPackage) => { |
|||
const allLibraries = {} |
|||
|
|||
for (let lib of libsFromPages(appPackage.pages)) { |
|||
const libModule = await import(makeLibraryUrl(appName, lib)) |
|||
allLibraries[lib] = libModule |
|||
/** |
|||
* 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) |
|||
} |
|||
|
|||
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}` |
|||
} |
|||
/** |
|||
* 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 = {} |
|||
|
|||
return { |
|||
importPath: `/lib${newPath}/${file}`, |
|||
libName: path, |
|||
} |
|||
for (let libraryName of application.componentLibraries) { |
|||
const LIBRARY_URL = `/${application._id}/componentlibrary?library=${libraryName}` |
|||
const libraryModule = await import(LIBRARY_URL) |
|||
allLibraries[libraryName] = libraryModule |
|||
} |
|||
|
|||
return pipe([appPackage.pages[pageName]], [libsFromPages, map(resolve)]) |
|||
return allLibraries |
|||
} |
|||
|
|||
@ -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> |
|||
@ -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> |
|||
|
Before Width: | Height: | Size: 228 B After Width: | Height: | Size: 196 B |
@ -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> |
|||
@ -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> |
|||
@ -1,66 +1,33 @@ |
|||
<script> |
|||
import Select from "../../../common/Select.svelte" |
|||
|
|||
export let record |
|||
export let field |
|||
export let errors |
|||
|
|||
$: isDropdown = |
|||
field.type === "string" && |
|||
field.typeOptions.values && |
|||
field.typeOptions.values.length > 0 |
|||
|
|||
$: isNumber = field.type === "number" |
|||
|
|||
$: isText = field.type === "string" && !isDropdown |
|||
|
|||
$: isCheckbox = field.type === "bool" |
|||
|
|||
$: isError = errors && errors.some(e => e.field && e.field === field.name) |
|||
|
|||
$: isDatetime = field.type === "datetime" |
|||
export let type = "text" |
|||
export let value = "" |
|||
export let label |
|||
export let errors = [] |
|||
export let className = "uk-input" |
|||
|
|||
let checked = type === "checkbox" ? value : false |
|||
|
|||
const handleInput = event => { |
|||
if (event.target.type === "checkbox") { |
|||
value = event.target.checked |
|||
return |
|||
} |
|||
|
|||
if (event.target.type === "number") { |
|||
value = parseInt(event.target.value) |
|||
return |
|||
} |
|||
|
|||
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" |
|||
|
|||
@ -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,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> |
|||
@ -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> |
|||