data binding and stndard componets work

This commit is contained in:
Michael Shanks 2019-09-19 04:35:40 +01:00
parent 346ec5c4da
commit 70ddecfab0
32 changed files with 21038 additions and 915 deletions

View File

@ -1,5 +1,3 @@
import { isUndefined } from "lodash/fp";
const apiCall = (method) => (url, body) => const apiCall = (method) => (url, body) =>
fetch(url, { fetch(url, {
method: method, method: method,

View File

@ -3,7 +3,8 @@ import {
isBoolean, isBoolean,
isNumber, isNumber,
isArray, isArray,
isObjectLike isObjectLike,
some
} from "lodash/fp"; } from "lodash/fp";
const defaultDef = typeName => () => ({ const defaultDef = typeName => () => ({
@ -47,6 +48,14 @@ export const expandPropsDefinition = propsDefinition => {
} }
const isComponent = isObjectLike; const isComponent = isObjectLike;
const isEvent = e =>
isObjectLike(e)
&& e.handlerType && isString(e.handlerType)
&& e.parameters && isArray(e.parameters);
const isEventList = e =>
isArray(e) && !some(ev => !isEvent(ev));
export const types = { export const types = {
string: propType(() => "", isString, defaultDef("string")), string: propType(() => "", isString, defaultDef("string")),
@ -56,4 +65,5 @@ export const types = {
options: propType(() => "", isString, defaultDef("options")), options: propType(() => "", isString, defaultDef("options")),
component: propType(() => ({_component:""}), isComponent, defaultDef("component")), component: propType(() => ({_component:""}), isComponent, defaultDef("component")),
asset: propType(() => "", isString, defaultDef("asset")), asset: propType(() => "", isString, defaultDef("asset")),
event: propType(() => [], isEventList, defaultDef("event"))
}; };

View File

@ -103,6 +103,18 @@ describe("createDefaultProps", () => {
expect(props.columns).toEqual([]); expect(props.columns).toEqual([]);
}); });
it("should create a object with single empty array, when prop definition is 'event' ", () => {
const propDef = {
onClick: "event"
};
const { props, errors } = createProps("some_component",propDef);
expect(errors).toEqual([]);
expect(props.onClick).toBeDefined();
expect(props.onClick).toEqual([]);
});
it("should create a object with single empty component props, when prop definition is 'component' ", () => { it("should create a object with single empty component props, when prop definition is 'component' ", () => {
const propDef = { const propDef = {
content: "component" content: "component"

View File

@ -3,7 +3,7 @@
"version": "0.0.3", "version": "0.0.3",
"license": "MPL-2.0", "license": "MPL-2.0",
"main": "dist/budibase-client.js", "main": "dist/budibase-client.js",
"module": "dist/budibase-client.js", "module": "dist/budibase-client.esm.mjs",
"scripts": { "scripts": {
"build": "rollup -c", "build": "rollup -c",
"test": "jest", "test": "jest",

View File

@ -26,12 +26,19 @@ const coreExternal = [
export default { export default {
input: 'src/index.js', input: 'src/index.js',
output: { output: [
{
sourcemap: true, sourcemap: true,
format: 'iife', format: 'iife',
name: 'app', name: 'app',
file: `./dist/budibase-client.js` file: `./dist/budibase-client.js`
}, },
{
file: 'dist/budibase-client.esm.mjs',
format: 'esm',
sourcemap: 'inline'
}
],
plugins: [ plugins: [
resolve({ resolve({

View File

@ -0,0 +1,46 @@
import {
split,
last
} from "lodash/fp";
import {writable} from "svelte/store";
import { $ } from "./core/common";
import { bindComponent } from "./stateBinding";
export const createApp = componentLibraries => {
const initialiseComponent = (props, htmlElement) => {
const {componentName, libName} = splitName(props._component);
const component = new (componentLibraries[libName][componentName])({
target: htmlElement,
props: {...props, _app}
});
bindComponent(store, component);
}
const store = writable({});
const _app = {
initialiseComponent,
store
};
return _app;
}
const splitName = fullname => {
const componentName = $(fullname, [
split("/"),
last
]);
const libName =fullname.substring(
0, fullname.length - componentName.length - 1);
return {libName, componentName};
}

View File

@ -1,5 +1,25 @@
import { initialise } from "./initialise"; import { createApp } from "./createApp";
const appDefinition = window["##BUDIBASE_APPDEFINITION##"]; const appDefinition = window["##BUDIBASE_APPDEFINITION##"];
initialise(window.document, appDefinition); const componentLibraryUrl = (lib) => "./" + trimSlash(lib)
const trimSlash = (str) => str.replace(/^\/+|\/+$/g, '');
(async () => {
const componentLibraries = {};
for(let lib of appDefinition.componentLibraries) {
componentLibraries[lib.libName] = await import(
componentLibraryUrl(lib.importPath));
}
const _app = createApp(componentLibraries);
_app.initialiseComponent(
props,
document.body);
})();

View File

@ -1,22 +0,0 @@
import { writable } from "svelte/store";
import { initialiseComponent } from "./initialiseComponent";
export const initialise = async (document, appDefinition) => {
const componentLibraries = {};
for(let lib of appDefinition.componentLibraries) {
componentLibraries[lib.libName] = await import(
componentLibraryUrl(lib.importPath));
}
const store = writable({});
initialiseComponent(componentLibraries, store)(
appDefinition.props,
document.body);
}
const componentLibraryUrl = (lib) => "./" + trimSlash(lib)
const trimSlash = (str) => str.replace(/^\/+|\/+$/g, '');

View File

@ -1,36 +0,0 @@
import {
find,
isUndefined,
split,
last
} from "lodash/fp";
import { $ } from "./core/common";
export const initialiseComponent = (componentLibraries, store) => (props, htmlElement) => {
const _app = {
initialiseComponent: initialiseComponent(componentLibraries, store),
store
};
const {componentName, libName} = splitName(props._component);
new (componentLibraries[libName][componentName])({
target: htmlElement,
props: {...props, _app}
});
}
const splitName = fullname => {
const componentName = $(fullname, [
split("/"),
last
]);
const libName =fullname.substring(
0, fullname.length - componentName.length - 1);
return {libName, componentName};
}

View File

@ -0,0 +1,59 @@
export const BB_STATE_INDICATOR = "##bbstate";
export const BB_STATE_FALLBACK = "##bbstatefallback";
export const bindComponent = (store, component) => {
const newProps = {...component.props};
const boundProps = [];
for(let propName in component.props) {
const val = newProps[propName];
if(!isState(val)) continue;
const binding = stateBinding(val);
const fallback = stateFallback(val);
boundProps.push({
stateBinding:binding,
fallback, propName
});
}
if(boundProps.length === 0) return;
const unsubscribe = store.subscribe(s => {
const newProps = {...component.props};
for(let boundProp of boundProps) {
const val = boundValueFromStore(
s,
boundProp.stateBinding,
boundProp.fallback);
if(val === undefined && newProps[boundProp.propName] !== undefined) {
delete newProps[boundProp.propName];
}
if(val !== undefined) {
newProps[boundProp.propName] = val;
}
}
component.$set(newProps);
});
return unsubscribe;
}
const isState = (prop) => prop[BB_STATE_INDICATOR] !== undefined;
const stateBinding = (prop) => prop[BB_STATE_INDICATOR];
const stateFallback = (prop) => prop[BB_STATE_FALLBACK];
const boundValueFromStore = (s, binding, fallback) => {
const value = s[binding];
if(value === undefined) return fallback;
return value;
}

View File

@ -7,6 +7,13 @@
<title>{{ title }}</title> <title>{{ title }}</title>
<link rel='icon' type='image/png' href='{{ favicon }}'> <link rel='icon' type='image/png' href='{{ favicon }}'>
<style>
html, body {
height: 100%;
width: 100%;
}
</style>
{{ each(options.stylesheets) }} {{ each(options.stylesheets) }}
<link rel='stylesheet' href='{{ @this }}'> <link rel='stylesheet' href='{{ @this }}'>
{{ /each }} {{ /each }}

View File

@ -13,7 +13,7 @@
"tags": ["button"] "tags": ["button"]
}, },
"login" : { "login" : {
"importPath": "login", "importPath": "Login",
"name": "Login Control", "name": "Login Control",
"desciption": "A control that accepts username, password an also handles password resets", "desciption": "A control that accepts username, password an also handles password resets",
"props" : { "props" : {
@ -21,33 +21,21 @@
"loginRedirect": "string", "loginRedirect": "string",
"usernameLabel": {"type":"string", "default": "Username"}, "usernameLabel": {"type":"string", "default": "Username"},
"passwordLabel": {"type":"string", "default": "Password"}, "passwordLabel": {"type":"string", "default": "Password"},
"loginButtonLabel": {"type":"string", "default": "Login"} "loginButtonLabel": {"type":"string", "default": "Login"},
"buttonClass": "string"
}, },
"tags": ["login", "credentials", "password", "logon"] "tags": ["login", "credentials", "password", "logon"]
}, },
"formControl" : {
"importPath": "formControl",
"name": "Form Control",
"desciption": "A wrapper for a control, used inside a form. Allows a label, and properly alligns the control inside the parent form",
"props" : {
"containerClass": "string",
"labelContainerClass": "string",
"controlContainerClass": "string",
"label": "string",
"control": "component",
"fullWidth": "bool"
},
"tags": ["login"]
},
"form" : { "form" : {
"importPath": "form", "importPath": "Form",
"name": "Form", "name": "Form",
"desciption": "A form - you should usually add FormControls as children to get nice allignment", "desciption": "A form - allgned fields with labels",
"props" : { "props" : {
"containerClass": "string", "containerClass": "string",
"formControls": { "formControls": {
"type":"array", "type":"array",
"elementDefinition": { "elementDefinition": {
"label": "string",
"control":"component" "control":"component"
} }
} }
@ -55,7 +43,7 @@
"tags": ["form"] "tags": ["form"]
}, },
"textbox" : { "textbox" : {
"importPath": "textbox", "importPath": "Textbox",
"name": "Textbox", "name": "Textbox",
"desciption": "An input type=text or password", "desciption": "An input type=text or password",
"props" : { "props" : {
@ -66,7 +54,7 @@
"tags": ["form"] "tags": ["form"]
}, },
"stackpanel": { "stackpanel": {
"importPath": "stackpanel", "importPath": "StackPanel",
"name": "StackPanel", "name": "StackPanel",
"desciption": "Layout elements in a stack, either horizontally or vertically", "desciption": "Layout elements in a stack, either horizontally or vertically",
"props" : { "props" : {
@ -87,5 +75,53 @@
"itemContainerClass":"string" "itemContainerClass":"string"
}, },
"tags": ["div", "container", "layout", "panel"] "tags": ["div", "container", "layout", "panel"]
},
"grid": {
"importPath": "Grid",
"name": "Grid",
"desciption": "CSS Grid layout ",
"props" : {
"gridTemplateRows": "string",
"gridTemplateColumns": "string",
"children": {
"type":"array",
"elementDefinition": {
"control":"component",
"gridColumn":"string",
"gridRow":"string",
"gridColumnStart":"string",
"gridColumnEnd":"string",
"gridRowStart":"string",
"gridRowEnd":"string"
}
},
"width": {"type":"string","default":"auto"},
"height": {"type":"string","default":"auto"},
"containerClass":"string",
"itemContainerClass":"string"
},
"tags": ["div", "container", "layout", "panel", "grid"]
},
"text": {
"importPath": "Text",
"name": "Text",
"desciption": "A div with text inside ",
"props" : {
"value": "string",
"containerClass": "string",
"background": "string",
"border": "string",
"font": "string",
"color": "string",
"padding": "string",
"display": {
"type": "options",
"default":"inline",
"options": [
"inline", "block", "inline-block"
]
}
},
"tags": ["div", "container"]
} }
} }

View File

@ -11,15 +11,19 @@
"start:dev": "sirv public --single --dev" "start:dev": "sirv public --single --dev"
}, },
"devDependencies": { "devDependencies": {
"@budibase/client": "*",
"lodash": "^4.17.15",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"rollup": "^1.11.0", "rollup": "^1.11.0",
"rollup-plugin-commonjs": "^10.0.2", "rollup-plugin-commonjs": "^10.0.2",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-livereload": "^1.0.1", "rollup-plugin-livereload": "^1.0.1",
"rollup-plugin-node-resolve": "^5.0.0", "rollup-plugin-node-resolve": "^5.0.0",
"rollup-plugin-svelte": "^5.0.0", "rollup-plugin-svelte": "^5.0.0",
"rollup-plugin-terser": "^5.1.1", "rollup-plugin-terser": "^5.1.1",
"shortid": "^2.2.15",
"sirv-cli": "^0.4.4", "sirv-cli": "^0.4.4",
"svelte": "^3.0.0" "svelte": "^3.12.1"
}, },
"keywords": [ "keywords": [
"svelte" "svelte"

View File

@ -1,9 +1,9 @@
.root.svelte-1s3350l{height:100%;display:grid;grid-template-columns:[left] 1fr [middle] auto [right] 1fr;grid-template-rows:[top] 1fr [center] auto [bottom] 1fr}.content.svelte-1s3350l{grid-column-start:middle;grid-row-start:center;width:400px}.logo-container.svelte-1s3350l{margin-bottom:20px #current_component.svelte-1xqz9vm{height:100%;width:100%}
}.logo-container.svelte-1s3350l>img.svelte-1s3350l{max-width:100%}.login-button-container.svelte-1s3350l{text-align:right;margin-top:20px} .root.svelte-1oto99m{height:100%;display:grid;grid-template-columns:[left] 1fr [middle] auto [right] 1fr;grid-template-rows:[top] 1fr [center] auto [bottom] 1fr}.content.svelte-1oto99m{grid-column-start:middle;grid-row-start:center;width:400px}.logo-container.svelte-1oto99m{margin-bottom:20px
.label.svelte-98bu7e{grid-column-start:label;padding:5px 10px;vertical-align:middle}.control.svelte-98bu7e{grid-column-start:control;padding:5px 10px}.overflow.svelte-98bu7e{grid-column-start:overflow}.full-width.svelte-98bu7e{width:100%} }.logo-container.svelte-1oto99m>img.svelte-1oto99m{max-width:100%}.login-button-container.svelte-1oto99m{text-align:right;margin-top:20px}.incorrect-details-panel.svelte-1oto99m{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-1oto99m{display:grid;grid-template-columns:[label] auto [control] 1fr}.label.svelte-1oto99m{grid-column-start:label;padding:5px 10px;vertical-align:middle}.control.svelte-1oto99m{grid-column-start:control;padding:5px 10px}
.form-root.svelte-h706oz{display:grid;grid-template-columns:[label] auto [control] auto} .root.svelte-10kw8to{display:grid}
.current.svelte-cgpppc{height:100%;width:100%} .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}
input.svelte-bmvn6x{width:100%}input.svelte-bmvn6x{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%}input.svelte-bmvn6x:disabled{color:#ccc} .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%}
button.svelte-19ku4ig{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}button.svelte-19ku4ig{color:#333;background-color:#f4f4f4;outline:none}button.svelte-19ku4ig:active{background-color:#ddd}button.svelte-19ku4ig:focus{border-color:#666} .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 */ /*# sourceMappingURL=bundle.css.map */

View File

@ -2,21 +2,21 @@
"version": 3, "version": 3,
"file": "bundle.css", "file": "bundle.css",
"sources": [ "sources": [
"..\\src\\Login.svelte",
"..\\src\\FormControl.svelte",
"..\\src\\Form.svelte",
"..\\src\\Test\\TestApp.svelte", "..\\src\\Test\\TestApp.svelte",
"..\\src\\Login.svelte",
"..\\src\\Grid.svelte",
"..\\src\\Textbox.svelte", "..\\src\\Textbox.svelte",
"..\\src\\Form.svelte",
"..\\src\\Button.svelte" "..\\src\\Button.svelte"
], ],
"sourcesContent": [ "sourcesContent": [
"<script>\r\n\r\nimport Textbox from \"./Textbox.svelte\";\r\nimport FormControl from \"./FormControl.svelte\";\r\nimport Form from \"./Form.svelte\";\r\nimport Button from \"./Button.svelte\";\r\n\r\nexport let usernameLabel = \"Username\";\r\nexport let passwordLabel = \"Password\";\r\nexport let loginRedirect = \"\";\r\nexport let logo = \"/budibase-logo.png\";\r\n\r\nlet username = \"\";\r\nlet password = \"\";\r\n\r\n</script>\r\n\r\n<div class=\"root\">\r\n\r\n <div class=\"content\">\r\n\r\n <div class=\"logo-container\">\r\n <img src={logo} alt=\"logo\"/>\r\n </div>\r\n\r\n <Form>\r\n <FormControl label={usernameLabel}>\r\n <Textbox bind:value={username} />\r\n </FormControl>\r\n <FormControl label={passwordLabel}>\r\n <Textbox bind:value={password} hideValue=true />\r\n </FormControl>\r\n </Form>\r\n\r\n <div class=\"login-button-container\">\r\n <Button>Login</Button>\r\n </div>\r\n\r\n </div>\r\n\r\n</div>\r\n\r\n<style>\r\n\r\n.root {\r\n height: 100%;\r\n display:grid;\r\n grid-template-columns: [left] 1fr [middle] auto [right] 1fr;\r\n grid-template-rows: [top] 1fr [center] auto [bottom] 1fr;\r\n}\r\n\r\n.content {\r\n grid-column-start: middle;\r\n grid-row-start: center;\r\n width: 400px;\r\n}\r\n\r\n.logo-container {\r\n margin-bottom: 20px\r\n}\r\n\r\n.logo-container > img {\r\n max-width: 100%;\r\n}\r\n\r\n.login-button-container {\r\n text-align: right;\r\n margin-top: 20px;\r\n}\r\n\r\n</style>", "<script>\nimport createApp from \"./createApp\";\nimport { props } from \"./props\";\n\nlet _app;\n\nconst _appPromise = createApp();\n_appPromise.then(a => _app = a);\n\nconst testProps = props.grid;\n\nlet currentComponent;\n\n$: {\n if(_app && currentComponent) {\n _app.initialiseComponent(testProps, currentComponent);\n }\n}\n\n\n\n</script>\n\n{#await _appPromise}\nloading\n{:then _app}\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\n\r\nexport let containerClass = \"\";\r\nexport let labelContainerClass = \"\";\r\nexport let controlContainerClass = \"\";\r\nexport let label = \"\";\r\nexport let control;\r\nexport let overflowControl;\r\nexport let fullWidth = false;\r\n\r\n</script>\r\n\r\n<div class=\"label {labelContainerClass}\">{label}</div>\r\n<div class=\"control {controlContainerClass}\" class:full-width={fullWidth}>\r\n {#if control}\r\n <control />\r\n {:else}\r\n <slot />\r\n {/if}\r\n \r\n</div>\r\n<!--div class=\"overflow\">\r\n {#if overflowControl}\r\n <overflowControl />\r\n {:else}\r\n <slot name=\"overflow\" />\r\n {/if}\r\n</div>-->\r\n\r\n<style>\r\n\r\n.label {\r\n grid-column-start: label;\r\n padding: 5px 10px;\r\n vertical-align: middle;\r\n}\r\n.control {\r\n grid-column-start: control;\r\n padding: 5px 10px;\r\n}\r\n.overflow {\r\n grid-column-start: overflow;\r\n}\r\n.full-width {\r\n width: 100%;\r\n}\r\n</style>", "<script>\n\nimport Textbox from \"./Textbox.svelte\";\nimport Form from \"./Form.svelte\";\nimport Button from \"./Button.svelte\";\nimport { authenticate } from \"./api\";\n\nexport let usernameLabel = \"Username\";\nexport let passwordLabel = \"Password\";\nexport let loginButtonLabel = \"Login\";\nexport let loginRedirect = \"\";\nexport let logo = \"\";\nexport let buttonClass = \"\";\n\nlet username = \"\";\nlet password = \"\";\nlet busy = false;\nlet incorrect = false;\n\nconst login = () => {\n busy = true;\n authenticate(username, password)\n .then(r => {\n busy = false;\n if(r.status === 200) {\n // reload page\n } else {\n incorrect = true;\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 <Textbox bind:value={username} />\n </div>\n <div class=\"label\">\n {passwordLabel}\n </div>\n <div class=\"control\">\n <Textbox bind:value={password} hideValue=true />\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</style>",
"<script>\r\n\r\nexport let containerClass = \"\";\r\nexport let formControls = [];\r\n\r\n</script>\r\n\r\n<div class=\"form-root {containerClass}\">\r\n {#each formControls as formControl}\r\n <formControl />\r\n {:else}\r\n <slot/>\r\n {/each}\r\n</div>\r\n\r\n<style>\r\n.form-root {\r\n display: grid;\r\n grid-template-columns: [label] auto [control] auto; /* [overflow] auto;*/\r\n}\r\n</style>", "<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 _app;\r\n\r\nlet style=\"\";\r\nlet htmlElements = {};\r\n\r\n$ : {\r\n if(_app && htmlElements) {\r\n for(let el in htmlElements) {\r\n _app.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>\r\nimport Login from \"../Login.svelte\";\r\n</script>\r\n\r\n\r\n<div class=\"current\">\r\n <Login />\r\n</div>\r\n\r\n<style>\r\n.current {\r\n height: 100%;\r\n width: 100%;\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 _app;\n\nlet actualValue = \"\";\n$: {\n\tif(_app && value._isstate) {\n\t\t_app.store.subscribe(s => {\n\t\t\tactualValue = _app.store.getValue(s, value);\n\t\t});\n\t}\n}\n\nconst onchange = (ev) => {\n\tif(_app && value._isstate) {\n\t\t_app.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\n\r\nexport let value=\"\";\r\nexport let hideValue = false;\r\n\r\n</script>\r\n\r\n{#if hideValue}\r\n<input type=\"password\" bind:value={value}/>\r\n{:else}\r\n<input type=\"text\" bind:value={value}/>\r\n{/if}\r\n\r\n<style>\r\ninput {\r\n width: 100%;\r\n}\r\n\r\ninput {\r\n\tfont-family: inherit;\r\n\tfont-size: inherit;\r\n\tpadding: 0.4em;\r\n\tmargin: 0 0 0.5em 0;\r\n\tbox-sizing: border-box;\r\n\tborder: 1px solid #ccc;\r\n border-radius: 2px;\r\n width: 100%;\r\n}\r\n\r\ninput:disabled {\r\n\tcolor: #ccc;\r\n}\r\n\r\n</style>", "<script>\nexport let containerClass = \"\";\nexport let formControls = [];\n\nexport let _app;\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(_app && htmlElements) {\n for(let el in htmlElements) {\n _app.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\nexport let className = \"\";\r\nexport let disabled = false;\r\nexport let contentText;\r\nexport let contentComponent;\r\n</script>\r\n\r\n\r\n<button class={className} {disabled} on:click>\r\n {#if contentComponent}\r\n {contentComponent}\r\n {:else if contentText}\r\n {contentText}\r\n {:else}\r\n <slot />\r\n {/if}\r\n</button>\r\n\r\n\r\n<style>\r\n\r\nbutton {\r\n\tfont-family: inherit;\r\n\tfont-size: inherit;\r\n\tpadding: 0.4em;\r\n\tmargin: 0 0 0.5em 0;\r\n\tbox-sizing: border-box;\r\n\tborder: 1px solid #ccc;\r\n\tborder-radius: 2px;\r\n}\r\n\r\n\r\nbutton {\r\n\tcolor: #333;\r\n\tbackground-color: #f4f4f4;\r\n\toutline: none;\r\n}\r\n\r\nbutton:active {\r\n\tbackground-color: #ddd;\r\n}\r\n\r\nbutton:focus {\r\n\tborder-color: #666;\r\n}\r\n\r\n</style>" "<script>\nexport let className = \"default\";\nexport let disabled = false;\nexport let contentText;\nexport let contentComponent;\n\nexport let _app;\nlet contentComponentContainer;\n\n$:{\n\tif(_app && contentComponentContainer)\n\t\t_app.initialiseComponent(contentComponent, contentComponentContainer);\n}\n\n</script>\n\n\n<button class={className} {disabled} on:click>\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": [], "names": [],
"mappings": "AA4CA,KAAK,eAAC,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,eAAC,CAAC,AACN,iBAAiB,CAAE,MAAM,CACzB,cAAc,CAAE,MAAM,CACtB,KAAK,CAAE,KAAK,AAChB,CAAC,AAED,eAAe,eAAC,CAAC,AACb,aAAa,CAAE,IAAI;AACvB,CAAC,AAED,8BAAe,CAAG,GAAG,eAAC,CAAC,AACnB,SAAS,CAAE,IAAI,AACnB,CAAC,AAED,uBAAuB,eAAC,CAAC,AACrB,UAAU,CAAE,KAAK,CACjB,UAAU,CAAE,IAAI,AACpB,CAAC;ACrCD,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;AC7BD,UAAU,cAAC,CAAC,AACR,OAAO,CAAE,IAAI,CACb,qBAAqB,CAAE,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,AACtD,CAAC;ACTD,QAAQ,cAAC,CAAC,AACN,MAAM,CAAE,IAAI,CACZ,KAAK,CAAE,IAAI,AACf,CAAC;ACCD,KAAK,cAAC,CAAC,AACH,KAAK,CAAE,IAAI,AACf,CAAC,AAED,KAAK,cAAC,CAAC,AACN,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,mBAAK,SAAS,AAAC,CAAC,AACf,KAAK,CAAE,IAAI,AACZ,CAAC;ACVD,MAAM,eAAC,CAAC,AACP,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,AACnB,CAAC,AAGD,MAAM,eAAC,CAAC,AACP,KAAK,CAAE,IAAI,CACX,gBAAgB,CAAE,OAAO,CACzB,OAAO,CAAE,IAAI,AACd,CAAC,AAED,qBAAM,OAAO,AAAC,CAAC,AACd,gBAAgB,CAAE,IAAI,AACvB,CAAC,AAED,qBAAM,MAAM,AAAC,CAAC,AACb,YAAY,CAAE,IAAI,AACnB,CAAC" "mappings": "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"
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
window['##BUDIBASE_APPDEFINITION##'] = {"hierarchy":{"name":"root","type":"root","children":[{"name":"customer","type":"record","fields":[{"name":"name","type":"string","typeOptions":{"maxLength":1000,"values":null,"allowDeclaredValuesOnly":false},"label":"name","getInitialValue":"default","getUndefinedValue":"default"}],"children":[{"name":"invoiceyooo","type":"record","fields":[{"name":"amount","type":"number","typeOptions":{"minValue":99999999999,"maxValue":99999999999,"decimalPlaces":2},"label":"amount","getInitialValue":"default","getUndefinedValue":"default"}],"children":[],"validationRules":[],"nodeId":2,"indexes":[],"allidsShardFactor":1,"collectionName":"invoices","isSingle":false}],"validationRules":[],"nodeId":1,"indexes":[{"name":"customer_invoices","type":"index","map":"return {...record};","filter":"","indexType":"ancestor","getShardName":"","getSortKey":"record.id","aggregateGroups":[],"allowedRecordNodeIds":[2],"nodeId":5}],"allidsShardFactor":64,"collectionName":"customers","isSingle":false}],"pathMaps":[],"indexes":[{"name":"Yeo index","type":"index","map":"return {...record};","filter":"","indexType":"ancestor","getShardName":"","getSortKey":"record.id","aggregateGroups":[],"allowedRecordNodeIds":[1],"nodeId":4},{"name":"everyones_invoices","type":"index","map":"return {...record};","filter":"","indexType":"ancestor","getShardName":"","getSortKey":"record.id","aggregateGroups":[],"allowedRecordNodeIds":[2],"nodeId":6}],"nodeId":0},"componentLibraries":["budibase-standard-components"],"appRootPath":"/testApp2","props":{}}

View File

@ -9,6 +9,7 @@
<link rel='icon' type='image/png' href='/favicon.png'> <link rel='icon' type='image/png' href='/favicon.png'>
<link rel='stylesheet' href='/global.css'> <link rel='stylesheet' href='/global.css'>
<link rel='stylesheet' href='/bundle.css'> <link rel='stylesheet' href='/bundle.css'>
</head> </head>
<body> <body>

View File

@ -3,9 +3,25 @@ import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs'; import commonjs from 'rollup-plugin-commonjs';
import livereload from 'rollup-plugin-livereload'; import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser'; import { terser } from 'rollup-plugin-terser';
import json from 'rollup-plugin-json';
const production = !process.env.ROLLUP_WATCH; const production = !process.env.ROLLUP_WATCH;
const lodash_fp_exports = ["union", "reduce", "isUndefined", "cloneDeep", "split", "some", "map", "filter", "isEmpty", "countBy", "includes", "last", "find", "constant",
"take", "first", "intersection", "mapValues", "isNull", "has", "isNumber", "isString", "isBoolean", "isDate", "isArray", "isObject", "clone", "values", "keyBy",
"keys", "orderBy", "concat", "reverse", "difference", "merge", "flatten", "each", "pull", "join", "defaultCase", "uniqBy", "every", "uniqWith", "isFunction", "groupBy",
"differenceBy", "intersectionBy", "isEqual", "max", "sortBy", "assign", "uniq", "trimChars", "trimCharsStart", "isObjectLike", "flattenDeep", "indexOf"];
const lodash_exports = ["toNumber", "flow", "isArray", "join", "replace", "trim", "dropRight", "takeRight", "head", "isUndefined", "isNull", "isNaN", "reduce", "isEmpty",
"constant", "tail", "includes", "startsWith", "findIndex", "isInteger", "isDate", "isString", "split", "clone", "keys", "isFunction", "merge", "has", "isBoolean", "isNumber",
"isObjectLike", "assign", "some", "each", "find", "orderBy", "union", "cloneDeep"];
const coreExternal = [
"lodash", "lodash/fp", "date-fns",
"lunr", "safe-buffer", "shortid",
"@nx-js/compiler-util"
];
export default { export default {
input: 'src/Test/testMain.js', input: 'src/Test/testMain.js',
output: { output: {
@ -32,9 +48,20 @@ export default {
// https://github.com/rollup/rollup-plugin-commonjs // https://github.com/rollup/rollup-plugin-commonjs
resolve({ resolve({
browser: true, browser: true,
dedupe: importee => importee === 'svelte' || importee.startsWith('svelte/') dedupe: importee => {
return importee === 'svelte'
|| importee.startsWith('svelte/')
|| coreExternal.includes(importee);
}
}), }),
commonjs(), commonjs({
namedExports: {
"lodash/fp": lodash_fp_exports,
"lodash":lodash_exports,
"shortid": ["generate"]
}
}),
json(),
// Watch the `public` directory and refresh the // Watch the `public` directory and refresh the
// browser on changes when not in production // browser on changes when not in production

View File

@ -1,21 +1,59 @@
<script> <script>
export let containerClass = ""; export let containerClass = "";
export let formControls = []; export let formControls = [];
export let _app;
let htmlElements = {};
let labels = {};
$ : {
let cIndex = 0;
for(let c of formControls) {
labels[cIndex] = c.label;
cIndex++;
}
if(_app && htmlElements) {
for(let el in htmlElements) {
_app.initialiseComponent(
formControls[el].control,
htmlElements[el]
);
}
}
}
</script> </script>
<div class="form-root {containerClass}"> <div class="form-root {containerClass}">
{#each formControls as formControl} {#each formControls as child, index}
<formControl /> <div class="label">{labels[index]}</div>
{:else} <div class="control"
<slot/> bind:this={htmlElements[index]}>
</div>
{/each} {/each}
</div> </div>
<style> <style>
.form-root { .form-root {
display: grid; display: grid;
grid-template-columns: [label] auto [control] auto; /* [overflow] auto;*/ grid-template-columns: [label] auto [control] 1fr; /* [overflow] auto;*/
}
.label {
grid-column-start: label;
padding: 5px 10px;
vertical-align: middle;
}
.control {
grid-column-start: control;
padding: 5px 10px;
}
.overflow {
grid-column-start: overflow;
}
.full-width {
width: 100%;
} }
</style> </style>

View File

@ -1,47 +0,0 @@
<script>
export let containerClass = "";
export let labelContainerClass = "";
export let controlContainerClass = "";
export let label = "";
export let control;
export let overflowControl;
export let fullWidth = false;
</script>
<div class="label {labelContainerClass}">{label}</div>
<div class="control {controlContainerClass}" class:full-width={fullWidth}>
{#if control}
<control />
{:else}
<slot />
{/if}
</div>
<!--div class="overflow">
{#if overflowControl}
<overflowControl />
{:else}
<slot name="overflow" />
{/if}
</div>-->
<style>
.label {
grid-column-start: label;
padding: 5px 10px;
vertical-align: middle;
}
.control {
grid-column-start: control;
padding: 5px 10px;
}
.overflow {
grid-column-start: overflow;
}
.full-width {
width: 100%;
}
</style>

View File

@ -0,0 +1,63 @@
<script>
import { onMount } from 'svelte'
import {buildStyle} from "./buildStyle";
export let gridTemplateRows ="";
export let gridTemplateColumns ="";
export let children = [];
export let width = "auto";
export let height = "auto";
export let containerClass="";
export let itemContainerClass="";
/*"gridColumnStart":"string",
"gridColumnEnd":"string",
"gridRowStart":"string",
"gridRowEnd":"string"*/
export let _app;
let style="";
let htmlElements = {};
$ : {
if(_app && htmlElements) {
for(let el in htmlElements) {
_app.initialiseComponent(
children[el].control,
htmlElements[el]
);
}
}
}
const childStyle = child =>
buildStyle({
"grid-column-start": child.gridColumnStart,
"grid-column-end": child.gridColumnEnd,
"grid-column": child.gridColumn,
"grid-row-start": child.gridRowStart,
"grid-row-end": child.gridRowStart,
"grid-row": child.gridRow,
});
</script>
<div class="root {containerClass}"
style="width: {width}; height: {height}; grid-template-columns: {gridTemplateColumns}; grid-template-rows: {gridTemplateRows};">
{#each children as child, index}
<div class="{itemContainerClass}"
style={childStyle(child)}
bind:this={htmlElements[index]}>
</div>
{/each}
</div>
<style>
.root {
display: grid;
}
</style>

View File

@ -1,18 +1,34 @@
<script> <script>
import Textbox from "./Textbox.svelte"; import Textbox from "./Textbox.svelte";
import FormControl from "./FormControl.svelte";
import Form from "./Form.svelte"; import Form from "./Form.svelte";
import Button from "./Button.svelte"; import Button from "./Button.svelte";
import { authenticate } from "./api";
export let usernameLabel = "Username"; export let usernameLabel = "Username";
export let passwordLabel = "Password"; export let passwordLabel = "Password";
export let loginButtonLabel = "Login"; export let loginButtonLabel = "Login";
export let loginRedirect = ""; export let loginRedirect = "";
export let logo = "/budibase-logo.png"; export let logo = "";
export let buttonClass = "";
let username = ""; let username = "";
let password = ""; let password = "";
let busy = false;
let incorrect = false;
const login = () => {
busy = true;
authenticate(username, password)
.then(r => {
busy = false;
if(r.status === 200) {
// reload page
} else {
incorrect = true;
}
})
}
</script> </script>
@ -20,23 +36,41 @@ let password = "";
<div class="content"> <div class="content">
{#if logo}
<div class="logo-container"> <div class="logo-container">
<img src={logo} alt="logo"/> <img src={logo} alt="logo"/>
</div> </div>
{/if}
<Form> <div class="form-root">
<FormControl label={usernameLabel}> <div class="label">
{usernameLabel}
</div>
<div class="control">
<Textbox bind:value={username} /> <Textbox bind:value={username} />
</FormControl> </div>
<FormControl label={passwordLabel}> <div class="label">
{passwordLabel}
</div>
<div class="control">
<Textbox bind:value={password} hideValue=true /> <Textbox bind:value={password} hideValue=true />
</FormControl> </div>
</Form> </div>
<div class="login-button-container"> <div class="login-button-container">
<Button>{loginButtonLabel}</Button> <Button disabled={busy}
on:click={login}
class={buttonClass}>
{loginButtonLabel}
</Button>
</div> </div>
{#if incorrect}
<div class="incorrect-details-panel">
Incorrect username or password
</div>
{/if}
</div> </div>
</div> </div>
@ -69,4 +103,31 @@ let password = "";
margin-top: 20px; margin-top: 20px;
} }
.incorrect-details-panel {
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 {
display: grid;
grid-template-columns: [label] auto [control] 1fr; /* [overflow] auto;*/
}
.label {
grid-column-start: label;
padding: 5px 10px;
vertical-align: middle;
}
.control {
grid-column-start: control;
padding: 5px 10px;
}
</style> </style>

View File

@ -1,14 +1,38 @@
<script> <script>
import Login from "../Login.svelte"; import createApp from "./createApp";
import { props } from "./props";
let _app;
const _appPromise = createApp();
_appPromise.then(a => _app = a);
const testProps = props.grid;
let currentComponent;
$: {
if(_app && currentComponent) {
_app.initialiseComponent(testProps, currentComponent);
}
}
</script> </script>
{#await _appPromise}
loading
{:then _app}
<div class="current"> <div id="current_component" bind:this={currentComponent}>
<Login />
</div> </div>
{/await}
<style> <style>
.current { #current_component {
height: 100%; height: 100%;
width: 100%; width: 100%;
} }

View File

@ -0,0 +1,40 @@
import { writable } from "svelte/store";
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 { createApp } from "@budibase/client/src/createApp";
export default async () => {
const componentLibraries = {
components : {
login : Login,
grid : Grid,
form : Form,
textbox : Textbox,
text: Text
}
}
return createApp(componentLibraries);
const initialiseComponent = (props, htmlElement) => {
new (components[props._component])({
target: htmlElement,
props: {...props, _app}
});
}
const store = writable({});
const _app = {
initialiseComponent,
store
};
return _app;
}

View File

@ -0,0 +1,66 @@
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"
}
]
},
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"
}
]
},
}

View File

@ -0,0 +1,33 @@
<script>
import {buildStyle} from "./buildStyle";
export let value="";
export let containerClass="";
export let background="";
export let border="";
export let font="";
export let display="";
export let textAlign="";
export let color="";
export let padding="";
export let _app;
let style="";
$: {
style=buildStyle({
border, background, font,
padding, display, color,
"text-align": textAlign
});
}
</script>
<div class={containerClass}
style={style}>
{value}
</div>

View File

@ -4,12 +4,33 @@ export let value="";
export let hideValue = false; export let hideValue = false;
export let className = "default"; export let className = "default";
export let _app;
let actualValue = "";
$: {
if(_app && value._isstate) {
_app.store.subscribe(s => {
actualValue = _app.store.getValue(s, value);
});
}
}
const onchange = (ev) => {
if(_app && value._isstate) {
_app.store.setValue(value, ev.target.value);
} else if(!value._isstate) {
actualValue = ev.target.value;
}
}
</script> </script>
{#if hideValue} {#if hideValue}
<input class={className} type="password" bind:value={value}/> <input class={className}
type="password"
value={actualValue} on:change/>
{:else} {:else}
<input class={className} type="text" bind:value={value}/> <input class={className} type="text" value={actualValue}/>
{/if} {/if}
<style> <style>

View File

@ -0,0 +1,22 @@
const apiCall = (method) => (url, body) =>
fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
},
body: body && JSON.stringify(body),
});
export const post = apiCall("POST");
export const get = apiCall("GET");
export const patch = apiCall("PATCH");
export const del = apiCall("DELETE");
export const authenticate = (username, password) => post("./api/authenticate", {
username, password
});
export default {
post, get, patch, delete:del
};

View File

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

View File

@ -3,6 +3,7 @@ export let className = "default";
export let disabled = false; export let disabled = false;
export let contentText; export let contentText;
export let contentComponent; export let contentComponent;
export let onClick = () => {};
export let _app; export let _app;
let contentComponentContainer; let contentComponentContainer;