Browse Source

include svelte files in prettier

pull/80/head
Martin McKeaveney 7 years ago
parent
commit
34d56e960f
  1. 3
      .prettierrc
  2. 5
      package.json
  3. 11
      packages/bootstrap-components/public/bundle.js
  4. 73
      packages/bootstrap-components/src/Form.svelte
  5. 201
      packages/bootstrap-components/src/Nav.svelte
  6. 36
      packages/bootstrap-components/src/Test/TestApp.svelte
  7. 69
      packages/builder/src/App.svelte
  8. 75
      packages/builder/src/BackendRoot.svelte
  9. 63
      packages/builder/src/NoPackage.svelte
  10. 130
      packages/builder/src/PackageRoot.svelte
  11. 31
      packages/builder/src/Settings.svelte
  12. 154
      packages/builder/src/accessLevels/AccessLevelView.svelte
  13. 178
      packages/builder/src/accessLevels/AccessLevelsRoot.svelte
  14. 199
      packages/builder/src/actionsAndTriggers/ActionView.svelte
  15. 167
      packages/builder/src/actionsAndTriggers/Actions.svelte
  16. 225
      packages/builder/src/actionsAndTriggers/ActionsAndTriggersRoot.svelte
  17. 105
      packages/builder/src/actionsAndTriggers/TriggerView.svelte
  18. 130
      packages/builder/src/actionsAndTriggers/Triggers.svelte
  19. 34
      packages/builder/src/common/ActionButton.svelte
  20. 166
      packages/builder/src/common/Button.svelte
  21. 28
      packages/builder/src/common/ButtonGroup.svelte
  22. 19
      packages/builder/src/common/Checkbox.svelte
  23. 28
      packages/builder/src/common/CodeArea.svelte
  24. 14
      packages/builder/src/common/ComingSoon.svelte
  25. 46
      packages/builder/src/common/DatePicker.svelte
  26. 73
      packages/builder/src/common/Dropdown.svelte
  27. 79
      packages/builder/src/common/DropdownButton.svelte
  28. 26
      packages/builder/src/common/ErrorsBox.svelte
  29. 90
      packages/builder/src/common/IconButton.svelte
  30. 12
      packages/builder/src/common/Icons/ArrowDown.svelte
  31. 4
      packages/builder/src/common/Icons/CircleIndicator.svelte
  32. 15
      packages/builder/src/common/Icons/Image.svelte
  33. 14
      packages/builder/src/common/Icons/Input.svelte
  34. 12
      packages/builder/src/common/Icons/Layout.svelte
  35. 14
      packages/builder/src/common/Icons/Paint.svelte
  36. 2
      packages/builder/src/common/Icons/Pencil.svelte
  37. 14
      packages/builder/src/common/Icons/Terminal.svelte
  38. 6
      packages/builder/src/common/Input.svelte
  39. 34
      packages/builder/src/common/Inputs/InputGroup.svelte
  40. 38
      packages/builder/src/common/Modal.svelte
  41. 29
      packages/builder/src/common/NumberBox.svelte
  42. 4
      packages/builder/src/common/PlusButton.svelte
  43. 22
      packages/builder/src/common/Select.svelte
  44. 42
      packages/builder/src/common/Textbox.svelte
  45. 41
      packages/builder/src/common/ValuesList.svelte
  46. 95
      packages/builder/src/database/ActionsHeader.svelte
  47. 18
      packages/builder/src/database/CollectionView.svelte
  48. 123
      packages/builder/src/database/DatabaseRoot.svelte
  49. 251
      packages/builder/src/database/FieldView.svelte
  50. 45
      packages/builder/src/database/HierarchyRow.svelte
  51. 125
      packages/builder/src/database/IndexView.svelte
  52. 425
      packages/builder/src/database/RecordView.svelte
  53. 214
      packages/builder/src/nav/BackendNav.svelte
  54. 95
      packages/builder/src/nav/HierarchyRow.svelte
  55. 44
      packages/builder/src/nav/NavItem.svelte
  56. 136
      packages/builder/src/userInterface/CodeEditor.svelte
  57. 193
      packages/builder/src/userInterface/ComponentPanel.svelte
  58. 69
      packages/builder/src/userInterface/ComponentSearch.svelte
  59. 218
      packages/builder/src/userInterface/ComponentSelector.svelte
  60. 164
      packages/builder/src/userInterface/ComponentsHierarchy.svelte
  61. 38
      packages/builder/src/userInterface/ComponentsHierarchyChildren.svelte
  62. 176
      packages/builder/src/userInterface/ComponentsList.svelte
  63. 77
      packages/builder/src/userInterface/ComponentsPaneSwitcher.svelte
  64. 120
      packages/builder/src/userInterface/CurrentItemPreview.svelte
  65. 155
      packages/builder/src/userInterface/EditComponentProps.svelte
  66. 168
      packages/builder/src/userInterface/EventsEditor/EventEditorModal.svelte
  67. 125
      packages/builder/src/userInterface/EventsEditor/EventsEditor.svelte
  68. 148
      packages/builder/src/userInterface/EventsEditor/HandlerSelector.svelte
  69. 92
      packages/builder/src/userInterface/LayoutEditor.svelte
  70. 193
      packages/builder/src/userInterface/NewComponent.svelte
  71. 101
      packages/builder/src/userInterface/PageView.svelte
  72. 121
      packages/builder/src/userInterface/PagesList.svelte
  73. 86
      packages/builder/src/userInterface/PropControl.svelte
  74. 116
      packages/builder/src/userInterface/PropsView.svelte
  75. 253
      packages/builder/src/userInterface/SettingsView.svelte
  76. 295
      packages/builder/src/userInterface/StateBindingControl.svelte
  77. 318
      packages/builder/src/userInterface/UserInterfaceRoot.svelte
  78. 42
      packages/core/test/recordApi.validate.spec.js
  79. 6
      packages/materialdesign-components/src/Button.svelte
  80. 12
      packages/materialdesign-components/src/H1.svelte
  81. 32
      packages/materialdesign-components/src/Test/TestApp.svelte
  82. 11
      packages/server/appPackages/_master/public/main/budibase-client.js
  83. 11
      packages/server/appPackages/_master/public/unauthenticated/budibase-client.js
  84. 11
      packages/server/appPackages/testApp/public/main/budibase-client.js
  85. 11
      packages/server/appPackages/testApp/public/unauthenticated/budibase-client.js
  86. 11
      packages/server/appPackages/testApp2/public/main/budibase-client.js
  87. 11
      packages/server/appPackages/testApp2/public/unauthenticated/budibase-client.js
  88. 11
      packages/standard-components/public/bundle.js
  89. 26
      packages/standard-components/src/Div.svelte
  90. 12
      packages/standard-components/src/H1.svelte
  91. 12
      packages/standard-components/src/H2.svelte
  92. 12
      packages/standard-components/src/H3.svelte
  93. 12
      packages/standard-components/src/H4.svelte
  94. 12
      packages/standard-components/src/H5.svelte
  95. 14
      packages/standard-components/src/H6.svelte
  96. 27
      packages/standard-components/src/Input.svelte
  97. 255
      packages/standard-components/src/Login.svelte
  98. 168
      packages/standard-components/src/Nav.svelte
  99. 36
      packages/standard-components/src/Select.svelte
  100. 102
      packages/standard-components/src/Table.svelte

3
.prettierrc

@ -3,5 +3,6 @@
"semi": false, "semi": false,
"singleQuote": false, "singleQuote": false,
"trailingComma": "es5", "trailingComma": "es5",
"plugins": ["prettier-plugin-svelte"] "plugins": ["prettier-plugin-svelte"],
"svelteSortOrder" : "scripts-markup-styles"
} }

5
package.json

@ -7,7 +7,8 @@
"eslint-plugin-svelte3": "^2.7.3", "eslint-plugin-svelte3": "^2.7.3",
"lerna": "^3.14.1", "lerna": "^3.14.1",
"prettier": "^1.19.1", "prettier": "^1.19.1",
"prettier-plugin-svelte": "^0.7.0" "prettier-plugin-svelte": "^0.7.0",
"svelte": "^3.18.1"
}, },
"dependencies": {}, "dependencies": {},
"scripts": { "scripts": {
@ -19,6 +20,6 @@
"test": "lerna run test", "test": "lerna run test",
"lint": "eslint packages", "lint": "eslint packages",
"lint:fix": "eslint --fix packages", "lint:fix": "eslint --fix packages",
"format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx}\"" "format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx,svelte}\""
} }
} }

11
packages/bootstrap-components/public/bundle.js

@ -8706,7 +8706,9 @@ var app = (function() {
null != n && null != n &&
!wu(n) !wu(n)
) || ) ||
nn.test(n) || !X.test(n) || (null != t && n in Qu(t)) nn.test(n) ||
!X.test(n) ||
(null != t && n in Qu(t))
) )
} }
function Re(n) { function Re(n) {
@ -18897,7 +18899,8 @@ var app = (function() {
// Non `Object` object instances with different constructors are not equal. // Non `Object` object instances with different constructors are not equal.
if ( if (
objCtor != othCtor && objCtor != othCtor &&
"constructor" in object && "constructor" in other && "constructor" in object &&
"constructor" in other &&
!( !(
typeof objCtor == "function" && typeof objCtor == "function" &&
objCtor instanceof objCtor && objCtor instanceof objCtor &&
@ -19391,7 +19394,9 @@ var app = (function() {
return ( return (
!!length && !!length &&
(type == "number" || (type != "symbol" && reIsUint.test(value))) && (type == "number" || (type != "symbol" && reIsUint.test(value))) &&
value > -1 && value % 1 == 0 && value < length value > -1 &&
value % 1 == 0 &&
value < length
) )
} }

73
packages/bootstrap-components/src/Form.svelte

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

201
packages/bootstrap-components/src/Nav.svelte

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

36
packages/bootstrap-components/src/Test/TestApp.svelte

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

69
packages/builder/src/App.svelte

@ -1,38 +1,33 @@
<script> <script>
import NoPackage from "./NoPackage.svelte"
import NoPackage from "./NoPackage.svelte"; import PackageRoot from "./PackageRoot.svelte"
import PackageRoot from "./PackageRoot.svelte"; import Settings from "./Settings.svelte"
import Settings from "./Settings.svelte"; import { store, initialise } from "./builderStore"
import {store, initialise} from "./builderStore"; import { onMount } from "svelte"
import { onMount } from 'svelte'; import IconButton from "./common/IconButton.svelte"
import IconButton from "./common/IconButton.svelte";
let init = initialise()
let init = initialise();
</script> </script>
<main> <main>
{#await init} {#await init}
<h1>loading</h1>
{:then result}
{#if $store.hasAppPackage}
<PackageRoot />
{:else} <h1>loading</h1>
<NoPackage /> {:then result}
{/if}
{#if $store.hasAppPackage}
<PackageRoot />
{:else}
<NoPackage />
{/if}
{:catch err} {:catch err}
<h1 style="color:red">{err}</h1> <h1 style="color:red">{err}</h1>
{/await} {/await}
<!-- <!--
<div class="settings"> <div class="settings">
<IconButton icon="settings" <IconButton icon="settings"
on:click={store.showSettings}/> on:click={store.showSettings}/>
@ -46,15 +41,15 @@
</main> </main>
<style> <style>
main { main {
height: 100%; height: 100%;
width: 100%; width: 100%;
font-family: "Roboto", Helvetica, Arial, sans-serif; font-family: "Roboto", Helvetica, Arial, sans-serif;
} }
.settings { .settings {
position: absolute; position: absolute;
bottom: 25px; bottom: 25px;
right: 25px; right: 25px;
} }
</style> </style>

75
packages/builder/src/BackendRoot.svelte

@ -1,56 +1,47 @@
<script> <script>
import BackendNav from "./nav/BackendNav.svelte"
import Database from "./database/DatabaseRoot.svelte"
import UserInterface from "./userInterface/UserInterfaceRoot.svelte"
import ActionsAndTriggers from "./actionsAndTriggers/ActionsAndTriggersRoot.svelte"
import AccessLevels from "./accessLevels/AccessLevelsRoot.svelte"
import ComingSoon from "./common/ComingSoon.svelte"
import BackendNav from "./nav/BackendNav.svelte"; import { store } from "./builderStore"
import Database from "./database/DatabaseRoot.svelte" ;
import UserInterface from "./userInterface/UserInterfaceRoot.svelte" ;
import ActionsAndTriggers from "./actionsAndTriggers/ActionsAndTriggersRoot.svelte" ;
import AccessLevels from "./accessLevels/AccessLevelsRoot.svelte" ;
import ComingSoon from "./common/ComingSoon.svelte";
import {store} from "./builderStore";
export let navWidth = "50px";
export let navWidth = "50px"
</script> </script>
<div class="root"> <div class="root">
<div class="nav"> <div class="nav">
<BackendNav /> <BackendNav />
</div> </div>
<div class="content" <div class="content" style="width: calc(100% - {navWidth}); left: {navWidth}">
style="width: calc(100% - {navWidth}); left: {navWidth}"> {#if $store.activeNav === 'database'}
{#if $store.activeNav === "database"} <Database />
<Database /> {:else if $store.activeNav === 'actions'}
{:else if $store.activeNav === "actions"} <ActionsAndTriggers />
<ActionsAndTriggers /> {:else if $store.activeNav === 'access levels'}
{:else if $store.activeNav === "access levels"} <AccessLevels />
<AccessLevels />
{/if} {/if}
</div> </div>
</div> </div>
<style> <style>
.root {
.root { height: 100%;
height: 100%; display: flex;
display: flex; }
}
.content {
flex: 1 1 auto;
.content { height: 100%;
flex: 1 1 auto; background-color: var(--white);
height: 100%; margin: 0;
background-color: var(--white); }
margin:0;
} .nav {
flex: 0 1 auto;
.nav { width: 300px;
flex: 0 1 auto; height: 100%;
width: 300px; }
height: 100%; </style>
}
</style>

63
packages/builder/src/NoPackage.svelte

@ -1,56 +1,55 @@
<script> <script>
import Button from "./common/Button.svelte"
import { store } from "./builderStore"
import Button from "./common/Button.svelte" let errors = []
import { store } from "./builderStore";
let errors = [];
</script> </script>
<div class="root"> <div class="root">
<div class="inner"> <div class="inner">
<img src="/_builder/assets/budibase-logo.png" class="logo" alt="budibase logo"/> <img
<div> src="/_builder/assets/budibase-logo.png"
class="logo"
<div> alt="budibase logo" />
<h4 style="margin-bottom: 20px">Choose an Application</h4> <div>
{#each $store.apps as app}
<a href={`#/${app}`} class="app-link">{app}</a> <div>
{/each} <h4 style="margin-bottom: 20px">Choose an Application</h4>
</div> {#each $store.apps as app}
</div> <a href={`#/${app}`} class="app-link">{app}</a>
{/each}
</div>
</div> </div>
</div>
</div> </div>
<style> <style>
.root {
.root {
position: fixed; position: fixed;
margin: 0 auto; margin: 0 auto;
text-align: center; text-align: center;
top: 20%; top: 20%;
/*color: #333333; /*color: #333333;
background-color: #fdfdfd;*/ background-color: #fdfdfd;*/
width:100%; width: 100%;
} }
.inner { .inner {
display:inline-block; display: inline-block;
margin: auto; margin: auto;
} }
.logo { .logo {
width: 300px; width: 300px;
margin-bottom: 40px; margin-bottom: 40px;
} }
.root :global(.option) { .root :global(.option) {
width:250px; width: 250px;
} }
.app-link { .app-link {
margin-top: 10px; margin-top: 10px;
display: block; display: block;
} }
</style>
</style>

130
packages/builder/src/PackageRoot.svelte

@ -1,57 +1,57 @@
<script> <script>
import IconButton from "./common/IconButton.svelte"
import IconButton from "./common/IconButton.svelte"; import { store } from "./builderStore"
import { store } from "./builderStore"; import UserInterfaceRoot from "./userInterface/UserInterfaceRoot.svelte"
import UserInterfaceRoot from "./userInterface/UserInterfaceRoot.svelte"; import BackendRoot from "./BackendRoot.svelte"
import BackendRoot from "./BackendRoot.svelte"; import { fade } from "svelte/transition"
import { fade } from "svelte/transition";
</script> </script>
<div class="root"> <div class="root">
<div class="top-nav"> <div class="top-nav">
<button class="home-logo"><img src="/assets/budibase-logo-only.png"/></button> <button class="home-logo">
<!-- <IconButton icon="home" <img src="/assets/budibase-logo-only.png" />
</button>
<!-- <IconButton icon="home"
color="var(--slate)" color="var(--slate)"
hoverColor="var(--secondary75)"/> --> hoverColor="var(--secondary75)"/> -->
<span class:active={$store.isBackend} <span
class="topnavitem" class:active={$store.isBackend}
on:click={store.showBackend}> class="topnavitem"
Backend on:click={store.showBackend}>
</span> Backend
<span class:active={!$store.isBackend} </span>
class="topnavitem" <span
on:click={store.showFrontend}> class:active={!$store.isBackend}
Frontend class="topnavitem"
</span> on:click={store.showFrontend}>
</div> Frontend
</span>
<div class="content"> </div>
{#if $store.isBackend}
<div in:fade out:fade> <div class="content">
<BackendRoot /> {#if $store.isBackend}
</div> <div in:fade out:fade>
{:else} <BackendRoot />
<div in:fade out:fade> </div>
<UserInterfaceRoot /> {:else}
</div> <div in:fade out:fade>
{/if} <UserInterfaceRoot />
</div> </div>
{/if}
</div>
</div> </div>
<style> <style>
.root {
.root { height: 100%;
height:100%; width: 100%;
width:100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
}
} .top-nav {
.top-nav {
flex: 0 0 auto; flex: 0 0 auto;
height: 48px; height: 48px;
background: white; background: white;
@ -60,21 +60,20 @@ import { fade } from "svelte/transition";
display: flex; display: flex;
align-items: center; align-items: center;
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
} }
.content { .content {
flex: 1 1 auto; flex: 1 1 auto;
width: 100%; width: 100%;
height: 100px; height: 100px;
} }
.content > div { .content > div {
height:100%; height: 100%;
width:100%; width: 100%;
}
}
.topnavitem { .topnavitem {
cursor: pointer; cursor: pointer;
color: var(--secondary50); color: var(--secondary50);
margin: 0px 15px; margin: 0px 15px;
@ -85,41 +84,38 @@ import { fade } from "svelte/transition";
display: flex; display: flex;
align-items: center; align-items: center;
box-sizing: border-box; box-sizing: border-box;
} }
.topnavitem:hover { .topnavitem:hover {
color: var(--secondary75); color: var(--secondary75);
font-weight: 600; font-weight: 600;
}
} .active {
.active {
color: var(--primary100); color: var(--primary100);
font-weight: 600; font-weight: 600;
border-bottom: 2px solid var(--primary100); border-bottom: 2px solid var(--primary100);
border-top: 2px solid transparent; border-top: 2px solid transparent;
} }
.home-logo { .home-logo {
border-style: none; border-style: none;
background-color: rgba(0,0,0,0); background-color: rgba(0, 0, 0, 0);
cursor: pointer; cursor: pointer;
outline: none; outline: none;
height: 40px; height: 40px;
padding: 8px 10px; padding: 8px 10px;
} }
.home-logo:hover { .home-logo:hover {
color: var(--hovercolor); color: var(--hovercolor);
} }
.home-logo:active {
outline:none;
}
.home-logo:active {
outline: none;
}
.home-logo img { .home-logo img {
height: 100%; height: 100%;
} }
</style> </style>

31
packages/builder/src/Settings.svelte

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

154
packages/builder/src/accessLevels/AccessLevelView.svelte

@ -1,99 +1,91 @@
<script> <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 { validateAccessLevels } from "../common/core"
import ErrorsBox from "../common/ErrorsBox.svelte"
import {cloneDeep, map, some, filter} from "lodash/fp"; export let level
import Textbox from "../common/Textbox.svelte"; export let allPermissions
import Checkbox from "../common/Checkbox.svelte"; export let onFinished
import ButtonGroup from "../common/ButtonGroup.svelte"; export let isNew
import Button from "../common/Button.svelte"; export let allLevels
import {validateAccessLevels} from "../common/core"; export let hierarchy
import ErrorsBox from "../common/ErrorsBox.svelte"; export let actions
export let level; let errors = []
export let allPermissions; let clonedLevel = cloneDeep(level)
export let onFinished;
export let isNew; const matchPermissions = (p1, p2) =>
export let allLevels; p1.type === p2.type &&
export let hierarchy; ((!p2.nodeKey && !p1.nodeKey) || p2.nodeKey === p1.nodeKey)
export let actions;
const hasPermission = hasPerm =>
let errors = []; some(p => matchPermissions(p, hasPerm))(clonedLevel.permissions)
let clonedLevel = cloneDeep(level);
$: permissionMatrix = map(p => ({
const matchPermissions = (p1, p2) => permission: p,
p1.type === p2.type hasPermission: hasPermission(p),
&& }))(allPermissions)
((!p2.nodeKey && !p1.nodeKey)
|| p2.nodeKey === p1.nodeKey); const getPermissionName = perm =>
perm.nodeKey ? `${perm.type} - ${perm.nodeKey}` : perm.type
const hasPermission = hasPerm =>
some(p => matchPermissions(p, hasPerm)) const save = () => {
(clonedLevel.permissions); const newLevels = isNew
? [...allLevels, clonedLevel]
$: permissionMatrix = : [...filter(l => l.name !== level.name)(allLevels), clonedLevel]
map(p => ({permission:p, hasPermission: hasPermission(p)}))
(allPermissions) errors = validateAccessLevels(hierarchy, actions, newLevels)
const getPermissionName = perm => if (errors.length > 0) return
perm.nodeKey
? `${perm.type} - ${perm.nodeKey}` onFinished(clonedLevel)
: perm.type; }
const save = () => { const permissionChanged = perm => ev => {
const hasPermission = ev.target.checked
const newLevels =
isNew if (hasPermission) {
? [...allLevels, clonedLevel] clonedLevel.permissions.push(perm)
: [...filter(l => l.name !== level.name)(allLevels), clonedLevel];
errors = validateAccessLevels(
hierarchy,
actions,
newLevels
);
if(errors.length > 0) return;
onFinished(clonedLevel);
}
const permissionChanged = perm => ev => {
const hasPermission = ev.target.checked;
if(hasPermission) {
clonedLevel.permissions.push(perm);
} else { } else {
clonedLevel.permissions = filter(p => !matchPermissions(p, perm))(clonedLevel.permissions); clonedLevel.permissions = filter(p => !matchPermissions(p, perm))(
clonedLevel.permissions
)
} }
} }
</script> </script>
<div> <div>
<ErrorsBox {errors} /> <ErrorsBox {errors} />
<form class="uk-form-horizontal">
<Textbox label="Name" bind:text={clonedLevel.name} /> <form class="uk-form-horizontal">
{#each permissionMatrix as permission} <Textbox label="Name" bind:text={clonedLevel.name} />
<div>
<Checkbox label={getPermissionName(permission.permission)}
checked={permission.hasPermission}
on:change={permissionChanged(permission.permission)} />
</div>
{/each}
</form> {#each permissionMatrix as permission}
<div>
<Checkbox
label={getPermissionName(permission.permission)}
checked={permission.hasPermission}
on:change={permissionChanged(permission.permission)} />
</div>
{/each}
<ButtonGroup style="margin-top: 10px"> </form>
<Button color="primary" grouped on:click={save}>Save</Button>
<Button color="secondary" grouped on:click={() => onFinished()}>Cancel</Button>
</ButtonGroup>
<ButtonGroup style="margin-top: 10px">
<Button color="primary" grouped on:click={save}>Save</Button>
<Button color="secondary" grouped on:click={() => onFinished()}>
Cancel
</Button>
</ButtonGroup>
</div> </div>
<style> <style>
</style> </style>

178
packages/builder/src/accessLevels/AccessLevelsRoot.svelte

@ -1,119 +1,115 @@
<script> <script>
import ButtonGroup from "../common/ButtonGroup.svelte"
import ButtonGroup from "../common/ButtonGroup.svelte"; import Button from "../common/Button.svelte"
import Button from "../common/Button.svelte"; import { store } from "../builderStore"
import {store} from "../builderStore"; import { generateFullPermissions, getNewAccessLevel } from "../common/core"
import {generateFullPermissions, getNewAccessLevel} from "../common/core"; import getIcon from "../common/icon"
import getIcon from "../common/icon"; import AccessLevelView from "./AccessLevelView.svelte"
import AccessLevelView from "./AccessLevelView.svelte"; import Modal from "../common/Modal.svelte"
import Modal from "../common/Modal.svelte";
let editingLevel = null
let editingLevel = null; let editingLevelIsNew = false
let editingLevelIsNew = false; $: isEditing = editingLevel !== null
$: isEditing = (editingLevel !== null);
let allPermissions = []
let allPermissions = []; store.subscribe(db => {
store.subscribe(db => { allPermissions = generateFullPermissions(db.hierarchy, db.actions)
allPermissions = generateFullPermissions(db.hierarchy, db.actions); })
})
let onLevelEdit = level => {
let onLevelEdit = (level) => { editingLevel = level
editingLevel = level; editingLevelIsNew = false
editingLevelIsNew = false; }
};
let onLevelCancel = () => {
let onLevelCancel = () => { editingAction = null
editingAction = null; }
};
let onLevelDelete = level => {
let onLevelDelete = (level) => { store.deleteLevel(level)
store.deleteLevel(level); }
};
let createNewLevel = () => {
editingLevelIsNew = true
let createNewLevel = () => { editingLevel = getNewAccessLevel()
editingLevelIsNew = true; }
editingLevel = getNewAccessLevel();
} let onEditingFinished = level => {
if (level) {
let onEditingFinished = (level) => { store.saveLevel(level, editingLevelIsNew, editingLevel)
if(level) {
store.saveLevel(level, editingLevelIsNew, editingLevel);
} }
editingLevel = null; editingLevel = null
} }
const getPermissionsString = perms => {
return `${perms.length} / ${allPermissions.length}`;
}
const getPermissionsString = perms => {
return `${perms.length} / ${allPermissions.length}`
}
</script> </script>
<div class="root"> <div class="root">
<ButtonGroup> <ButtonGroup>
<Button grouped color="secondary" on:click={createNewLevel}>Create New Access Level</Button> <Button grouped color="secondary" on:click={createNewLevel}>
</ButtonGroup> Create New Access Level
</Button>
</ButtonGroup>
{#if $store.accessLevels} {#if $store.accessLevels}
<table class="fields-table uk-table uk-table-small"> <table class="fields-table uk-table uk-table-small">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Permissions</th> <th>Permissions</th>
<th></th> <th />
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#each $store.accessLevels.levels as level} {#each $store.accessLevels.levels as level}
<tr> <tr>
<td >{level.name}</td> <td>{level.name}</td>
<td >{getPermissionsString(level.permissions)}</td> <td>{getPermissionsString(level.permissions)}</td>
<td class="edit-button"> <td class="edit-button">
<span on:click={() => onLevelEdit(level)}>{@html getIcon("edit")}</span> <span on:click={() => onLevelEdit(level)}>
<span on:click={() => onLevelDelete(level)}>{@html getIcon("trash")}</span> {@html getIcon('edit')}
</span>
<span on:click={() => onLevelDelete(level)}>
{@html getIcon('trash')}
</span>
</td> </td>
</tr> </tr>
{/each} {/each}
</tbody> </tbody>
</table> </table>
{:else} {:else}(no actions added){/if}
(no actions added)
{/if}
<Modal bind:isOpen={isEditing}> <Modal bind:isOpen={isEditing}>
{#if isEditing} {#if isEditing}
<AccessLevelView level={editingLevel} <AccessLevelView
allPermissions={allPermissions} level={editingLevel}
onFinished={onEditingFinished} {allPermissions}
isNew={editingLevelIsNew} onFinished={onEditingFinished}
allLevels={$store.accessLevels.levels} isNew={editingLevelIsNew}
hierarchy={$store.hierarchy} allLevels={$store.accessLevels.levels}
actions={$store.actions} /> hierarchy={$store.hierarchy}
{/if} actions={$store.actions} />
</Modal> {/if}
</Modal>
</div> </div>
<style> <style>
.root {
.root {
height: 100%; height: 100%;
position: relative; position: relative;
padding: 1.5rem; padding: 1.5rem;
}
} .actions-header {
.actions-header {
flex: 0 1 auto; flex: 0 1 auto;
} }
.node-view { .node-view {
overflow-y: auto; overflow-y: auto;
flex: 1 1 auto; flex: 1 1 auto;
} }
</style>
</style>

199
packages/builder/src/actionsAndTriggers/ActionView.svelte

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

167
packages/builder/src/actionsAndTriggers/Actions.svelte

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

225
packages/builder/src/actionsAndTriggers/ActionsAndTriggersRoot.svelte

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

105
packages/builder/src/actionsAndTriggers/TriggerView.svelte

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

130
packages/builder/src/actionsAndTriggers/Triggers.svelte

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

34
packages/builder/src/common/ActionButton.svelte

@ -1,11 +1,22 @@
<script> <script>
export let disabled = false; export let disabled = false
export let hidden = false; export let hidden = false
export let primary = true; export let primary = true
export let alert = false; export let alert = false
export let warning = false; export let warning = false
</script> </script>
<button
on:click
class="button"
class:hidden
class:primary
class:alert
class:warning
{disabled}>
<slot />
</button>
<style> <style>
.primary { .primary {
color: #0055ff; color: #0055ff;
@ -14,7 +25,7 @@
.alert { .alert {
color: rgba(255, 0, 31, 1); color: rgba(255, 0, 31, 1);
background: rgba(255, 0, 31, 0.1);; background: rgba(255, 0, 31, 0.1);
} }
.button { .button {
@ -40,14 +51,3 @@
visibility: hidden; visibility: hidden;
} }
</style> </style>
<button
on:click
class="button"
class:hidden
class:primary
class:alert
class:warning
{disabled}>
<slot />
</button>

166
packages/builder/src/common/Button.svelte

@ -1,163 +1,167 @@
<script> <script>
export let color = "primary"
export let className = ""
export let style = ""
export let groupPosition = ""
export let grouped = false
export let color = "primary"; $: borderClass = grouped ? "" : "border-normal"
export let className = "";
export let style = "";
export let groupPosition = "";
export let grouped = false;
$: borderClass = grouped
? ""
: "border-normal";
</script> </script>
<button class="{color} {className} {borderClass} {grouped ? "grouped" : ""}" <button
style="{style}" class="{color}
on:click > {className}
<slot/> {borderClass}
{grouped ? 'grouped' : ''}"
{style}
on:click>
<slot />
</button> </button>
<style> <style>
.border-normal {
.border-normal { border-radius: var(--borderradiusall); } border-radius: var(--borderradiusall);
.border-left { border-radius: var(--borderradius) 0 0 var(--borderradius); } }
.border-right { border-radius: 0 var(--borderradius) var(--borderradius) 0; } .border-left {
.border-middle { border-radius: 0; } border-radius: var(--borderradius) 0 0 var(--borderradius);
}
button { .border-right {
border-style: solid; border-radius: 0 var(--borderradius) var(--borderradius) 0;
}
.border-middle {
border-radius: 0;
}
button {
border-style: solid;
padding: 7.5px 15px; padding: 7.5px 15px;
cursor:pointer; cursor: pointer;
margin:5px; margin: 5px;
border-radius: 5px; border-radius: 5px;
} }
/* ---- PRIMARY ----*/ /* ---- PRIMARY ----*/
.primary { .primary {
background-color: var(--primary100); background-color: var(--primary100);
border-color: var(--primary100); border-color: var(--primary100);
color: var(--white); color: var(--white);
} }
.primary:hover { .primary:hover {
background-color: var(--primary75); background-color: var(--primary75);
border-color: var(--primary75); border-color: var(--primary75);
} }
.primary:active { .primary:active {
background-color: var(--primarydark); background-color: var(--primarydark);
border-color: var(--primarydark); border-color: var(--primarydark);
} }
.primary-outline { .primary-outline {
background-color: var(--white); background-color: var(--white);
border-color: var(--primary100); border-color: var(--primary100);
color: var(--primary100); color: var(--primary100);
} }
.primary-outline:hover { .primary-outline:hover {
background-color: var(--primary10); background-color: var(--primary10);
} }
.primary-outline:pressed { .primary-outline:pressed {
background-color: var(--primary25); background-color: var(--primary25);
} }
/* ---- secondary ----*/ /* ---- secondary ----*/
.secondary { .secondary {
background-color: var(--secondary100); background-color: var(--secondary100);
border-color: var(--secondary100); border-color: var(--secondary100);
color: var(--white); color: var(--white);
} }
.secondary:hover { .secondary:hover {
background-color: var(--secondary75); background-color: var(--secondary75);
border-color: var(--secondary75); border-color: var(--secondary75);
} }
.secondary:pressed { .secondary:pressed {
background-color: var(--secondarydark); background-color: var(--secondarydark);
border-color: var(--secondarydark); border-color: var(--secondarydark);
} }
.secondary-outline { .secondary-outline {
background-color: var(--white); background-color: var(--white);
border-color: var(--secondary100); border-color: var(--secondary100);
color: var(--secondary100); color: var(--secondary100);
} }
.secondary-outline:hover { .secondary-outline:hover {
background-color: var(--secondary10); background-color: var(--secondary10);
} }
.secondary-outline:pressed { .secondary-outline:pressed {
background-color: var(--secondary25); background-color: var(--secondary25);
} }
/* ---- success ----*/
/* ---- success ----*/ .success {
.success {
background-color: var(--success100); background-color: var(--success100);
border-color: var(--success100); border-color: var(--success100);
color: var(--white); color: var(--white);
} }
.success:hover { .success:hover {
background-color: var(--success75); background-color: var(--success75);
border-color: var(--success75); border-color: var(--success75);
} }
.success:pressed { .success:pressed {
background-color: var(--successdark); background-color: var(--successdark);
border-color: var(--successdark); border-color: var(--successdark);
} }
.success-outline { .success-outline {
background-color: var(--white); background-color: var(--white);
border-color: var(--success100); border-color: var(--success100);
color: var(--success100); color: var(--success100);
} }
.success-outline:hover { .success-outline:hover {
background-color: var(--success10); background-color: var(--success10);
} }
.success-outline:pressed { .success-outline:pressed {
background-color: var(--success25); background-color: var(--success25);
} }
/* ---- deletion ----*/ /* ---- deletion ----*/
.deletion { .deletion {
background-color: var(--deletion100); background-color: var(--deletion100);
border-color: var(--deletion100); border-color: var(--deletion100);
color: var(--white); color: var(--white);
} }
.deletion:hover { .deletion:hover {
background-color: var(--deletion75); background-color: var(--deletion75);
border-color: var(--deletion75); border-color: var(--deletion75);
} }
.deletion:pressed { .deletion:pressed {
background-color: var(--deletiondark); background-color: var(--deletiondark);
border-color: var(--deletiondark); border-color: var(--deletiondark);
} }
.deletion-outline { .deletion-outline {
background-color: var(--white); background-color: var(--white);
border-color: var(--deletion100); border-color: var(--deletion100);
color: var(--deletion100); color: var(--deletion100);
} }
.deletion-outline:hover { .deletion-outline:hover {
background-color: var(--deletion10); background-color: var(--deletion10);
} }
.deletion-outline:pressed { .deletion-outline:pressed {
background-color: var(--deletion25); background-color: var(--deletion25);
} }
</style>
</style>

28
packages/builder/src/common/ButtonGroup.svelte

@ -1,27 +1,25 @@
<script> <script>
export let style=""; export let style = ""
</script> </script>
<div class="root" style={style}> <div class="root" {style}>
<slot /> <slot />
</div> </div>
<style> <style>
.root {
.root {
display: flex; display: flex;
} }
.root:last-child { .root:last-child {
border-radius: 0 var(--borderradius) var(--borderradius) 0; border-radius: 0 var(--borderradius) var(--borderradius) 0;
} }
.root:first-child { .root:first-child {
border-radius: var(--borderradius) 0 0 var(--borderradius); border-radius: var(--borderradius) 0 0 var(--borderradius);
} }
.root:not(:first-child):not(:last-child) { .root:not(:first-child):not(:last-child) {
border-radius: 0; border-radius: 0;
} }
</style>
</style>

19
packages/builder/src/common/Checkbox.svelte

@ -1,16 +1,13 @@
<script> <script>
export let checked = false
export let checked=false; export let label = ""
export let label="";
</script> </script>
<input class="uk-checkbox" type="checkbox" bind:checked on:change />{label} <input class="uk-checkbox" type="checkbox" bind:checked on:change />
{label}
<style> <style>
input {
input { margin-right: 7px;
margin-right:7px; }
} </style>
</style>

28
packages/builder/src/common/CodeArea.svelte

@ -1,24 +1,22 @@
<script> <script>
// todo: use https://ace.c9.io // todo: use https://ace.c9.io
export let text = ""; export let text = ""
export let label = ""; export let label = ""
</script> </script>
<div>{label}</div> <div>{label}</div>
<textarea class="uk-textarea" bind:value={text}></textarea> <textarea class="uk-textarea" bind:value={text} />
<style> <style>
textarea {
textarea { padding: 3px;
padding:3px; margin-top: 5px;
margin-top:5px; margin-bottom: 10px;
margin-bottom:10px;
background: var(--lightslate); background: var(--lightslate);
color: var(--white); color: var(--white);
font-family: 'Courier New', Courier, monospace; font-family: "Courier New", Courier, monospace;
width:95%; width: 95%;
height:100px; height: 100px;
border-radius: 5px; border-radius: 5px;
} }
</style>
</style>

14
packages/builder/src/common/ComingSoon.svelte

@ -1,17 +1,13 @@
<script> <script>
export let name = ""
export let name = "";
</script> </script>
<div> <div>
<h4>Coming Sometime: {name}</h4> <h4>Coming Sometime: {name}</h4>
</div> </div>
<style> <style>
h4 {
h4 {
margin-top: 20px; margin-top: 20px;
} }
</style>
</style>

46
packages/builder/src/common/DatePicker.svelte

@ -1,36 +1,34 @@
<script> <script>
import flatpickr from "flatpickr"
import "flatpickr/dist/flatpickr.css"
import { onMount } from "svelte"
import flatpickr from "flatpickr"; export let value
import "flatpickr/dist/flatpickr.css"; export let label
import { onMount } from 'svelte'; export let width = "medium"
export let size = "small"
export let value; let input
export let label; let fpInstance
export let width = "medium";
export let size = "small";
let input; $: if (fpInstance) fpInstance.setDate(value)
let fpInstance;
$: if (fpInstance) fpInstance.setDate(value); onMount(() => {
fpInstance = flatpickr(input, {})
onMount(() => {
fpInstance = flatpickr(input, {});
fpInstance.config.onChange.push(selectedDates => { fpInstance.config.onChange.push(selectedDates => {
if(selectedDates.length > 0) if (selectedDates.length > 0) value = new Date(selectedDates[0])
value = new Date(selectedDates[0]); })
});
return fpInstance;
})
return fpInstance
})
</script> </script>
<div class="uk-margin"> <div class="uk-margin">
<label class="uk-form-label">{label}</label> <label class="uk-form-label">{label}</label>
<div class="uk-form-controls"> <div class="uk-form-controls">
<input class="uk-input uk-form-width-{width} uk-form-{size}" bind:this={input} > <input
</div> class="uk-input uk-form-width-{width} uk-form-{size}"
bind:this={input} />
</div>
</div> </div>

73
packages/builder/src/common/Dropdown.svelte

@ -1,39 +1,44 @@
<script> <script>
import {createEventDispatcher} from "svelte"; import { createEventDispatcher } from "svelte"
export let selected; export let selected
export let label; export let label
export let options; export let options
export let valueMember; export let valueMember
export let textMember; export let textMember
export let multiple=false; export let multiple = false
export let width = "medium"; export let width = "medium"
export let size = "small"; export let size = "small"
const dispatch =createEventDispatcher(); const dispatch = createEventDispatcher()
</script> </script>
<div class="uk-margin"> <div class="uk-margin">
<label class="uk-form-label">{label}</label> <label class="uk-form-label">{label}</label>
<div class="uk-form-controls"> <div class="uk-form-controls">
{#if multiple} {#if multiple}
<select
<select class="uk-select uk-form-width-{width} uk-form-{size}" multiple bind:value={selected} on:change> class="uk-select uk-form-width-{width} uk-form-{size}"
{#each options as option} multiple
<option value={!valueMember ? option : valueMember(option)}>{!textMember ? option : textMember(option)}</option> bind:value={selected}
{/each} on:change>
</select> {#each options as option}
<option value={!valueMember ? option : valueMember(option)}>
{:else} {!textMember ? option : textMember(option)}
</option>
<select class="uk-select uk-form-width-{width} uk-form-{size}" bind:value={selected} on:change> {/each}
{#each options as option} </select>
<option value={!valueMember ? option : valueMember(option)}>{!textMember ? option : textMember(option)}</option> {:else}
{/each} <select
</select> class="uk-select uk-form-width-{width} uk-form-{size}"
{/if} bind:value={selected}
</div> on:change>
{#each options as option}
<option value={!valueMember ? option : valueMember(option)}>
{!textMember ? option : textMember(option)}
</option>
{/each}
</select>
{/if}
</div>
</div> </div>

79
packages/builder/src/common/DropdownButton.svelte

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

26
packages/builder/src/common/ErrorsBox.svelte

@ -1,29 +1,29 @@
<script> <script>
export let errors = []; export let errors = []
$:hasErrors = errors.length > 0; $: hasErrors = errors.length > 0
</script> </script>
{#if hasErrors} {#if hasErrors}
<div class="error-container"> <div class="error-container">
{#each errors as error} {#each errors as error}
<div class="error-row">{error.field ? `${error.field}: ` : ""}{error.error}</div> <div class="error-row">
{error.field ? `${error.field}: ` : ''}{error.error}
</div>
{/each} {/each}
</div> </div>
{/if} {/if}
<style> <style>
.error-container {
.error-container { padding: 10px;
padding:10px;
border-style: solid; border-style: solid;
border-color: var(--deletion100); border-color: var(--deletion100);
border-radius: var(--borderradiusall); border-radius: var(--borderradiusall);
background: var(--deletion75); background: var(--deletion75);
} }
.error-row { .error-row {
padding: 5px 0px; padding: 5px 0px;
} }
</style>
</style>

90
packages/builder/src/common/IconButton.svelte

@ -1,63 +1,59 @@
<script> <script>
import getIcon from "./icon"; import getIcon from "./icon"
export let size = 18; export let size = 18
export let icon = ""; export let icon = ""
export let style = ""; export let style = ""
export let color = "var(--secondary100)"; export let color = "var(--secondary100)"
export let hoverColor = "var(--secondary75)"; export let hoverColor = "var(--secondary75)"
export let attributes = {}; export let attributes = {}
let currentAttributes = []; let currentAttributes = []
const addAttributes = (node, attributes) => { const addAttributes = (node, attributes) => {
const add = _attributes => {
const add = (_attributes) => { const attrs = []
const attrs = []; for (let attr in _attributes) {
for(let attr in _attributes) { node.setAttribute(attr, _attributes[attr])
node.setAttribute(attr, _attributes[attr]); attrs.push("uk-toggle")
attrs.push("uk-toggle") }
} currentAttributes = attrs
currentAttributes = attrs;
} }
add(attributes); add(attributes)
return { return {
// should implement update method // should implement update method
update(attributes) { update(attributes) {
for(let attr of currentAttributes) { for (let attr of currentAttributes) {
node.removeAttribute(attr) node.removeAttribute(attr)
} }
add(attributes); add(attributes)
}, },
destroy() {} destroy() {},
} }
} }
</script> </script>
<button style="{style}{style ? ";" : ""} color:{color}; --hovercolor:{hoverColor}" <button
on:click style="{style}{style ? ';' : ''} color:{color}; --hovercolor:{hoverColor}"
use:addAttributes={attributes}> on:click
{@html getIcon(icon, size)} use:addAttributes={attributes}>
{@html getIcon(icon, size)}
</button> </button>
<style> <style>
button {
button {
border-style: none; border-style: none;
background-color: rgba(0,0,0,0); background-color: rgba(0, 0, 0, 0);
cursor: pointer; cursor: pointer;
outline:none; outline: none;
} }
button:hover { button:hover {
color: var(--hovercolor); color: var(--hovercolor);
} }
button:active {
outline:none;
}
</style> button:active {
outline: none;
}
</style>

12
packages/builder/src/common/Icons/ArrowDown.svelte

@ -1,4 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"> <svg
<path fill="none" d="M0 0h24v24H0z"/> xmlns="http://www.w3.org/2000/svg"
<path fill="currentColor" d="M12 13.172l4.95-4.95 1.414 1.414L12 16 5.636 9.636 7.05 8.222z"/> viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M12 13.172l4.95-4.95 1.414 1.414L12 16 5.636 9.636 7.05 8.222z" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 228 B

After

Width:  |  Height:  |  Size: 246 B

4
packages/builder/src/common/Icons/CircleIndicator.svelte

@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8" width="8" height="8"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8" width="8" height="8">
<circle cx="4" cy="4" r="4" stroke-width="0" fill="currentColor" /> <circle cx="4" cy="4" r="4" stroke-width="0" fill="currentColor" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 158 B

After

Width:  |  Height:  |  Size: 157 B

15
packages/builder/src/common/Icons/Image.svelte

@ -1,4 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"> <svg
<path fill="none" d="M0 0h24v24H0z"/> xmlns="http://www.w3.org/2000/svg"
<path fill="currentColor" d="M4.828 21l-.02.02-.021-.02H2.992A.993.993 0 0 1 2 20.007V3.993A1 1 0 0 1 2.992 3h18.016c.548 0 .992.445.992.993v16.014a1 1 0 0 1-.992.993H4.828zM20 15V5H4v14L14 9l6 6zm0 2.828l-6-6L6.828 19H20v-1.172zM8 11a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/> viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M4.828 21l-.02.02-.021-.02H2.992A.993.993 0 0 1 2 20.007V3.993A1 1 0 0 1
2.992 3h18.016c.548 0 .992.445.992.993v16.014a1 1 0 0 1-.992.993H4.828zM20
15V5H4v14L14 9l6 6zm0 2.828l-6-6L6.828 19H20v-1.172zM8 11a2 2 0 1 1 0-4 2 2
0 0 1 0 4z" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 400 B

After

Width:  |  Height:  |  Size: 430 B

14
packages/builder/src/common/Icons/Input.svelte

@ -1,4 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"> <svg
<path fill="none" d="M0 0h24v24H0z"/> xmlns="http://www.w3.org/2000/svg"
<path fill="currentColor" d="M5 5v14h14V5H5zM4 3h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm5.869 12l-.82 2H6.833L11 7h2l4.167 10H14.95l-.82-2H9.87zm.82-2h2.622L12 9.8 10.689 13z"/> viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M5 5v14h14V5H5zM4 3h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1
1 0 0 1 1-1zm5.869 12l-.82 2H6.833L11 7h2l4.167
10H14.95l-.82-2H9.87zm.82-2h2.622L12 9.8 10.689 13z" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 339 B

After

Width:  |  Height:  |  Size: 365 B

12
packages/builder/src/common/Icons/Layout.svelte

@ -1,3 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/> <svg
<path fill="currentColor" d="M21 20a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16zM11 5H5v14h6V5zm8 8h-6v6h6v-6zm0-8h-6v6h6V5z"/> xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M21 20a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h16a1 1 0 0 1 1
1v16zM11 5H5v14h6V5zm8 8h-6v6h6v-6zm0-8h-6v6h6V5z" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 280 B

After

Width:  |  Height:  |  Size: 305 B

14
packages/builder/src/common/Icons/Paint.svelte

@ -1,3 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/> <svg
<path fill="currentColor" d="M19.228 18.732l1.768-1.768 1.767 1.768a2.5 2.5 0 1 1-3.535 0zM8.878 1.08l11.314 11.313a1 1 0 0 1 0 1.415l-8.485 8.485a1 1 0 0 1-1.414 0l-8.485-8.485a1 1 0 0 1 0-1.415l7.778-7.778-2.122-2.121L8.88 1.08zM11 6.03L3.929 13.1 11 20.173l7.071-7.071L11 6.029z"/> xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M19.228 18.732l1.768-1.768 1.767 1.768a2.5 2.5 0 1 1-3.535 0zM8.878
1.08l11.314 11.313a1 1 0 0 1 0 1.415l-8.485 8.485a1 1 0 0 1-1.414
0l-8.485-8.485a1 1 0 0 1 0-1.415l7.778-7.778-2.122-2.121L8.88 1.08zM11
6.03L3.929 13.1 11 20.173l7.071-7.071L11 6.029z" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 415 B

After

Width:  |  Height:  |  Size: 448 B

2
packages/builder/src/common/Icons/Pencil.svelte

@ -16,4 +16,4 @@
svg:hover { svg:hover {
cursor: pointer; cursor: pointer;
} }
</style> </style>

Before

Width:  |  Height:  |  Size: 446 B

After

Width:  |  Height:  |  Size: 447 B

14
packages/builder/src/common/Icons/Terminal.svelte

@ -1,4 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"> <svg
<path fill="none" d="M0 0h24v24H0z"/> xmlns="http://www.w3.org/2000/svg"
<path fill="currentColor" d="M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm1 2v14h16V5H4zm8 10h6v2h-6v-2zm-3.333-3L5.838 9.172l1.415-1.415L11.495 12l-4.242 4.243-1.415-1.415L8.667 12z"/> viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm1
2v14h16V5H4zm8 10h6v2h-6v-2zm-3.333-3L5.838 9.172l1.415-1.415L11.495
12l-4.242 4.243-1.415-1.415L8.667 12z" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 346 B

After

Width:  |  Height:  |  Size: 372 B

6
packages/builder/src/common/Input.svelte

@ -1,7 +1,9 @@
<script> <script>
export let value = ""; export let value = ""
</script> </script>
<input type="text" on:change bind:value />
<style> <style>
input { input {
display: block; display: block;
@ -23,5 +25,3 @@
height: 50px; height: 50px;
} }
</style> </style>
<input type="text" on:change bind:value />

34
packages/builder/src/common/Inputs/InputGroup.svelte

@ -1,29 +1,29 @@
<script> <script>
export let meta = []; export let meta = []
export let size = ''; export let size = ""
export let values = []; export let values = []
export let type = "number"; export let type = "number"
export let onStyleChanged = () => {}; export let onStyleChanged = () => {}
let _values = values.map(v => v); let _values = values.map(v => v)
$: onStyleChanged(_values); $: onStyleChanged(_values)
</script> </script>
<div class="inputs {size}"> <div class="inputs {size}">
{#each meta as { placeholder }, i} {#each meta as { placeholder }, i}
<input {type} <input
placeholder="{placeholder}" {type}
value={values[i]} {placeholder}
on:input={(e) => _values[i] = e.target.value} /> value={values[i]}
on:input={e => (_values[i] = e.target.value)} />
{/each} {/each}
</div> </div>
<style> <style>
.inputs { .inputs {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
input { input {
@ -34,18 +34,18 @@
opacity: 0.7; opacity: 0.7;
padding: 5px 10px; padding: 5px 10px;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid #DBDBDB; border: 1px solid #dbdbdb;
border-radius: 2px; border-radius: 2px;
outline: none; outline: none;
} }
input[type=number]::-webkit-inner-spin-button, input[type="number"]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button { input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none; -moz-appearance: none;
appearance: none; appearance: none;
margin: 0; margin: 0;
} }
.small > input { .small > input {
width: 38px; width: 38px;

38
packages/builder/src/common/Modal.svelte

@ -1,29 +1,38 @@
<script> <script>
import UIkit from "uikit"; import UIkit from "uikit"
export let isOpen = false; export let isOpen = false
export let onClosed = () => {}; export let onClosed = () => {}
export let id = ""; export let id = ""
let ukModal; let ukModal
let listenerAdded = false; let listenerAdded = false
$: { $: {
if (ukModal && !listenerAdded) { if (ukModal && !listenerAdded) {
listenerAdded = true; listenerAdded = true
ukModal.addEventListener("hidden", onClosed); ukModal.addEventListener("hidden", onClosed)
} }
if (ukModal) { if (ukModal) {
if (isOpen) { if (isOpen) {
UIkit.modal(ukModal).show(); UIkit.modal(ukModal).show()
} else { } else {
UIkit.modal(ukModal).hide(); UIkit.modal(ukModal).hide()
} }
} }
} }
</script> </script>
<div bind:this={ukModal} uk-modal {id}>
<div class="uk-modal-dialog uk-modal-body" uk-overflow-auto>
{#if onClosed}
<button class="uk-modal-close-default" type="button" uk-close />
{/if}
<slot />
</div>
</div>
<style> <style>
.uk-modal-dialog { .uk-modal-dialog {
border-radius: 0.3rem; border-radius: 0.3rem;
@ -33,12 +42,3 @@
flex-direction: column; flex-direction: column;
} }
</style> </style>
<div bind:this={ukModal} uk-modal {id}>
<div class="uk-modal-dialog uk-modal-body" uk-overflow-auto>
{#if onClosed}
<button class="uk-modal-close-default" type="button" uk-close />
{/if}
<slot />
</div>
</div>

29
packages/builder/src/common/NumberBox.svelte

@ -1,26 +1,21 @@
<script> <script>
export let value
export let label
export let value; const inputChanged = ev => {
export let label;
const inputChanged = ev => {
try { try {
value = Number(ev.target.value); value = Number(ev.target.value)
} catch(_) { } catch (_) {
value = null; value = null
} }
} }
let numberText = value === null || value === undefined
? "" : value.toString();
let numberText = value === null || value === undefined ? "" : value.toString()
</script> </script>
<div class="uk-margin"> <div class="uk-margin">
<label class="uk-form-label">{label}</label> <label class="uk-form-label">{label}</label>
<div class="uk-form-controls"> <div class="uk-form-controls">
<input class="uk-input" value={value} on:change={inputChanged} > <input class="uk-input" {value} on:change={inputChanged} />
</div> </div>
</div> </div>

4
packages/builder/src/common/PlusButton.svelte

@ -1,3 +1,5 @@
<button on:click>+</button>
<style> <style>
button { button {
cursor: pointer; cursor: pointer;
@ -19,5 +21,3 @@
color: rgba(22, 48, 87, 1); color: rgba(22, 48, 87, 1);
} }
</style> </style>
<button on:click>+</button>

22
packages/builder/src/common/Select.svelte

@ -1,8 +1,17 @@
<script> <script>
import getIcon from "./icon"; import getIcon from "./icon"
export let value; export let value
</script> </script>
<div class="select-container">
<select on:change {value}>
<slot />
</select>
<span class="arrow">
{@html getIcon('chevron-down', '24')}
</span>
</div>
<style> <style>
.select-container { .select-container {
padding-bottom: 10px; padding-bottom: 10px;
@ -44,12 +53,3 @@
color: var(--primary100); color: var(--primary100);
} }
</style> </style>
<div class="select-container">
<select on:change {value}>
<slot />
</select>
<span class="arrow">
{@html getIcon('chevron-down', '24')}
</span>
</div>

42
packages/builder/src/common/Textbox.svelte

@ -1,32 +1,32 @@
<script> <script>
export let text = ""; export let text = ""
export let label = ""; export let label = ""
export let width = "medium"; export let width = "medium"
export let size = "small"; export let size = "small"
export let margin = true; export let margin = true
export let infoText = ""; export let infoText = ""
export let hasError = false; export let hasError = false
export let disabled = false; export let disabled = false
</script> </script>
<div class:uk-margin={margin}> <div class:uk-margin={margin}>
<label class="uk-form-label">{label}</label> <label class="uk-form-label">{label}</label>
<div class="uk-form-controls"> <div class="uk-form-controls">
<input class="uk-input uk-form-width-{width} uk-form-{size}" <input
class:uk-form-danger={hasError} class="uk-input uk-form-width-{width} uk-form-{size}"
on:change class:uk-form-danger={hasError}
bind:value={text} on:change
{disabled}> bind:value={text}
</div> {disabled} />
{#if infoText} </div>
{#if infoText}
<div class="info-text">{infoText}</div> <div class="info-text">{infoText}</div>
{/if} {/if}
</div> </div>
<style> <style>
.info-text { .info-text {
font-size: 0.7rem; font-size: 0.7rem;
color: var(--secondary50); color: var(--secondary50);
} }
</style> </style>

41
packages/builder/src/common/ValuesList.svelte

@ -1,35 +1,30 @@
<script> <script>
import {join} from "lodash/fp"; import { join } from "lodash/fp"
export let values; export let values
export let label; export let label
const inputChanged = ev => { const inputChanged = ev => {
try { try {
values = ev.target.value.split("\n"); values = ev.target.value.split("\n")
} catch(_) { } catch (_) {
values = []; values = []
} }
} }
$: valuesText = join("\n")(values);
$: valuesText = join("\n")(values)
</script> </script>
<div class="uk-margin"> <div class="uk-margin">
<label class="uk-form-label">{label}</label> <label class="uk-form-label">{label}</label>
<div class="uk-form-controls"> <div class="uk-form-controls">
<textarea value={valuesText} on:change={inputChanged} ></textarea> <textarea value={valuesText} on:change={inputChanged} />
</div> </div>
</div> </div>
<style> <style>
textarea {
textarea { width: 300px;
width:300px; height: 200px;
height:200px; }
} </style>
</style>

95
packages/builder/src/database/ActionsHeader.svelte

@ -1,62 +1,59 @@
<script> <script>
import Button from "../common/Button.svelte"; import Button from "../common/Button.svelte"
import ButtonGroup from "../common/ButtonGroup.svelte"; import ButtonGroup from "../common/ButtonGroup.svelte"
import {store} from "../builderStore"; import { store } from "../builderStore"
import Modal from "../common/Modal.svelte"; import Modal from "../common/Modal.svelte"
import ErrorsBox from "../common/ErrorsBox.svelte"; import ErrorsBox from "../common/ErrorsBox.svelte"
export let left; export let left
let confirmDelete = false; let confirmDelete = false
const openConfirmDelete = () => { const openConfirmDelete = () => {
confirmDelete = true; confirmDelete = true
} }
const deleteCurrentNode = () => { const deleteCurrentNode = () => {
confirmDelete = false; confirmDelete = false
store.deleteCurrentNode(); store.deleteCurrentNode()
} }
</script> </script>
<div class="root" style="left: {left}"> <div class="root" style="left: {left}">
<ButtonGroup> <ButtonGroup>
<Button color="secondary" grouped on:click={store.saveCurrentNode}> <Button color="secondary" grouped on:click={store.saveCurrentNode}>
{#if $store.currentNodeIsNew} {#if $store.currentNodeIsNew}Create{:else}Update{/if}
Create </Button>
{:else}
Update {#if !$store.currentNodeIsNew}
{/if} <Button color="tertiary" grouped on:click={openConfirmDelete}>
</Button> Delete
</Button>
{#if !$store.currentNodeIsNew} {/if}
<Button color="tertiary" grouped on:click={openConfirmDelete}> </ButtonGroup>
Delete
</Button> {#if !!$store.errors && $store.errors.length > 0}
{/if}
</ButtonGroup>
{#if !!$store.errors && $store.errors.length > 0}
<div style="width: 500px"> <div style="width: 500px">
<ErrorsBox errors={$store.errors}/> <ErrorsBox errors={$store.errors} />
</div> </div>
{/if} {/if}
<Modal bind:isOpen={confirmDelete}> <Modal bind:isOpen={confirmDelete}>
<div style="margin: 10px 0px 20px 0px">Are you sure you want to delete {$store.currentNode.name} ?</div> <div style="margin: 10px 0px 20px 0px">
<div style="float:right"> Are you sure you want to delete {$store.currentNode.name} ?
<Button color="primary" on:click={deleteCurrentNode}>Yes</Button> </div>
<Button color="secondary" on:click={() => confirmDelete = false}>No</Button> <div style="float:right">
</div> <Button color="primary" on:click={deleteCurrentNode}>Yes</Button>
</Modal> <Button color="secondary" on:click={() => (confirmDelete = false)}>
No
</Button>
</div>
</Modal>
</div> </div>
<style> <style>
.root {
.root {
padding: 1.5rem; padding: 1.5rem;
width: 100%; width: 100%;
align-items: right; align-items: right;
} }
</style>
</style>

18
packages/builder/src/database/CollectionView.svelte

@ -1,20 +1,16 @@
<script> <script>
import RecordCard from "./RecordCard.svelte"
import RecordCard from "./RecordCard.svelte"; export let record = {}
export let record = {};
</script> </script>
<div class="root"> <div class="root">
<div class="title">{record.name}</div> <div class="title">{record.name}</div>
<div class="inner"> <div class="inner">
<div class="node-path">{record.nodeKey()}</div> <div class="node-path">{record.nodeKey()}</div>
</div> </div>
</div> </div>
<style> <style>
</style>
</style>

123
packages/builder/src/database/DatabaseRoot.svelte

@ -1,82 +1,85 @@
<script> <script>
import HierarchyRow from "./HierarchyRow.svelte"
import RecordView from "./RecordView.svelte"
import IndexView from "./IndexView.svelte"
import ActionsHeader from "./ActionsHeader.svelte"
import { store } from "../builderStore"
import getIcon from "../common/icon"
import DropdownButton from "../common/DropdownButton.svelte"
import { hierarchy as hierarchyFunctions } from "../../../core/src"
import HierarchyRow from "./HierarchyRow.svelte"; const hierarchyWidth = "200px"
import RecordView from "./RecordView.svelte";
import IndexView from "./IndexView.svelte";
import ActionsHeader from "./ActionsHeader.svelte";
import {store} from "../builderStore";
import getIcon from "../common/icon";
import DropdownButton from "../common/DropdownButton.svelte";
import {hierarchy as hierarchyFunctions} from "../../../core/src";
const hierarchyWidth = "200px"; const defaultNewIndexActions = [
{
label: "New Root Index",
onclick: store.newRootIndex,
},
]
const defaultNewIndexActions = [{ const defaultNewRecordActions = [
label:"New Root Index", {
onclick: store.newRootIndex label: "New Root Record",
}]; onclick: store.newRootRecord,
},
]
const defaultNewRecordActions = [{ let newIndexActions = defaultNewIndexActions
label:"New Root Record", let newRecordActions = defaultNewRecordActions
onclick: store.newRootRecord
}];
let newIndexActions = defaultNewIndexActions; store.subscribe(db => {
let newRecordActions = defaultNewRecordActions; if (!db.currentNode || hierarchyFunctions.isIndex(db.currentNode)) {
newRecordActions = defaultNewRecordActions
store.subscribe(db => { newIndexActions = defaultNewIndexActions
if(!db.currentNode || hierarchyFunctions.isIndex(db.currentNode)) {
newRecordActions = defaultNewRecordActions;
newIndexActions = defaultNewIndexActions;
} else { } else {
newRecordActions = [ newRecordActions = [
...defaultNewRecordActions, ...defaultNewRecordActions,
{label: `New Child Record of ${db.currentNode.name}`, {
onclick: store.newChildRecord} label: `New Child Record of ${db.currentNode.name}`,
]; onclick: store.newChildRecord,
},
]
newIndexActions = [ newIndexActions = [
...defaultNewIndexActions, ...defaultNewIndexActions,
{label: `New Index on ${db.currentNode.name}`, {
onclick: store.newChildIndex} label: `New Index on ${db.currentNode.name}`,
]; onclick: store.newChildIndex,
},
]
} }
}); })
</script> </script>
<div class="root"> <div class="root">
<div class="actions-header"> <div class="actions-header">
{#if $store.currentNode} {#if $store.currentNode}
<ActionsHeader left={hierarchyWidth}/> <ActionsHeader left={hierarchyWidth} />
{/if} {/if}
</div> </div>
<div class="node-view"> <div class="node-view">
{#if !$store.currentNode} {#if !$store.currentNode}
<h1 style="margin-left: 100px">:)</h1> <h1 style="margin-left: 100px">:)</h1>
{:else if $store.currentNode.type === "record"} {:else if $store.currentNode.type === 'record'}
<RecordView /> <RecordView />
{:else} {:else}
<IndexView /> <IndexView />
{/if} {/if}
</div> </div>
</div> </div>
<style> <style>
.root { .root {
height: 100%; height: 100%;
position: relative; position: relative;
} }
.actions-header { .actions-header {
flex: 0 1 auto; flex: 0 1 auto;
} }
.node-view { .node-view {
overflow-y: auto; overflow-y: auto;
flex: 1 1 auto; flex: 1 1 auto;
} }
</style>
</style>

251
packages/builder/src/database/FieldView.svelte

@ -1,121 +1,158 @@
<script> <script>
import Dropdown from "../common/Dropdown.svelte"; import Dropdown from "../common/Dropdown.svelte"
import Textbox from "../common/Textbox.svelte"; import Textbox from "../common/Textbox.svelte"
import Button from "../common/Button.svelte"; import Button from "../common/Button.svelte"
import ButtonGroup from "../common/ButtonGroup.svelte"; import ButtonGroup from "../common/ButtonGroup.svelte"
import NumberBox from "../common/NumberBox.svelte"; import NumberBox from "../common/NumberBox.svelte"
import ValuesList from "../common/ValuesList.svelte"; import ValuesList from "../common/ValuesList.svelte"
import ErrorsBox from "../common/ErrorsBox.svelte"; import ErrorsBox from "../common/ErrorsBox.svelte"
import Checkbox from "../common/Checkbox.svelte"; import Checkbox from "../common/Checkbox.svelte"
import DatePicker from "../common/DatePicker.svelte"; import DatePicker from "../common/DatePicker.svelte"
import {cloneDeep, assign, keys, import {
isNumber, includes, map, isBoolean} from "lodash/fp"; cloneDeep,
import {allTypes, validate, getPotentialReferenceIndexes, assign,
getDefaultTypeOptions, getNode, keys,
getPotentialReverseReferenceIndexes} from "../common/core"; isNumber,
includes,
export let field; map,
export let allFields; isBoolean,
export let onFinished = () => {}; } from "lodash/fp"
export let store; import {
allTypes,
let errors = []; validate,
let clonedField = cloneDeep(field); getPotentialReferenceIndexes,
getDefaultTypeOptions,
$: isNew = !!field && field.name.length === 0; getNode,
getPotentialReverseReferenceIndexes,
$: possibleReferenceIndexes = getPotentialReferenceIndexes( } from "../common/core"
store.hierarchy, store.currentNode
); export let field
export let allFields
$: selectedReverseRefIndex = export let onFinished = () => {}
!clonedField.typeOptions.indexNodeKey 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); : getNode(store.hierarchy, clonedField.typeOptions.indexNodeKey)
$: possibleReverseReferenceIndexes = $: possibleReverseReferenceIndexes = !selectedReverseRefIndex
!selectedReverseRefIndex
? [] ? []
: getPotentialReverseReferenceIndexes( : getPotentialReverseReferenceIndexes(
store.hierarchy, selectedReverseRefIndex); store.hierarchy,
selectedReverseRefIndex
const typeChanged = (ev) => )
clonedField.typeOptions = getDefaultTypeOptions(ev.detail);
const typeChanged = ev =>
(clonedField.typeOptions = getDefaultTypeOptions(ev.detail))
const save = () => {
const save = () => {
errors = validate.field(allFields)(clonedField); errors = validate.field(allFields)(clonedField)
if(errors.length > 0) return; if (errors.length > 0) return
field.typeOptions = cloneDeep(clonedField.typeOptions); field.typeOptions = cloneDeep(clonedField.typeOptions)
onFinished( onFinished(assign(field)(clonedField))
assign(field)(clonedField) }
);
}
</script> </script>
<div class="root"> <div class="root">
<ErrorsBox errors={errors} /> <ErrorsBox {errors} />
<form class="uk-form-horizontal"> <form class="uk-form-horizontal">
<Dropdown label="Type" bind:selected={clonedField.type} options={keys(allTypes)} on:change={typeChanged} /> <Dropdown
label="Type"
{#if isNew} bind:selected={clonedField.type}
<Textbox label="Field Name" bind:text={clonedField.name} /> options={keys(allTypes)}
{:else} on:change={typeChanged} />
<div style="font-weight: bold">{clonedField.name}</div>
{/if} {#if isNew}
<Textbox label="Field Name" bind:text={clonedField.name} />
<Textbox label="Label" bind:text={clonedField.label} /> {:else}
<div style="font-weight: bold">{clonedField.name}</div>
{#if clonedField.type === "string"} {/if}
<NumberBox label="Max Length" bind:value={clonedField.typeOptions.maxLength} />
<ValuesList label="Values (options)" bind:values={clonedField.typeOptions.values} /> <Textbox label="Label" bind:text={clonedField.label} />
<Checkbox label="Declared Values Only" bind:checked={clonedField.typeOptions.allowDeclaredValuesOnly} />
{:else if clonedField.type === "bool"} {#if clonedField.type === 'string'}
<Checkbox label="Allow Null" bind:checked={clonedField.typeOptions.allowNulls} /> <NumberBox
{:else if clonedField.type === "datetime"} label="Max Length"
<DatePicker label="Min Value" bind:value={clonedField.typeOptions.minValue} /> bind:value={clonedField.typeOptions.maxLength} />
<DatePicker label="Max Value" bind:value={clonedField.typeOptions.maxValue} /> <ValuesList
{:else if clonedField.type === "number"} label="Values (options)"
<NumberBox label="Min Value" bind:value={clonedField.typeOptions.minValue} /> bind:values={clonedField.typeOptions.values} />
<NumberBox label="Max Value" bind:value={clonedField.typeOptions.maxValue} /> <Checkbox
<NumberBox label="Decimal Places" bind:value={clonedField.typeOptions.decimalPlaces} /> label="Declared Values Only"
{:else if clonedField.type === "reference"} bind:checked={clonedField.typeOptions.allowDeclaredValuesOnly} />
<Dropdown label="Lookup Index" {:else if clonedField.type === 'bool'}
options={possibleReferenceIndexes} <Checkbox
valueMember={n => n.nodeKey()} label="Allow Null"
textMember={n => n.name} bind:checked={clonedField.typeOptions.allowNulls} />
bind:selected={clonedField.typeOptions.indexNodeKey} /> {:else if clonedField.type === 'datetime'}
<DatePicker
<Dropdown label="Reverse Reference Index" label="Min Value"
options={possibleReverseReferenceIndexes} bind:value={clonedField.typeOptions.minValue} />
multiple=true <DatePicker
valueMember={n => n.nodeKey()} label="Max Value"
textMember={n => n.name} bind:value={clonedField.typeOptions.maxValue} />
bind:selected={clonedField.typeOptions.reverseIndexNodeKeys} /> {:else if clonedField.type === 'number'}
<NumberBox
<Textbox label="Display Value" bind:text={clonedField.typeOptions.displayValue} /> label="Min Value"
bind:value={clonedField.typeOptions.minValue} />
{:else if clonedField.type.startsWith("array")} <NumberBox
<NumberBox label="Min Length" bind:value={clonedField.typeOptions.minLength} /> label="Max Value"
<NumberBox label="Max Length" bind:value={clonedField.typeOptions.maxLength} /> bind:value={clonedField.typeOptions.maxValue} />
{/if} <NumberBox
label="Decimal Places"
</form> bind:value={clonedField.typeOptions.decimalPlaces} />
{:else if clonedField.type === 'reference'}
<ButtonGroup style="float: right;"> <Dropdown
<Button color="primary" grouped on:click={save}>Save</Button> label="Lookup Index"
<Button color="tertiary" grouped on:click={() => onFinished(false)}>Cancel</Button> options={possibleReferenceIndexes}
</ButtonGroup> 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>
<ButtonGroup style="float: right;">
<Button color="primary" grouped on:click={save}>Save</Button>
<Button color="tertiary" grouped on:click={() => onFinished(false)}>
Cancel
</Button>
</ButtonGroup>
</div> </div>
<style> <style>
</style>
</style>

45
packages/builder/src/database/HierarchyRow.svelte

@ -1,45 +1,42 @@
<script> <script>
import { store } from "../builderStore"
import {store} from "../builderStore"; import { cloneDeep } from "lodash/fp"
import {cloneDeep} from "lodash/fp"; export let level = 0
export let level = 0; export let node
export let node;
</script> </script>
<div class="root"> <div class="root">
<div class="title" on:click={() => store.selectExistingNode(node.nodeId)} style="padding-left: {20 + (level * 20)}px"> <div
{node.name} class="title"
</div> on:click={() => store.selectExistingNode(node.nodeId)}
{#if node.children} style="padding-left: {20 + level * 20}px">
{node.name}
</div>
{#if node.children}
{#each node.children as child} {#each node.children as child}
<svelte:self node={child} <svelte:self node={child} level={level + 1} />
level={level+1}/>
{/each} {/each}
{/if} {/if}
</div> </div>
<style> <style>
.root {
.root {
display: block; display: block;
font-size: .9rem; font-size: 0.9rem;
width: 100%; width: 100%;
cursor: pointer; cursor: pointer;
font-weight: bold; font-weight: bold;
} }
.title { .title {
font: var(--fontblack); font: var(--fontblack);
padding-top: 10px; padding-top: 10px;
padding-right: 5px; padding-right: 5px;
padding-bottom: 10px; padding-bottom: 10px;
color: var(--secondary100); color: var(--secondary100);
} }
.title:hover { .title:hover {
background-color: var(--secondary10); background-color: var(--secondary10);
} }
</style>
</style>

125
packages/builder/src/database/IndexView.svelte

@ -1,76 +1,79 @@
<script> <script>
import Textbox from "../common/Textbox.svelte"
import Textbox from "../common/Textbox.svelte"; import CodeArea from "../common/CodeArea.svelte"
import CodeArea from "../common/CodeArea.svelte"; import Button from "../common/Button.svelte"
import Button from "../common/Button.svelte"; import Dropdown from "../common/Dropdown.svelte"
import Dropdown from "../common/Dropdown.svelte"; import { store } from "../builderStore"
import {store} from "../builderStore"; import { filter, some, map } from "lodash/fp"
import {filter, some, map} from "lodash/fp"; import { hierarchy as hierarchyFunctions, common } from "../../../core/src"
import {hierarchy as hierarchyFunctions, common} from "../../../core/src";
const pipe = common.$
const pipe = common.$;
let index
let index; let indexableRecords = []
let indexableRecords = [];
store.subscribe($store => {
store.subscribe($store => { index = $store.currentNode
index = $store.currentNode; indexableRecords = pipe($store.hierarchy, [
indexableRecords = pipe($store.hierarchy,[ hierarchyFunctions.getFlattenedHierarchy,
hierarchyFunctions.getFlattenedHierarchy, filter(hierarchyFunctions.isDecendant(index.parent())),
filter(hierarchyFunctions.isDecendant(index.parent())), filter(hierarchyFunctions.isRecord),
filter(hierarchyFunctions.isRecord), map(n => ({
map(n => ({ node: n,
node:n, isallowed: some(id => n.nodeId === id)(index.allowedRecordNodeIds),
isallowed: some(id => n.nodeId === id)(index.allowedRecordNodeIds) })),
})) ])
]); })
});
const toggleAllowedRecord = record => {
const toggleAllowedRecord = record => { if (record.isallowed) {
if(record.isallowed) { index.allowedRecordNodeIds = filter(id => id !== record.node.nodeId)(
index.allowedRecordNodeIds = filter(id => id !== record.node.nodeId) index.allowedRecordNodeIds
(index.allowedRecordNodeIds); )
} else { } else {
index.allowedRecordNodeIds.push(record.node.nodeId); index.allowedRecordNodeIds.push(record.node.nodeId)
} }
}; }
</script> </script>
<form class="uk-form-horizontal root"> <form class="uk-form-horizontal root">
<Textbox bind:text={index.name} label="Name"/> <Textbox bind:text={index.name} label="Name" />
<div class="allowed-records"> <div class="allowed-records">
<div>Records to Index</div> <div>Records to Index</div>
{#each indexableRecords as rec} {#each indexableRecords as rec}
<input type="checkbox" checked={rec.isallowed} on:change={() => toggleAllowedRecord(rec)}/> <input
<span>{rec.node.name}</span> type="checkbox"
{/each} checked={rec.isallowed}
</div> on:change={() => toggleAllowedRecord(rec)} />
<span>{rec.node.name}</span>
{/each}
</div>
<Dropdown
label="Index Type"
bind:selected={index.indexType}
options={['ancestor', 'reference']} />
<CodeArea bind:text={index.map} label="Map (javascript)" />
<CodeArea bind:text={index.filter} label="Filter (javascript expression)" />
<CodeArea
bind:text={index.getShardName}
label="Shard Name (javascript expression)" />
<Dropdown label="Index Type" bind:selected={index.indexType} options={["ancestor", "reference"]} />
<CodeArea bind:text={index.map} label="Map (javascript)"/>
<CodeArea bind:text={index.filter} label="Filter (javascript expression)"/>
<CodeArea bind:text={index.getShardName} label="Shard Name (javascript expression)"/>
</form> </form>
<style> <style>
.root {
.root {
height: 100%; height: 100%;
padding: 15px; padding: 15px;
} }
.allowed-records { .allowed-records {
margin: 20px 0px; margin: 20px 0px;
} }
.allowed-records > span {
margin-right:30px;
}
</style> .allowed-records > span {
margin-right: 30px;
}
</style>

425
packages/builder/src/database/RecordView.svelte

@ -1,300 +1,307 @@
<script> <script>
import Textbox from "../common/Textbox.svelte"
import Textbox from "../common/Textbox.svelte"; import Button from "../common/Button.svelte"
import Button from "../common/Button.svelte"; import getIcon from "../common/icon"
import getIcon from "../common/icon"; import FieldView from "./FieldView.svelte"
import FieldView from "./FieldView.svelte"; import Modal from "../common/Modal.svelte"
import Modal from "../common/Modal.svelte"; import { map, join, filter, some, find, keys, isDate } from "lodash/fp"
import {map, join, filter, some, import { store } from "../builderStore"
find, keys, isDate} from "lodash/fp"; import { common, hierarchy as h } from "../../../core/src"
import { store } from "../builderStore"; import { templateApi, pipe, validate } from "../common/core"
import {common, hierarchy as h} from "../../../core/src";
import {templateApi, pipe, validate} from "../common/core"; let record
let getIndexAllowedRecords
let record; let editingField = false
let getIndexAllowedRecords; let fieldToEdit
let editingField = false; let isNewField = false
let fieldToEdit; let newField
let isNewField = false; let editField
let newField; let deleteField
let editField; let onFinishedFieldEdit
let deleteField; let editIndex
let onFinishedFieldEdit;
let editIndex; store.subscribe($store => {
record = $store.currentNode
store.subscribe($store => { const flattened = h.getFlattenedHierarchy($store.hierarchy)
record = $store.currentNode; getIndexAllowedRecords = index =>
const flattened = h.getFlattenedHierarchy($store.hierarchy); pipe(index.allowedRecordNodeIds, [
getIndexAllowedRecords = index => filter(id => some(n => n.nodeId === id)(flattened)),
pipe(index.allowedRecordNodeIds, [ map(id => find(n => n.nodeId === id)(flattened).name),
filter(id => some(n => n.nodeId === id)(flattened)), join(", "),
map(id => find(n => n.nodeId === id) ])
(flattened).name),
join(", ")
]);
newField = () => { newField = () => {
isNewField = true; isNewField = true
fieldToEdit = templateApi($store.hierarchy).getNewField("string"); fieldToEdit = templateApi($store.hierarchy).getNewField("string")
editingField = true; editingField = true
} }
onFinishedFieldEdit = field => {
onFinishedFieldEdit = (field) => { if (field) {
if(field) { store.saveField(field)
store.saveField(field); }
} editingField = false
editingField = false;
} }
editField = (field) => { editField = field => {
isNewField = false; isNewField = false
fieldToEdit = field; fieldToEdit = field
editingField = true; editingField = true
} }
deleteField = (field) => { deleteField = field => {
store.deleteField(field); store.deleteField(field)
} }
editIndex = index => { editIndex = index => {
store.selectExistingNode(index.nodeId); store.selectExistingNode(index.nodeId)
} }
})
})
let getTypeOptionsValueText = value => {
let getTypeOptionsValueText = value => { if (
if(value === Number.MAX_SAFE_INTEGER value === Number.MAX_SAFE_INTEGER ||
|| value === Number.MIN_SAFE_INTEGER value === Number.MIN_SAFE_INTEGER ||
|| new Date(value).getTime() === new Date(8640000000000000).getTime() new Date(value).getTime() === new Date(8640000000000000).getTime() ||
|| new Date(value).getTime() === new Date(-8640000000000000).getTime()) return "(any)"; new Date(value).getTime() === new Date(-8640000000000000).getTime()
)
if(value === null) return "(not set)"; return "(any)"
return value;
} if (value === null) return "(not set)"
return value
let getTypeOptions = typeOptions => }
let getTypeOptions = typeOptions =>
pipe(typeOptions, [ pipe(typeOptions, [
keys, keys,
map(k => `<span style="color:var(--slate)">${k}: </span>${getTypeOptionsValueText(typeOptions[k])}`), map(
join("<br>") k =>
]); `<span style="color:var(--slate)">${k}: </span>${getTypeOptionsValueText(
typeOptions[k]
const nameChanged = ev => { )}`
const pluralName = n => `${n}s`; ),
if(record.collectionName === "") { join("<br>"),
record.collectionName = pluralName(ev.target.value); ])
const nameChanged = ev => {
const pluralName = n => `${n}s`
if (record.collectionName === "") {
record.collectionName = pluralName(ev.target.value)
} }
} }
</script> </script>
<div class="root"> <div class="root">
<form class="uk-form-horizontal"> <form class="uk-form-horizontal">
<h3 class="settings-title"> <h3 class="settings-title">Settings</h3>
Settings
</h3> <Textbox label="Name:" bind:text={record.name} on:change={nameChanged} />
{#if !record.isSingle}
<Textbox label="Name:" bind:text={record.name} on:change={nameChanged}/> <Textbox label="Collection Name:" bind:text={record.collectionName} />
{#if !record.isSingle} <Textbox label="Shard Factor:" bind:text={record.allidsShardFactor} />
<Textbox label="Collection Name:" bind:text={record.collectionName} />
<Textbox label="Shard Factor:" bind:text={record.allidsShardFactor} />
{/if}
<div class="recordkey">{record.nodeKey()}</div>
</form>
<h3 class="title">
Fields <span class="add-field-button" on:click={newField}>{@html getIcon("plus")}</span>
</h3>
{#if record.fields.length > 0}
<table class="fields-table uk-table">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Options</th>
<th></th>
</tr>
</thead>
<tbody>
{#each record.fields as field}
<tr>
<td >
<div class="field-label">{field.label}</div>
<div style="font-size: 0.8em; color: var(--slate)">{field.name}</div>
</td>
<td >{field.type}</td>
<td >{@html getTypeOptions(field.typeOptions)}</td>
<td>
<span class="edit-button" on:click={() => editField(field)}>{@html getIcon("edit")}</span>
<span class="edit-button" on:click={() => deleteField(field)}>{@html getIcon("trash")}</span>
</td>
</tr>
{/each}
</tbody>
</table>
{:else}
(no fields added)
{/if} {/if}
<div class="recordkey">{record.nodeKey()}</div>
</form>
<h3 class="title">
Fields
<span class="add-field-button" on:click={newField}>
{@html getIcon('plus')}
</span>
</h3>
{#if editingField} {#if record.fields.length > 0}
<Modal bind:isOpen={editingField} onClosed={() => onFinishedFieldEdit(false) }> <table class="fields-table uk-table">
<FieldView field={fieldToEdit} <thead>
onFinished={onFinishedFieldEdit} <tr>
allFields={record.fields} <th>Name</th>
store={$store}/> <th>Type</th>
<th>Options</th>
<th />
</tr>
</thead>
<tbody>
{#each record.fields as field}
<tr>
<td>
<div class="field-label">{field.label}</div>
<div style="font-size: 0.8em; color: var(--slate)">
{field.name}
</div>
</td>
<td>{field.type}</td>
<td>
{@html getTypeOptions(field.typeOptions)}
</td>
<td>
<span class="edit-button" on:click={() => editField(field)}>
{@html getIcon('edit')}
</span>
<span class="edit-button" on:click={() => deleteField(field)}>
{@html getIcon('trash')}
</span>
</td>
</tr>
{/each}
</tbody>
</table>
{:else}(no fields added){/if}
{#if editingField}
<Modal
bind:isOpen={editingField}
onClosed={() => onFinishedFieldEdit(false)}>
<FieldView
field={fieldToEdit}
onFinished={onFinishedFieldEdit}
allFields={record.fields}
store={$store} />
</Modal> </Modal>
{/if} {/if}
<h3 class="title"> <h3 class="title">Indexes</h3>
Indexes
</h3>
{#each record.indexes as index} {#each record.indexes as index}
<div class="index-container"> <div class="index-container">
<div class="index-name"> <div class="index-name">
{index.name} {index.name}
<span style="margin-left: 7px" on:click={() => editIndex(index)}>{@html getIcon("edit")}</span> <span style="margin-left: 7px" on:click={() => editIndex(index)}>
</div> {@html getIcon('edit')}
<div class="index-field-row"> </span>
<span class="index-label">records indexed: </span> </div>
<span>{getIndexAllowedRecords(index)}</span> <div class="index-field-row">
<span class="index-label" style="margin-left: 15px">type:</span> <span class="index-label">records indexed:</span>
<span>{index.indexType}</span> <span>{getIndexAllowedRecords(index)}</span>
</div> <span class="index-label" style="margin-left: 15px">type:</span>
<span>{index.indexType}</span>
</div>
<div class="index-field-row">
<span class="index-label">map:</span>
<code class="index-mapfilter">{index.map}</code>
</div>
{#if index.filter}
<div class="index-field-row"> <div class="index-field-row">
<span class="index-label">map:</span> <span class="index-label">filter:</span>
<code class="index-mapfilter">{index.map}</code> <code class="index-mapfilter">{index.filter}</code>
</div> </div>
{#if index.filter} {/if}
<div class="index-field-row">
<span class="index-label">filter:</span>
<code class="index-mapfilter">{index.filter}</code>
</div>
{/if}
</div>
{:else}
<div class="no-indexes">
No indexes added.
</div> </div>
{/each} {:else}
<div class="no-indexes">No indexes added.</div>
{/each}
</div> </div>
<style> <style>
.root {
.root {
height: 100%; height: 100%;
padding: 2rem; padding: 2rem;
} }
.settings-title { .settings-title {
font-weight: 700; font-weight: 700;
} }
.title { .title {
margin: 3rem 0rem 0rem 0rem; margin: 3rem 0rem 0rem 0rem;
font-weight: 700; font-weight: 700;
} }
.recordkey { .recordkey {
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;
color: var(--primary100); color: var(--primary100);
} }
.fields-table { .fields-table {
margin: 1rem 1rem 0rem 0rem; margin: 1rem 1rem 0rem 0rem;
border-collapse:collapse; border-collapse: collapse;
} }
.add-field-button { .add-field-button {
cursor:pointer; cursor: pointer;
} }
.edit-button { .edit-button {
cursor:pointer; cursor: pointer;
color: var(--secondary25); color: var(--secondary25);
} }
.edit-button:hover { .edit-button:hover {
cursor:pointer; cursor: pointer;
color: var(--secondary75); color: var(--secondary75);
} }
th { th {
text-align: left; text-align: left;
} }
td { td {
padding: 1rem 5rem 1rem 0rem; padding: 1rem 5rem 1rem 0rem;
margin:0; margin: 0;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
}
} .field-label {
font-size: 14px;
.field-label { font-weight: 500;
font-size: 14px; }
font-weight: 500;
}
thead > tr { thead > tr {
border-width: 0px 0px 1px 0px; border-width: 0px 0px 1px 0px;
border-style: solid; border-style: solid;
border-color: var(--secondary75); border-color: var(--secondary75);
margin-bottom: 20px; margin-bottom: 20px;
} }
tbody > tr { tbody > tr {
border-width: 0px 0px 1px 0px; border-width: 0px 0px 1px 0px;
border-style: solid; border-style: solid;
border-color: var(--primary10); border-color: var(--primary10);
} }
tbody > tr:hover { tbody > tr:hover {
background-color: var(--primary10); background-color: var(--primary10);
} }
tbody > tr:hover .edit-button { tbody > tr:hover .edit-button {
color: var(--secondary75); color: var(--secondary75);
} }
.index-container { .index-container {
border-style: solid; border-style: solid;
border-width: 0 0 1px 0; border-width: 0 0 1px 0;
border-color: var(--secondary25); border-color: var(--secondary25);
padding: 10px; padding: 10px;
margin-bottom: 5px; margin-bottom: 5px;
} }
.index-label { .index-label {
color: var(--slate); color: var(--slate);
} }
.index-name { .index-name {
font-weight: bold; font-weight: bold;
color: var(--primary100); color: var(--primary100);
} }
.index-container code { .index-container code {
margin: 0; margin: 0;
display: inline; display: inline;
background-color: var(--primary10); background-color: var(--primary10);
color: var(--secondary100); color: var(--secondary100);
padding:3px; padding: 3px;
} }
.index-field-row { .index-field-row {
margin: 1rem 0rem 0rem 0rem; margin: 1rem 0rem 0rem 0rem;
} }
.no-indexes { .no-indexes {
margin: 1rem 0rem 0rem 0rem; margin: 1rem 0rem 0rem 0rem;
font-family: var(--fontnormal); font-family: var(--fontnormal);
font-size: 14px; font-size: 14px;
} }
</style> </style>

214
packages/builder/src/nav/BackendNav.svelte

@ -1,163 +1,153 @@
<script> <script>
import { store } from "../builderStore"
import { store } from "../builderStore"; import HierarchyRow from "./HierarchyRow.svelte"
import HierarchyRow from "./HierarchyRow.svelte"; import DropdownButton from "../common/DropdownButton.svelte"
import DropdownButton from "../common/DropdownButton.svelte"; import { hierarchy as hierarchyFunctions } from "../../../core/src"
import {hierarchy as hierarchyFunctions} from "../../../core/src"; import NavItem from "./NavItem.svelte"
import NavItem from "./NavItem.svelte"; import getIcon from "../common/icon"
import getIcon from "../common/icon";
const newRootRecord = () => {
store.newRootRecord()
const newRootRecord = () => { }
store.newRootRecord();
} const newRootIndex = () => {
store.newRootIndex()
const newRootIndex = () => { }
store.newRootIndex();
} const newChildRecord = () => {
store.newChildRecord()
const newChildRecord = () => { }
store.newChildRecord();
} const newChildIndex = () => {
store.newChildIndex()
const newChildIndex = () => { }
store.newChildIndex();
} const defaultNewChildActions = [
const defaultNewChildActions = [
{ {
label:"New Root Record", label: "New Root Record",
onclick: newRootRecord onclick: newRootRecord,
}, },
{ {
label:"New Root Index", label: "New Root Index",
onclick: newRootIndex onclick: newRootIndex,
} },
]; ]
let newChildActions = defaultNewChildActions; let newChildActions = defaultNewChildActions
const setActiveNav = (name) => () => { const setActiveNav = name => () => {
store.setActiveNav(name); store.setActiveNav(name)
} }
store.subscribe(db => { store.subscribe(db => {
if(!db.currentNode || hierarchyFunctions.isIndex(db.currentNode)) { if (!db.currentNode || hierarchyFunctions.isIndex(db.currentNode)) {
newChildActions = defaultNewChildActions; newChildActions = defaultNewChildActions
} else { } else {
newChildActions = [ newChildActions = [
{ {
label:"New Root Record", label: "New Root Record",
onclick: newRootRecord onclick: newRootRecord,
}, },
{ {
label:"New Root Index", label: "New Root Index",
onclick: newRootIndex onclick: newRootIndex,
}, },
{ {
label: `New Child Record of ${db.currentNode.name}`, label: `New Child Record of ${db.currentNode.name}`,
onclick: newChildRecord onclick: newChildRecord,
}, },
{ {
label: `New Index on ${db.currentNode.name}`, label: `New Index on ${db.currentNode.name}`,
onclick: newChildIndex onclick: newChildIndex,
} },
]; ]
} }
}); })
</script> </script>
<div class="items-root"> <div class="items-root">
<div class="hierarchy"> <div class="hierarchy">
<div class="components-list-container"> <div class="components-list-container">
<div class="nav-group-header"> <div class="nav-group-header">
<div>{@html getIcon("database","18")}</div> <div>
<div class="hierarchy-title">Database</div> {@html getIcon('database', '18')}
<DropdownButton iconName="plus" actions={newChildActions} />
</div>
</div> </div>
<div class="hierarchy-title">Database</div>
<DropdownButton iconName="plus" actions={newChildActions} />
</div>
</div>
<div class="hierarchy-items-container"> <div class="hierarchy-items-container">
{#each $store.hierarchy.children as record} {#each $store.hierarchy.children as record}
<HierarchyRow node={record} <HierarchyRow node={record} type="record" />
type="record" /> {/each}
{/each}
{#each $store.hierarchy.indexes as index} {#each $store.hierarchy.indexes as index}
<HierarchyRow node={index} <HierarchyRow node={index} type="index" />
type="index" /> {/each}
{/each}
</div>
</div> </div>
</div>
<NavItem name="actions" label="Actions & Triggers"/> <NavItem name="actions" label="Actions & Triggers" />
<NavItem name="access levels" label="User Levels"/> <NavItem name="access levels" label="User Levels" />
</div> </div>
<style> <style>
.items-root {
.items-root {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-height: 100%; max-height: 100%;
height: 100%; height: 100%;
background-color: var(--secondary5); background-color: var(--secondary5);
} }
.nav-group-header { .nav-group-header {
display:grid; display: grid;
grid-template-columns: [icon] auto [title] 1fr [button] auto; grid-template-columns: [icon] auto [title] 1fr [button] auto;
padding: 2rem 1rem 0rem 1rem; padding: 2rem 1rem 0rem 1rem;
font-size: .9rem; font-size: 0.9rem;
font-weight: bold; font-weight: bold;
} }
.nav-group-header>div:nth-child(1) { .nav-group-header > div:nth-child(1) {
padding: 0rem .7rem 0rem 0rem; padding: 0rem 0.7rem 0rem 0rem;
vertical-align: bottom; vertical-align: bottom;
grid-column-start: icon; grid-column-start: icon;
margin-right: 5px; margin-right: 5px;
} }
.nav-group-header>div:nth-child(2) { .nav-group-header > div:nth-child(2) {
margin-left:5px; margin-left: 5px;
vertical-align: bottom; vertical-align: bottom;
grid-column-start: title; grid-column-start: title;
margin-top:auto; margin-top: auto;
} }
.nav-group-header>div:nth-child(3) { .nav-group-header > div:nth-child(3) {
vertical-align: bottom; vertical-align: bottom;
grid-column-start: button; grid-column-start: button;
cursor: pointer; cursor: pointer;
color: var(--primary75); color: var(--primary75);
} }
.nav-group-header>div:nth-child(3):hover { .nav-group-header > div:nth-child(3):hover {
color: var(--primary75); color: var(--primary75);
} }
.hierarchy-title { .hierarchy-title {
flex: auto 1 1; flex: auto 1 1;
} }
.hierarchy { .hierarchy {
display:flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1 0 auto; flex: 1 0 auto;
height: 100px; height: 100px;
} }
.hierarchy-items-container { .hierarchy-items-container {
flex: 1 1 auto; flex: 1 1 auto;
overflow-y:auto; overflow-y: auto;
} }
</style>
</style>

95
packages/builder/src/nav/HierarchyRow.svelte

@ -1,66 +1,63 @@
<script> <script>
import { store } from "../builderStore"
import {store} from "../builderStore"; import { cloneDeep } from "lodash/fp"
import {cloneDeep} from "lodash/fp"; import getIcon from "../common/icon"
import getIcon from "../common/icon"; export let level = 0
export let level = 0; export let node
export let node; export let type
export let type;
let navActive = ""
let navActive = ""; $: icon = type === "index" ? "list" : "file"
$:icon = type==="index" ? "list" : "file";
store.subscribe(s => {
store.subscribe(s => { if (s.currentNode)
if(s.currentNode) navActive =
navActive = (s.activeNav === "database" && node.nodeId === s.currentNode.nodeId s.activeNav === "database" && node.nodeId === s.currentNode.nodeId
? "active" : "") ? "active"
}); : ""
})
</script> </script>
<div class="root"> <div class="root">
<div class="title {navActive}" on:click={() => store.selectExistingNode(node.nodeId)} style="padding-left: {20 + (level * 20)}px"> <div
{@html getIcon(icon, 12)} <span style="margin-left: 1rem">{node.name}</span> class="title {navActive}"
</div> on:click={() => store.selectExistingNode(node.nodeId)}
{#if node.children} style="padding-left: {20 + level * 20}px">
{#each node.children as child} {@html getIcon(icon, 12)}
<svelte:self node={child} <span style="margin-left: 1rem">{node.name}</span>
level={level+1} </div>
type="record"/> {#if node.children}
{/each} {#each node.children as child}
{/if} <svelte:self node={child} level={level + 1} type="record" />
{#if node.indexes} {/each}
{#each node.indexes as index} {/if}
<svelte:self node={index} {#if node.indexes}
level={level+1} {#each node.indexes as index}
type="index"/> <svelte:self node={index} level={level + 1} type="index" />
{/each} {/each}
{/if} {/if}
</div> </div>
<style> <style>
.root {
.root {
display: block; display: block;
font-size: .9rem; font-size: 0.9rem;
width: 100%; width: 100%;
cursor: pointer; cursor: pointer;
color: var(--secondary50); color: var(--secondary50);
font-weight: 500; font-weight: 500;
} }
.title { .title {
padding-top: .5rem; padding-top: 0.5rem;
padding-right: .5rem; padding-right: 0.5rem;
} }
.title:hover { .title:hover {
background-color: var(--secondary10); background-color: var(--secondary10);
} }
.active { .active {
background-color: var(--primary10); background-color: var(--primary10);
} }
</style>
</style>

44
packages/builder/src/nav/NavItem.svelte

@ -1,43 +1,35 @@
<script> <script>
import { store } from "../builderStore"
import getIcon from "../common/icon"
import {store} from "../builderStore"; export let name = ""
import getIcon from "../common/icon"; export let label = ""
export let name = ""; let navActive = ""
export let label = "";
let navActive = ""; store.subscribe(db => {
navActive = db.activeNav === name ? "active" : ""
store.subscribe(db => { })
navActive = (db.activeNav === name ? "active" : "")
});
const setActive = () =>
store.setActiveNav(name);
const setActive = () => store.setActiveNav(name)
</script> </script>
<div class="nav-item {navActive}" on:click={setActive}> <div class="nav-item {navActive}" on:click={setActive}>{label}</div>
{label}
</div>
<style> <style>
.nav-item {
.nav-item {
padding: 1.5rem 1rem 0rem 1rem; padding: 1.5rem 1rem 0rem 1rem;
font-size: .9rem; font-size: 0.9rem;
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer;
flex: 0 0 auto; flex: 0 0 auto;
} }
.nav-item:hover { .nav-item:hover {
background-color: var(--primary10); background-color: var(--primary10);
} }
.active { .active {
background-color: var(--primary10); background-color: var(--primary10);
} }
</style>
</style>

136
packages/builder/src/userInterface/CodeEditor.svelte

@ -1,83 +1,84 @@
<script> <script>
import { store } from "../builderStore/store"; import { store } from "../builderStore/store"
import UIkit from "uikit"; import UIkit from "uikit"
import Button from "../common/Button.svelte"; import Button from "../common/Button.svelte"
import ButtonGroup from "../common/ButtonGroup.svelte"; import ButtonGroup from "../common/ButtonGroup.svelte"
import CodeMirror from "codemirror"; import CodeMirror from "codemirror"
import "codemirror/mode/javascript/javascript.js"; import "codemirror/mode/javascript/javascript.js"
export let onCodeChanged; export let onCodeChanged
export let code; export let code
export const show = () => { export const show = () => {
UIkit.modal(codeModal).show(); UIkit.modal(codeModal).show()
}
let codeModal;
let editor;
let cmInstance;
$: currentCode = code;
$: originalCode = code;
$: {
if(editor) {
if(!cmInstance) {
cmInstance = CodeMirror.fromTextArea(editor, {
mode: 'javascript',
lineNumbers: false,
lineWrapping: true,
smartIndent: true,
matchBrackets: true,
readOnly: false
});
cmInstance.on("change", () => currentCode = cmInstance.getValue());
}
cmInstance.focus();
cmInstance.setValue(code || "");
} }
}
const cancel = () => { let codeModal
UIkit.modal(codeModal).hide(); let editor
currentCode = originalCode; let cmInstance
}
$: currentCode = code
$: originalCode = code
$: {
if (editor) {
if (!cmInstance) {
cmInstance = CodeMirror.fromTextArea(editor, {
mode: "javascript",
lineNumbers: false,
lineWrapping: true,
smartIndent: true,
matchBrackets: true,
readOnly: false,
})
cmInstance.on("change", () => (currentCode = cmInstance.getValue()))
}
cmInstance.focus()
cmInstance.setValue(code || "")
}
}
const save = () => { const cancel = () => {
originalCode = currentCode; UIkit.modal(codeModal).hide()
onCodeChanged(currentCode); currentCode = originalCode
UIkit.modal(codeModal).hide(); }
}
const save = () => {
originalCode = currentCode
onCodeChanged(currentCode)
UIkit.modal(codeModal).hide()
}
</script> </script>
<div bind:this={codeModal} uk-modal> <div bind:this={codeModal} uk-modal>
<div class="uk-modal-dialog" uk-overflow-auto> <div class="uk-modal-dialog" uk-overflow-auto>
<div class="uk-modal-header"> <div class="uk-modal-header">
<h3>Code</h3> <h3>Code</h3>
</div> </div>
<div class="uk-modal-body uk-form-horizontal">
<div class="uk-modal-body uk-form-horizontal" > <p>
Use the code box below to control how this component is displayed, with
<p>Use the code box below to control how this component is displayed, with javascript.</p> javascript.
</p>
<div>
<div class="editor-code-surround">function(render, context, store) {"{"}</div> <div>
<div class="editor"> <div class="editor-code-surround">
<textarea bind:this={editor}></textarea> function(render, context, store) {'{'}
</div> </div>
<div class="editor-code-surround"> <div class="editor">
{"}"} <textarea bind:this={editor} />
</div>
</div> </div>
<div class="editor-code-surround">{'}'}</div>
</div> </div>
<ButtonGroup style="float: right;">
<Button color="primary" grouped on:click={save}>Save</Button>
<Button color="tertiary" grouped on:click={cancel}>Close</Button>
</ButtonGroup>
</div> </div>
<ButtonGroup style="float: right;">
<Button color="primary" grouped on:click={save}>Save</Button>
<Button color="tertiary" grouped on:click={cancel}>Close</Button>
</ButtonGroup>
</div>
</div> </div>
<style> <style>
@ -105,5 +106,4 @@ const save = () => {
.editor-code-surround { .editor-code-surround {
font-family: "Courier New", Courier, monospace; font-family: "Courier New", Courier, monospace;
} }
</style> </style>

193
packages/builder/src/userInterface/ComponentPanel.svelte

@ -1,119 +1,129 @@
<script> <script>
import PropsView from "./PropsView.svelte"; import PropsView from "./PropsView.svelte"
import { store } from "../builderStore"; import { store } from "../builderStore"
import IconButton from "../common/IconButton.svelte"; import IconButton from "../common/IconButton.svelte"
import { LayoutIcon, PaintIcon, TerminalIcon, CircleIndicator, EventsIcon } from '../common/Icons/'; import {
import CodeEditor from './CodeEditor.svelte'; LayoutIcon,
import LayoutEditor from './LayoutEditor.svelte'; PaintIcon,
import EventsEditor from "./EventsEditor"; TerminalIcon,
CircleIndicator,
let current_view = 'props'; EventsIcon,
let codeEditor; } from "../common/Icons/"
import CodeEditor from "./CodeEditor.svelte"
$: component = $store.currentComponentInfo; import LayoutEditor from "./LayoutEditor.svelte"
$: originalName = component.name; import EventsEditor from "./EventsEditor"
$: name = component.name;
$: description = component.description; let current_view = "props"
$: componentInfo = $store.currentComponentInfo; let codeEditor
$: components = $store.components;
$: component = $store.currentComponentInfo
const onPropChanged = store.setComponentProp; $: originalName = component.name
const onStyleChanged = store.setComponentStyle; $: name = component.name
$: description = component.description
$: componentInfo = $store.currentComponentInfo
$: components = $store.components
const onPropChanged = store.setComponentProp
const onStyleChanged = store.setComponentStyle
</script> </script>
<div class="root"> <div class="root">
<ul> <ul>
<li> <li>
<button class:selected={current_view === 'props'} on:click={() => current_view = 'props'}> <button
<PaintIcon /> class:selected={current_view === 'props'}
</button> on:click={() => (current_view = 'props')}>
</li> <PaintIcon />
<li> </button>
<button class:selected={current_view === 'layout'} on:click={() => current_view = 'layout'}> </li>
<LayoutIcon /> <li>
</button> <button
</li> class:selected={current_view === 'layout'}
<li> on:click={() => (current_view = 'layout')}>
<button class:selected={current_view === 'code'} on:click={() => codeEditor && codeEditor.show()}> <LayoutIcon />
{#if componentInfo._code && componentInfo._code.trim().length > 0} </button>
<div class="button-indicator"> </li>
<CircleIndicator /> <li>
</div> <button
{/if} class:selected={current_view === 'code'}
<TerminalIcon /> on:click={() => codeEditor && codeEditor.show()}>
</button> {#if componentInfo._code && componentInfo._code.trim().length > 0}
</li> <div class="button-indicator">
<li> <CircleIndicator />
<button class:selected={current_view === 'events'} on:click={() => current_view = 'events'}> </div>
<EventsIcon />
</button>
</li>
</ul>
{#if !componentInfo.component}
<div class="component-props-container">
{#if current_view === 'props'}
<PropsView {componentInfo} {components} {onPropChanged} />
{:else if current_view === 'layout'}
<LayoutEditor {onStyleChanged} {componentInfo}/>
{:else if current_view === 'events'}
<EventsEditor {componentInfo} {components} {onPropChanged} />
{/if} {/if}
<TerminalIcon />
<CodeEditor </button>
bind:this={codeEditor} </li>
code={$store.currentComponentInfo._code} <li>
onCodeChanged={store.setComponentCode} /> <button
class:selected={current_view === 'events'}
</div> on:click={() => (current_view = 'events')}>
{:else} <EventsIcon />
<h1> This is a screen, this will be dealt with later</h1> </button>
{/if} </li>
</ul>
{#if !componentInfo.component}
<div class="component-props-container">
{#if current_view === 'props'}
<PropsView {componentInfo} {components} {onPropChanged} />
{:else if current_view === 'layout'}
<LayoutEditor {onStyleChanged} {componentInfo} />
{:else if current_view === 'events'}
<EventsEditor {componentInfo} {components} {onPropChanged} />
{/if}
<CodeEditor
bind:this={codeEditor}
code={$store.currentComponentInfo._code}
onCodeChanged={store.setComponentCode} />
</div>
{:else}
<h1>This is a screen, this will be dealt with later</h1>
{/if}
</div> </div>
<style> <style>
.root {
.root {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
}
} .title > div:nth-child(1) {
.title > div:nth-child(1) {
grid-column-start: name; grid-column-start: name;
color: var(--secondary100); color: var(--secondary100);
} }
.title > div:nth-child(2) { .title > div:nth-child(2) {
grid-column-start: actions; grid-column-start: actions;
} }
.component-props-container { .component-props-container {
margin-top: 10px; margin-top: 10px;
flex: 1 1 auto; flex: 1 1 auto;
overflow-y: auto; overflow-y: auto;
} }
ul { ul {
list-style: none; list-style: none;
display: flex; display: flex;
padding: 0; padding: 0;
} }
li { li {
margin-right: 20px; margin-right: 20px;
background: none; background: none;
border-radius: 5px; border-radius: 5px;
width: 48px; width: 48px;
height: 48px; height: 48px;
} }
li button { li button {
width: 100%; width: 100%;
height: 100%; height: 100%;
background: none; background: none;
@ -123,18 +133,17 @@ li button {
outline: none; outline: none;
cursor: pointer; cursor: pointer;
position: relative; position: relative;
} }
.selected { .selected {
color: var(--button-text); color: var(--button-text);
background: var(--background-button)!important; background: var(--background-button) !important;
} }
.button-indicator { .button-indicator {
position: absolute; position: absolute;
top: 8px; top: 8px;
right: 10px; right: 10px;
color: var(--button-text); color: var(--button-text);
} }
</style> </style>

69
packages/builder/src/userInterface/ComponentSearch.svelte

@ -1,64 +1,59 @@
<script> <script>
import { searchAllComponents } from "./pagesParsing/searchComponents"
import { store } from "../builderStore"
import { searchAllComponents } from "./pagesParsing/searchComponents"; export let onComponentChosen = () => {}
import { store } from "../builderStore";
export let onComponentChosen = () => {}; let phrase = ""
let phrase = ""; components = $store.components
components = $store.components; $: filteredComponents = !phrase ? [] : searchAllComponents(components, phrase)
$: filteredComponents =
!phrase
? []
: searchAllComponents(components, phrase);
</script> </script>
<div class="root"> <div class="root">
<form class="uk-search uk-search-large"> <form class="uk-search uk-search-large">
<span uk-search-icon></span> <span uk-search-icon />
<input class="uk-search-input" <input
type="search" class="uk-search-input"
placeholder="Based on component..." type="search"
bind:value={phrase}> placeholder="Based on component..."
</form> bind:value={phrase} />
</form>
<div>
{#each filteredComponents as component} <div>
<div class="component" on:click={() => onComponentChosen(component)}> {#each filteredComponents as component}
<div class="title">{component.name}</div> <div class="component" on:click={() => onComponentChosen(component)}>
<div class="description">{component.description}</div> <div class="title">{component.name}</div>
</div> <div class="description">{component.description}</div>
{/each} </div>
</div> {/each}
</div>
</div> </div>
<style> <style>
.component {
.component { padding: 5px;
padding:5px;
border-style: solid; border-style: solid;
border-width: 0 0 1px 0; border-width: 0 0 1px 0;
border-color: var(--lightslate); border-color: var(--lightslate);
cursor: pointer; cursor: pointer;
} }
.component:hover { .component:hover {
background-color: var(--primary10); background-color: var(--primary10);
} }
.component > .title { .component > .title {
font-size: 13pt; font-size: 13pt;
color: var(--secondary100); color: var(--secondary100);
} }
.component > .description { .component > .description {
font-size: 10pt; font-size: 10pt;
color: var(--primary75); color: var(--primary75);
font-style: italic; font-style: italic;
} }
</style> </style>

218
packages/builder/src/userInterface/ComponentSelector.svelte

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

164
packages/builder/src/userInterface/ComponentsHierarchy.svelte

@ -1,120 +1,114 @@
<script> <script>
import ComponentsHierarchyChildren from './ComponentsHierarchyChildren.svelte'; import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte"
import {
last,
sortBy,
map,
trimCharsStart,
trimChars,
join,
} from "lodash/fp";
import { pipe } from "../common/core";
import { store } from "../builderStore";
import { ArrowDownIcon } from '../common/Icons/'
export let components = []
const joinPath = join("/");
const normalizedName = name => pipe(name, [
trimCharsStart("./"),
trimCharsStart("~/"),
trimCharsStart("../"),
trimChars(" ")
]);
const lastPartOfName = (c) =>
last(c.name ? c.name.split("/") : c._component.split("/"))
const isComponentSelected = (current, comp) =>
current &&
current.component &&
comp.component &&
current.component.name === comp.component.name
const isFolderSelected = (current, folder) =>
isInSubfolder(current, folder)
$: _components =
pipe(components, [
map(c => ({component: c, title:lastPartOfName(c)})),
sortBy("title")
]);
function select_component(screen, component) {
store.setCurrentScreen(screen);
store.selectComponent(component);
}
const isScreenSelected = component =>
component.component &&
$store.currentFrontEndItem &&
component.component.name === $store.currentFrontEndItem.name;
</script> import { last, sortBy, map, trimCharsStart, trimChars, join } from "lodash/fp"
<div class="root"> import { pipe } from "../common/core"
import { store } from "../builderStore"
import { ArrowDownIcon } from "../common/Icons/"
export let components = []
const joinPath = join("/")
const normalizedName = name =>
pipe(name, [
trimCharsStart("./"),
trimCharsStart("~/"),
trimCharsStart("../"),
trimChars(" "),
])
const lastPartOfName = c =>
last(c.name ? c.name.split("/") : c._component.split("/"))
const isComponentSelected = (current, comp) =>
current &&
current.component &&
comp.component &&
current.component.name === comp.component.name
{#each _components as component} const isFolderSelected = (current, folder) => isInSubfolder(current, folder)
<div class="hierarchy-item component"
class:selected={isComponentSelected($store.currentComponentInfo, component)}
on:click|stopPropagation={() => store.setCurrentScreen(component.component.name)}>
<span class="icon" style="transform: rotate({isScreenSelected(component) ? 0 : -90}deg);"> $: _components = pipe(components, [
{#if component.component.props && component.component.props._children} map(c => ({ component: c, title: lastPartOfName(c) })),
<ArrowDownIcon /> sortBy("title"),
{/if} ])
</span>
<span class="title">{component.title}</span> function select_component(screen, component) {
</div> store.setCurrentScreen(screen)
store.selectComponent(component)
}
{#if isScreenSelected(component) && component.component.props && component.component.props._children} const isScreenSelected = component =>
<ComponentsHierarchyChildren components={component.component.props._children} component.component &&
currentComponent={$store.currentComponentInfo} $store.currentFrontEndItem &&
onSelect={child => select_component(component.component.name, child)} /> component.component.name === $store.currentFrontEndItem.name
</script>
<div class="root">
{#each _components as component}
<div
class="hierarchy-item component"
class:selected={isComponentSelected($store.currentComponentInfo, component)}
on:click|stopPropagation={() => store.setCurrentScreen(component.component.name)}>
<span
class="icon"
style="transform: rotate({isScreenSelected(component) ? 0 : -90}deg);">
{#if component.component.props && component.component.props._children}
<ArrowDownIcon />
{/if} {/if}
{/each} </span>
<span class="title">{component.title}</span>
</div>
{#if isScreenSelected(component) && component.component.props && component.component.props._children}
<ComponentsHierarchyChildren
components={component.component.props._children}
currentComponent={$store.currentComponentInfo}
onSelect={child => select_component(component.component.name, child)} />
{/if}
{/each}
</div> </div>
<style> <style>
.root {
.root {
font-weight: 500; font-weight: 500;
font-size: 0.9rem; font-size: 0.9rem;
color: #828fa5; color: #828fa5;
} }
.hierarchy-item { .hierarchy-item {
cursor: pointer; cursor: pointer;
padding: 11px 7px; padding: 11px 7px;
margin: 5px 0; margin: 5px 0;
border-radius: 5px; border-radius: 5px;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.hierarchy-item:hover { .hierarchy-item:hover {
/* color: var(--secondary); */ /* color: var(--secondary); */
background: #fafafa; background: #fafafa;
} }
.selected { .selected {
color: var(--button-text); color: var(--button-text);
background: var(--background-button)!important; background: var(--background-button) !important;
} }
.title { .title {
margin-left: 10px; margin-left: 10px;
} }
.icon { .icon {
display: inline-block; display: inline-block;
transition: 0.2s; transition: 0.2s;
width: 24px; width: 24px;
height: 24px; height: 24px;
} }
</style> </style>

38
packages/builder/src/userInterface/ComponentsHierarchyChildren.svelte

@ -1,29 +1,31 @@
<script> <script>
import { last } from "lodash/fp"; import { last } from "lodash/fp"
import { pipe } from "../common/core"; import { pipe } from "../common/core"
export let components = []; export let components = []
export let currentComponent; export let currentComponent
export let onSelect = () => {}; export let onSelect = () => {}
export let level = 0; export let level = 0
const capitalise = s => s.substring(0,1).toUpperCase() + s.substring(1); const capitalise = s => s.substring(0, 1).toUpperCase() + s.substring(1)
const get_name = s => last(s.split('/')); const get_name = s => last(s.split("/"))
const get_capitalised_name = name => pipe(name, [get_name,capitalise]); const get_capitalised_name = name => pipe(name, [get_name, capitalise])
</script> </script>
<ul> <ul>
{#each components as component} {#each components as component}
<li on:click|stopPropagation={() => onSelect(component)}> <li on:click|stopPropagation={() => onSelect(component)}>
<span class="item" <span
class:selected={currentComponent === component} class="item"
style="padding-left: {level * 20 + 67}px"> class:selected={currentComponent === component}
style="padding-left: {level * 20 + 67}px">
{get_capitalised_name(component._component)} {get_capitalised_name(component._component)}
</span> </span>
{#if component._children} {#if component._children}
<svelte:self components={component._children} <svelte:self
{currentComponent} components={component._children}
{onSelect} {currentComponent}
level={level + 1}/> {onSelect}
level={level + 1} />
{/if} {/if}
</li> </li>
{/each} {/each}
@ -46,6 +48,6 @@
} }
.selected { .selected {
color: var(--button-text); color: var(--button-text);
background: var(--background-button)!important; background: var(--background-button) !important;
} }
</style> </style>

176
packages/builder/src/userInterface/ComponentsList.svelte

@ -1,96 +1,91 @@
<script> <script>
import { splitName } from "./pagesParsing/splitRootComponentName.js" import { splitName } from "./pagesParsing/splitRootComponentName.js"
import { store } from "../builderStore"; import { store } from "../builderStore"
import { find, sortBy } from "lodash/fp"; import { find, sortBy } from "lodash/fp"
import { ImageIcon, InputIcon, LayoutIcon } from '../common/Icons/'; import { ImageIcon, InputIcon, LayoutIcon } from "../common/Icons/"
let componentLibraries = []; let componentLibraries = []
let current_view = 'text'; let current_view = "text"
const addRootComponent = (c, all) => { const addRootComponent = (c, all) => {
const { libName } = splitName(c.name); const { libName } = splitName(c.name)
let group = find(r => r.libName === libName)(all); let group = find(r => r.libName === libName)(all)
if(!group) { if (!group) {
group = { group = {
libName, libName,
components: [], components: [],
generators: [] generators: [],
}; }
all.push(group); all.push(group)
} }
group.components.push(c)
};
const onComponentChosen = store.addChildComponent; group.components.push(c)
}
$: { const onComponentChosen = store.addChildComponent
const newComponentLibraries = [];
for(let comp of sortBy(["name"])($store.components)) { $: {
addRootComponent( const newComponentLibraries = []
comp,
newComponentLibraries);
}
componentLibraries = newComponentLibraries; for (let comp of sortBy(["name"])($store.components)) {
addRootComponent(comp, newComponentLibraries)
} }
componentLibraries = newComponentLibraries
}
</script> </script>
<div class="root"> <div class="root">
{#each componentLibraries as lib} {#each componentLibraries as lib}
<div class="library-header"> <div class="library-header">{lib.libName}</div>
{lib.libName}
</div>
<div class="library-container"> <div class="library-container">
<ul> <ul>
<li> <li>
<button class:selected={current_view === 'text'} on:click={() => current_view = 'text'}> <button
<InputIcon /> class:selected={current_view === 'text'}
</button> on:click={() => (current_view = 'text')}>
</li> <InputIcon />
<li> </button>
<button class:selected={current_view === 'layout'} on:click={() => current_view = 'layout'}> </li>
<LayoutIcon /> <li>
</button> <button
</li> class:selected={current_view === 'layout'}
<li> on:click={() => (current_view = 'layout')}>
<button class:selected={current_view === 'media'} on:click={() => current_view = 'media'}> <LayoutIcon />
<ImageIcon /> </button>
</button> </li>
</li> <li>
</ul> <button
class:selected={current_view === 'media'}
{#each lib.components.filter(_ => true) as component} on:click={() => (current_view = 'media')}>
<ImageIcon />
<div class="component" </button>
on:click={() => onComponentChosen(component.name)}> </li>
<div class="name"> </ul>
{splitName(component.name).componentName}
</div> {#each lib.components.filter(_ => true) as component}
<div
class="component"
on:click={() => onComponentChosen(component.name)}>
<div class="name">{splitName(component.name).componentName}</div>
</div> </div>
{/each}
{/each}
</div> </div>
{/each}
{/each}
</div> </div>
<style> <style>
.root {
.root {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.library-header { .library-header {
font-size: 1.1em; font-size: 1.1em;
border-color: var(--primary25); border-color: var(--primary25);
border-width: 1px 0px; border-width: 1px 0px;
@ -98,15 +93,15 @@
background-color: var(--primary10); background-color: var(--primary10);
padding: 5px 0; padding: 5px 0;
flex: 0 0 auto; flex: 0 0 auto;
} }
.library-container { .library-container {
padding: 0 0 10px 10px; padding: 0 0 10px 10px;
flex: 1 1 auto; flex: 1 1 auto;
min-height: 0px; min-height: 0px;
} }
.component { .component {
padding: 0 15px; padding: 0 15px;
cursor: pointer; cursor: pointer;
border: 1px solid #ccc; border: 1px solid #ccc;
@ -117,35 +112,35 @@
color: #163057; color: #163057;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.component:hover { .component:hover {
background-color: var(--lightslate); background-color: var(--lightslate);
} }
.component > .name { .component > .name {
color: #163057; color: #163057;
display: inline-block; display: inline-block;
font-size: 12px; font-size: 12px;
font-weight: bold; font-weight: bold;
opacity: 0.6; opacity: 0.6;
} }
ul { ul {
list-style: none; list-style: none;
display: flex; display: flex;
padding: 0; padding: 0;
} }
li { li {
margin-right: 20px; margin-right: 20px;
background: none; background: none;
border-radius: 5px; border-radius: 5px;
width: 48px; width: 48px;
height: 48px; height: 48px;
} }
li button { li button {
width: 100%; width: 100%;
height: 100%; height: 100%;
background: none; background: none;
@ -154,11 +149,10 @@ li button {
padding: 12px; padding: 12px;
outline: none; outline: none;
cursor: pointer; cursor: pointer;
} }
.selected { .selected {
color: var(--button-text); color: var(--button-text);
background: var(--background-button)!important; background: var(--background-button) !important;
} }
</style> </style>

77
packages/builder/src/userInterface/ComponentsPaneSwitcher.svelte

@ -1,63 +1,59 @@
<script> <script>
import ComponentPanel from "./ComponentPanel.svelte"; import ComponentPanel from "./ComponentPanel.svelte"
import ComponentsList from "./ComponentsList.svelte"; import ComponentsList from "./ComponentsList.svelte"
let selected="properties"; let selected = "properties"
const isSelected = tab => const isSelected = tab => selected === tab
selected === tab;
const selectTab = tab => const selectTab = tab => (selected = tab)
selected = tab;
</script> </script>
<div class="root"> <div class="root">
<div class="switcher"> <div class="switcher">
<button <button
class:selected={selected==="properties"} class:selected={selected === 'properties'}
on:click={() => selectTab("properties")}> on:click={() => selectTab('properties')}>
Properties Properties
</button> </button>
<button <button
class:selected={selected==="components"} class:selected={selected === 'components'}
on:click={() => selectTab("components")}> on:click={() => selectTab('components')}>
Components Components
</button> </button>
</div> </div>
<div class="panel"> <div class="panel">
{#if selected==="properties"} {#if selected === 'properties'}
<ComponentPanel /> <ComponentPanel />
{/if} {/if}
{#if selected==="components"} {#if selected === 'components'}
<ComponentsList /> <ComponentsList />
{/if} {/if}
</div> </div>
</div> </div>
<style> <style>
.root {
.root {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 2rem 1.5rem 2rem 1.5rem; padding: 2rem 1.5rem 2rem 1.5rem;
} }
.switcher { .switcher {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-bottom: 25px; margin-bottom: 25px;
} }
.switcher > button { .switcher > button {
display: inline-block; display: inline-block;
border: none; border: none;
margin: 0; margin: 0;
@ -67,17 +63,16 @@
font-size: 0.85rem; font-size: 0.85rem;
text-transform: uppercase; text-transform: uppercase;
color: #999; color: #999;
background-color: rgba(0,0,0,0); background-color: rgba(0, 0, 0, 0);
} }
.switcher > .selected { .switcher > .selected {
color: #333; color: #333;
} }
.panel { .panel {
flex: 1 1 auto; flex: 1 1 auto;
height: 0px; height: 0px;
overflow-y: auto; overflow-y: auto;
} }
</style> </style>

120
packages/builder/src/userInterface/CurrentItemPreview.svelte

@ -1,85 +1,71 @@
<script> <script>
import { store } from "../builderStore"; import { store } from "../builderStore"
import { map, join } from "lodash/fp"; import { map, join } from "lodash/fp"
import { pipe } from "../common/core"; import { pipe } from "../common/core"
import { buildPropsHierarchy } from "./pagesParsing/buildPropsHierarchy"; import { buildPropsHierarchy } from "./pagesParsing/buildPropsHierarchy"
let iframe; let iframe
$: iframe && console.log(iframe.contentDocument.head.insertAdjacentHTML('beforeend', '<style></style>')) $: iframe &&
$: hasComponent = !!$store.currentFrontEndItem; console.log(
$: styles = hasComponent ? $store.currentFrontEndItem._css : ''; iframe.contentDocument.head.insertAdjacentHTML(
"beforeend",
'<style ✂prettier:content✂=""></style>'
)
)
$: hasComponent = !!$store.currentFrontEndItem
$: styles = hasComponent ? $store.currentFrontEndItem._css : ""
$: stylesheetLinks = pipe($store.pages.stylesheets, [ $: stylesheetLinks = pipe($store.pages.stylesheets, [
map(s => `<link rel="stylesheet" href="${s}"/>`), map(s => `<link rel="stylesheet" href="${s}"/>`),
join("\n") join("\n"),
]); ])
$: appDefinition = { $: appDefinition = {
componentLibraries: $store.loadLibraryUrls(), componentLibraries: $store.loadLibraryUrls(),
props: buildPropsHierarchy( props: buildPropsHierarchy(
$store.components, $store.components,
$store.screens, $store.screens,
$store.currentFrontEndItem), $store.currentFrontEndItem
hierarchy: $store.hierarchy, ),
appRootPath: "" hierarchy: $store.hierarchy,
}; appRootPath: "",
}
</script> </script>
<div class="component-container"> <div class="component-container">
{#if hasComponent} {#if hasComponent}
<iframe style="height: 100%; width: 100%" <iframe
title="componentPreview" style="height: 100%; width: 100%"
bind:this={iframe} title="componentPreview"
srcdoc={ bind:this={iframe}
`<html> srcdoc={`<html>
<head> <head>
${stylesheetLinks} ${stylesheetLinks}
<script> <script ✂prettier:content✂="CiAgICAgICAgd2luZG93WyIjI0JVRElCQVNFX0FQUERFRklOSVRJT04jIyJdID0gJHtKU09OLnN0cmluZ2lmeShhcHBEZWZpbml0aW9uKX07CiAgICAgICAgd2luZG93WyIjI0JVRElCQVNFX1VJRlVOQ1RJT05TIl0gPSAkeyRzdG9yZS5jdXJyZW50U2NyZWVuRnVuY3Rpb25zfTsKICAgICAgICAKICAgICAgICBpbXBvcnQoJy9fYnVpbGRlci9idWRpYmFzZS1jbGllbnQuZXNtLm1qcycpCiAgICAgICAgLnRoZW4obW9kdWxlID0+IHsKICAgICAgICAgICAgbW9kdWxlLmxvYWRCdWRpYmFzZSh7IHdpbmRvdywgbG9jYWxTdG9yYWdlIH0pOwogICAgICAgIH0pCiAgICA=">{}</script> <style ✂prettier:content✂="CgogICAgICAgIGJvZHkgewogICAgICAgICAgICBib3gtc2l6aW5nOiBib3JkZXItYm94OwogICAgICAgICAgICBwYWRkaW5nOiAyMHB4OwogICAgICAgIH0KICAgICR7c3R5bGVzfQogICAg"></style></head>
window["##BUDIBASE_APPDEFINITION##"] = ${JSON.stringify(appDefinition)};
window["##BUDIBASE_UIFUNCTIONS"] = ${$store.currentScreenFunctions};
import('/_builder/budibase-client.esm.mjs')
.then(module => {
module.loadBudibase({ window, localStorage });
})
</script>
<style>
body {
box-sizing: border-box;
padding: 20px;
}
${styles}
</style>
</head>
<body> <body>
</body> </body>
</html>`}> </html>`} />
</iframe> {/if}
{/if}
</div> </div>
<style> <style>
.component-container { .component-container {
grid-row-start: middle; grid-row-start: middle;
grid-column-start: middle; grid-column-start: middle;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
padding-top: 56.25%; padding-top: 56.25%;
margin: auto; margin: auto;
} }
.component-container iframe { .component-container iframe {
border: 0; border: 0;
height: 100%; height: 100%;
left: 0; left: 0;
position: absolute; position: absolute;
top: 0; top: 0;
width: 100%; width: 100%;
} }
</style> </style>

155
packages/builder/src/userInterface/EditComponentProps.svelte

@ -1,90 +1,91 @@
<script> <script>
import PropsView from "./PropsView.svelte"; import PropsView from "./PropsView.svelte"
import { store } from "../builderStore"; import { store } from "../builderStore"
import IconButton from "../common/IconButton.svelte"; import IconButton from "../common/IconButton.svelte"
import Textbox from "../common/Textbox.svelte"; import Textbox from "../common/Textbox.svelte"
import Button from "../common/Button.svelte"; import Button from "../common/Button.svelte"
import { LayoutIcon, PaintIcon, TerminalIcon } from '../common/Icons/'; import { LayoutIcon, PaintIcon, TerminalIcon } from "../common/Icons/"
import { import { cloneDeep, join, split, last } from "lodash/fp"
cloneDeep, import { assign } from "lodash"
join,
split,
last
} from "lodash/fp";
import { assign } from "lodash";
$: component = $store.currentFrontEndItem; $: component = $store.currentFrontEndItem
$: componentInfo = $store.currentComponentInfo; $: componentInfo = $store.currentComponentInfo
$: components = $store.components; $: components = $store.components
const updateComponent = doChange => doChange(cloneDeep(component))
const updateComponent = doChange => const onPropsChanged = newProps => {
doChange(cloneDeep(component)); updateComponent(newComponent => assign(newComponent.props, newProps))
}
const onPropsChanged = newProps => {
updateComponent(newComponent =>
assign(newComponent.props, newProps));
}
</script> </script>
<div class="root"> <div class="root">
<ul> <ul>
<li><button><PaintIcon /></button></li> <li>
<li><button><LayoutIcon /></button></li> <button>
<li><button><TerminalIcon /></button></li> <PaintIcon />
</ul> </button>
</li>
<div class="component-props-container"> <li>
<PropsView <button>
{componentInfo} <LayoutIcon />
{onPropsChanged} /> </button>
</div> </li>
<li>
<button>
<TerminalIcon />
</button>
</li>
</ul>
<div class="component-props-container">
<PropsView {componentInfo} {onPropsChanged} />
</div>
</div> </div>
<style> <style>
.root { .root {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
}
}
.title > div:nth-child(1) {
.title > div:nth-child(1) { grid-column-start: name;
grid-column-start: name; color: var(--secondary100);
color: var(--secondary100); }
}
.title > div:nth-child(2) {
.title > div:nth-child(2) { grid-column-start: actions;
grid-column-start: actions; }
}
.component-props-container {
.component-props-container { flex: 1 1 auto;
flex: 1 1 auto; overflow-y: auto;
overflow-y: auto; }
}
ul {
ul { list-style: none;
list-style: none; display: flex;
display: flex; padding: 0;
padding: 0; }
}
li {
li { margin-right: 20px;
margin-right: 20px; background: none;
background: none; border-radius: 5px;
border-radius: 5px; width: 45px;
width: 45px; height: 45px;
height: 45px; }
}
li button {
li button { width: 100%;
width: 100%; height: 100%;
height: 100%; background: none;
background: none; border: none;
border: none; border-radius: 5px;
border-radius: 5px; padding: 12px;
padding: 12px; }
}
</style> </style>

168
packages/builder/src/userInterface/EventsEditor/EventEditorModal.svelte

@ -1,107 +1,64 @@
<script> <script>
import Modal from "../../common/Modal.svelte"; import Modal from "../../common/Modal.svelte"
import HandlerSelector from "./HandlerSelector.svelte"; import HandlerSelector from "./HandlerSelector.svelte"
import IconButton from "../../common/IconButton.svelte"; import IconButton from "../../common/IconButton.svelte"
import ActionButton from "../../common/ActionButton.svelte"; import ActionButton from "../../common/ActionButton.svelte"
import PlusButton from "../../common/PlusButton.svelte"; import PlusButton from "../../common/PlusButton.svelte"
import Select from "../../common/Select.svelte"; import Select from "../../common/Select.svelte"
import Input from "../../common/Input.svelte"; import Input from "../../common/Input.svelte"
import getIcon from "../../common/icon"; import getIcon from "../../common/icon"
import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers"; import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers"
export let event; export let event
export let eventOptions; export let eventOptions
export let open; export let open
export let onClose; export let onClose
export let onPropChanged; export let onPropChanged
let eventType = "onClick"; let eventType = "onClick"
let draftEventHandler = { parameters: [] }; let draftEventHandler = { parameters: [] }
$: eventData = event || { handlers: [] }; $: eventData = event || { handlers: [] }
const closeModal = () => { const closeModal = () => {
onClose(); onClose()
draftEventHandler = { parameters: [] }; draftEventHandler = { parameters: [] }
eventData = { handlers: [] }; eventData = { handlers: [] }
}; }
const updateEventHandler = (updatedHandler, index) => { const updateEventHandler = (updatedHandler, index) => {
eventData.handlers[index] = updatedHandler; eventData.handlers[index] = updatedHandler
}; }
const updateDraftEventHandler = updatedHandler => { const updateDraftEventHandler = updatedHandler => {
draftEventHandler = updatedHandler; draftEventHandler = updatedHandler
}; }
const deleteEventHandler = index => { const deleteEventHandler = index => {
eventData.handlers.splice(index, 1); eventData.handlers.splice(index, 1)
eventData = eventData; eventData = eventData
}; }
const createNewEventHandler = handler => { const createNewEventHandler = handler => {
const newHandler = handler || { const newHandler = handler || {
parameters: {}, parameters: {},
[EVENT_TYPE_MEMBER_NAME]: "" [EVENT_TYPE_MEMBER_NAME]: "",
}; }
eventData.handlers.push(newHandler); eventData.handlers.push(newHandler)
eventData = eventData; eventData = eventData
};
const deleteEvent = () => {
onPropChanged(eventType, []);
closeModal();
};
const saveEventData = () => {
onPropChanged(eventType, eventData.handlers);
closeModal();
};
</script>
<style>
h2 {
color: var(--primary100);
font-size: 20px;
font-weight: bold;
margin-bottom: 0;
}
h5 {
color: rgba(22, 48, 87, 0.6);
font-size: 15px;
margin: 0;
} }
.event-options { const deleteEvent = () => {
display: grid; onPropChanged(eventType, [])
grid-template-columns: 1fr 1fr; closeModal()
grid-gap: 10px;
}
.actions,
header {
display: flex;
justify-content: space-between;
align-items: center;
}
.actions {
margin-top: auto;
}
header {
margin-top: 30px;
margin-bottom: 10px;
} }
a { const saveEventData = () => {
color: rgba(22, 48, 87, 0.6); onPropChanged(eventType, eventData.handlers)
font-size: 12px; closeModal()
margin-top: 0;
} }
</style> </script>
<Modal bind:isOpen={open} onClosed={closeModal}> <Modal bind:isOpen={open} onClosed={closeModal}>
<h2> <h2>
@ -133,8 +90,8 @@
newHandler newHandler
onChanged={updateDraftEventHandler} onChanged={updateDraftEventHandler}
onCreate={() => { onCreate={() => {
createNewEventHandler(draftEventHandler); createNewEventHandler(draftEventHandler)
draftEventHandler = { parameters: [] }; draftEventHandler = { parameters: [] }
}} }}
handler={draftEventHandler} /> handler={draftEventHandler} />
{#if eventData} {#if eventData}
@ -162,3 +119,46 @@
</ActionButton> </ActionButton>
</div> </div>
</Modal> </Modal>
<style>
h2 {
color: var(--primary100);
font-size: 20px;
font-weight: bold;
margin-bottom: 0;
}
h5 {
color: rgba(22, 48, 87, 0.6);
font-size: 15px;
margin: 0;
}
.event-options {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 10px;
}
.actions,
header {
display: flex;
justify-content: space-between;
align-items: center;
}
.actions {
margin-top: auto;
}
header {
margin-top: 30px;
margin-bottom: 10px;
}
a {
color: rgba(22, 48, 87, 0.6);
font-size: 12px;
margin-top: 0;
}
</style>

125
packages/builder/src/userInterface/EventsEditor/EventsEditor.svelte

@ -8,54 +8,80 @@
isEqual, isEqual,
sortBy, sortBy,
filter, filter,
difference difference,
} from "lodash/fp"; } from "lodash/fp"
import { pipe } from "../../common/core"; import { pipe } from "../../common/core"
import Checkbox from "../../common/Checkbox.svelte"; import Checkbox from "../../common/Checkbox.svelte"
import Textbox from "../../common/Textbox.svelte"; import Textbox from "../../common/Textbox.svelte"
import Dropdown from "../../common/Dropdown.svelte"; import Dropdown from "../../common/Dropdown.svelte"
import PlusButton from "../../common/PlusButton.svelte"; import PlusButton from "../../common/PlusButton.svelte"
import IconButton from "../../common/IconButton.svelte"; import IconButton from "../../common/IconButton.svelte"
import Modal from "../../common/Modal.svelte"; import Modal from "../../common/Modal.svelte"
import EventEditorModal from "./EventEditorModal.svelte"; import EventEditorModal from "./EventEditorModal.svelte"
import HandlerSelector from "./HandlerSelector.svelte"; import HandlerSelector from "./HandlerSelector.svelte"
import { PencilIcon } from "../../common/Icons"; import { PencilIcon } from "../../common/Icons"
import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers"; import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers"
export const EVENT_TYPE = "event"; export const EVENT_TYPE = "event"
export let componentInfo; export let componentInfo
export let onPropChanged = () => {}; export let onPropChanged = () => {}
export let components; export let components
let modalOpen = false; let modalOpen = false
let events = []; let events = []
let selectedEvent = null; let selectedEvent = null
$: { $: {
events = Object.keys(componentInfo) events = Object.keys(componentInfo)
.filter(key => findType(key) === EVENT_TYPE) .filter(key => findType(key) === EVENT_TYPE)
.map(key => ({ name: key, handlers: componentInfo[key] })); .map(key => ({ name: key, handlers: componentInfo[key] }))
} }
function findType(propName) { function findType(propName) {
if (!componentInfo._component) return; if (!componentInfo._component) return
return components.find(({ name }) => name === componentInfo._component) return components.find(({ name }) => name === componentInfo._component)
.props[propName]; .props[propName]
} }
const openModal = event => { const openModal = event => {
selectedEvent = event; selectedEvent = event
modalOpen = true; modalOpen = true
}; }
const closeModal = () => { const closeModal = () => {
selectedEvent = null; selectedEvent = null
modalOpen = false; modalOpen = false
}; }
</script> </script>
<header>
<h3>Events</h3>
<PlusButton on:click={() => openModal()} />
</header>
<div class="root">
<form class="uk-form-stacked form-root">
{#each events as event, index}
{#if event.handlers.length > 0}
<div
class="handler-container hierarchy-item {selectedEvent && selectedEvent.index === index ? 'selected' : ''}"
on:click={() => openModal({ ...event, index })}>
<span class="event-name">{event.name}</span>
<span class="edit-text">EDIT</span>
</div>
{/if}
{/each}
</form>
</div>
<EventEditorModal
{onPropChanged}
open={modalOpen}
onClose={closeModal}
eventOptions={events}
event={selectedEvent} />
<style> <style>
h3 { h3 {
text-transform: uppercase; text-transform: uppercase;
@ -123,30 +149,3 @@
background: var(--background-button) !important; background: var(--background-button) !important;
} }
</style> </style>
<header>
<h3>Events</h3>
<PlusButton on:click={() => openModal()} />
</header>
<div class="root">
<form class="uk-form-stacked form-root">
{#each events as event, index}
{#if event.handlers.length > 0}
<div
class="handler-container hierarchy-item {selectedEvent && selectedEvent.index === index ? 'selected' : ''}"
on:click={() => openModal({ ...event, index })}>
<span class="event-name">{event.name}</span>
<span class="edit-text">EDIT</span>
</div>
{/if}
{/each}
</form>
</div>
<EventEditorModal
{onPropChanged}
open={modalOpen}
onClose={closeModal}
eventOptions={events}
event={selectedEvent}
/>

148
packages/builder/src/userInterface/EventsEditor/HandlerSelector.svelte

@ -1,83 +1,116 @@
<script> <script>
import IconButton from "../../common/IconButton.svelte"; import IconButton from "../../common/IconButton.svelte"
import PlusButton from "../../common/PlusButton.svelte"; import PlusButton from "../../common/PlusButton.svelte"
import Select from "../../common/Select.svelte"; import Select from "../../common/Select.svelte"
import StateBindingControl from "../StateBindingControl.svelte"; import StateBindingControl from "../StateBindingControl.svelte"
import { find, map, keys, reduce, keyBy } from "lodash/fp"; import { find, map, keys, reduce, keyBy } from "lodash/fp"
import { pipe, userWithFullAccess } from "../../common/core"; import { pipe, userWithFullAccess } from "../../common/core"
import { import {
EVENT_TYPE_MEMBER_NAME, EVENT_TYPE_MEMBER_NAME,
allHandlers allHandlers,
} from "../../common/eventHandlers"; } from "../../common/eventHandlers"
import { store } from "../../builderStore"; import { store } from "../../builderStore"
export let handler; export let handler
export let onCreate; export let onCreate
export let onChanged; export let onChanged
export let onRemoved; export let onRemoved
export let index; export let index
export let newHandler; export let newHandler
let eventOptions; let eventOptions
let handlerType; let handlerType
let parameters = []; let parameters = []
$: eventOptions = allHandlers( $: eventOptions = allHandlers(
{ hierarchy: $store.hierarchy }, { hierarchy: $store.hierarchy },
userWithFullAccess({ userWithFullAccess({
hierarchy: $store.hierarchy, hierarchy: $store.hierarchy,
actions: keyBy("name")($store.actions) actions: keyBy("name")($store.actions),
}) })
); )
$: { $: {
if (handler) { if (handler) {
handlerType = handler[EVENT_TYPE_MEMBER_NAME]; handlerType = handler[EVENT_TYPE_MEMBER_NAME]
parameters = Object.entries(handler.parameters).map(([name, value]) => ({ parameters = Object.entries(handler.parameters).map(([name, value]) => ({
name, name,
value value,
})); }))
} else { } else {
// Empty Handler // Empty Handler
handlerType = ""; handlerType = ""
parameters = []; parameters = []
} }
} }
const handlerChanged = (type, params) => { const handlerChanged = (type, params) => {
const handlerParams = {}; const handlerParams = {}
for (let param of params) { for (let param of params) {
handlerParams[param.name] = param.value; handlerParams[param.name] = param.value
} }
const updatedHandler = { const updatedHandler = {
[EVENT_TYPE_MEMBER_NAME]: type, [EVENT_TYPE_MEMBER_NAME]: type,
parameters: handlerParams parameters: handlerParams,
}; }
onChanged(updatedHandler, index); onChanged(updatedHandler, index)
}; }
const handlerTypeChanged = e => { const handlerTypeChanged = e => {
const handlerType = eventOptions.find( const handlerType = eventOptions.find(
handler => handler.name === e.target.value handler => handler.name === e.target.value
); )
const defaultParams = handlerType.parameters.map(param => ({ const defaultParams = handlerType.parameters.map(param => ({
name: param, name: param,
value: "" value: "",
})); }))
handlerChanged(handlerType.name, defaultParams); handlerChanged(handlerType.name, defaultParams)
}; }
const onParameterChanged = index => value => { const onParameterChanged = index => value => {
const newParams = [...parameters]; const newParams = [...parameters]
newParams[index].value = value; newParams[index].value = value
handlerChanged(handlerType, newParams); handlerChanged(handlerType, newParams)
}; }
</script> </script>
<div class="type-selector-container {newHandler && 'new-handler'}">
<div class="handler-controls">
<div class="handler-option">
<span>Action</span>
<Select value={handlerType} on:change={handlerTypeChanged}>
<option />
{#each eventOptions as option}
<option value={option.name}>{option.name}</option>
{/each}
</Select>
</div>
{#if parameters}
{#each parameters as param, idx}
<div class="handler-option">
<span>{param.name}</span>
<StateBindingControl
onChanged={onParameterChanged(idx)}
value={param.value} />
</div>
{/each}
{/if}
</div>
<div class="event-action-button">
{#if parameters.length > 0}
{#if newHandler}
<PlusButton on:click={onCreate} />
{:else}
<IconButton icon="x" on:click={onRemoved} />
{/if}
{/if}
</div>
</div>
<style> <style>
.type-selector-container { .type-selector-container {
display: flex; display: flex;
@ -113,36 +146,3 @@
margin-bottom: 5px; margin-bottom: 5px;
} }
</style> </style>
<div class="type-selector-container {newHandler && 'new-handler'}">
<div class="handler-controls">
<div class="handler-option">
<span>Action</span>
<Select value={handlerType} on:change={handlerTypeChanged}>
<option />
{#each eventOptions as option}
<option value={option.name}>{option.name}</option>
{/each}
</Select>
</div>
{#if parameters}
{#each parameters as param, idx}
<div class="handler-option">
<span>{param.name}</span>
<StateBindingControl
onChanged={onParameterChanged(idx)}
value={param.value} />
</div>
{/each}
{/if}
</div>
<div class="event-action-button">
{#if parameters.length > 0}
{#if newHandler}
<PlusButton on:click={onCreate} />
{:else}
<IconButton icon="x" on:click={onRemoved} />
{/if}
{/if}
</div>
</div>

92
packages/builder/src/userInterface/LayoutEditor.svelte

@ -1,74 +1,74 @@
<script> <script>
import InputGroup from '../common/Inputs/InputGroup.svelte'; import InputGroup from "../common/Inputs/InputGroup.svelte"
export let onStyleChanged = () => {}; export let onStyleChanged = () => {}
export let componentInfo; export let componentInfo
const tbrl = [ const tbrl = [
{ placeholder: 'T' }, { placeholder: "T" },
{ placeholder: 'R' }, { placeholder: "R" },
{ placeholder: 'B' }, { placeholder: "B" },
{ placeholder: 'L' } { placeholder: "L" },
];
const se = [
{ placeholder: 'START' },
{ placeholder: 'END' },
] ]
const single = [{ placeholder: '' }]; const se = [{ placeholder: "START" }, { placeholder: "END" }]
const single = [{ placeholder: "" }]
$: layout = { ...componentInfo._styles.position, ...componentInfo._styles.layout }; $: layout = {
...componentInfo._styles.position,
...componentInfo._styles.layout,
}
$: layouts = { $: layouts = {
templaterows: ['Grid Rows', single], templaterows: ["Grid Rows", single],
templatecolumns: ['Grid Columns', single], templatecolumns: ["Grid Columns", single],
}; }
$: positions = { $: positions = {
column: ['Column', se], column: ["Column", se],
row: ['Row', se], row: ["Row", se],
}; }
$: spacing = { $: spacing = {
margin: ['Margin', tbrl, 'small'], margin: ["Margin", tbrl, "small"],
padding: ['Padding', tbrl, 'small'] padding: ["Padding", tbrl, "small"],
}; }
$: zindex = { $: zindex = {
zindex: ['Z-Index', single] zindex: ["Z-Index", single],
} }
const newValue = n => Array(n).fill(''); const newValue = n => Array(n).fill("")
</script> </script>
<h3>Styles</h3> <h3>Styles</h3>
<h4>Positioning</h4> <h4>Positioning</h4>
<div class="layout-pos"> <div class="layout-pos">
{#each Object.entries(layouts) as [key, [name, meta, size]]} {#each Object.entries(layouts) as [key, [name, meta, size]]}
<div class="grid"> <div class="grid">
<h5>{name}:</h5> <h5>{name}:</h5>
<InputGroup onStyleChanged={_value => onStyleChanged('layout',key, _value)} <InputGroup
values={layout[key] || newValue(meta.length)} onStyleChanged={_value => onStyleChanged('layout', key, _value)}
{meta} values={layout[key] || newValue(meta.length)}
{size} {meta}
type="text"/> {size}
type="text" />
</div> </div>
{/each} {/each}
</div> </div>
<h4>Positioning</h4> <h4>Positioning</h4>
<div class="layout-pos"> <div class="layout-pos">
{#each Object.entries(positions) as [key, [name, meta, size]]} {#each Object.entries(positions) as [key, [name, meta, size]]}
<div class="grid"> <div class="grid">
<h5>{name}:</h5> <h5>{name}:</h5>
<InputGroup onStyleChanged={_value => onStyleChanged('position',key, _value)} <InputGroup
values={layout[key] || newValue(meta.length)} onStyleChanged={_value => onStyleChanged('position', key, _value)}
{meta} values={layout[key] || newValue(meta.length)}
{size} /> {meta}
{size} />
</div> </div>
{/each} {/each}
</div> </div>
@ -78,10 +78,11 @@
{#each Object.entries(spacing) as [key, [name, meta, size]]} {#each Object.entries(spacing) as [key, [name, meta, size]]}
<div class="grid"> <div class="grid">
<h5>{name}:</h5> <h5>{name}:</h5>
<InputGroup onStyleChanged={_value => onStyleChanged('position', key, _value)} <InputGroup
values={layout[key] || newValue(meta.length)} onStyleChanged={_value => onStyleChanged('position', key, _value)}
{meta} values={layout[key] || newValue(meta.length)}
{size} /> {meta}
{size} />
</div> </div>
{/each} {/each}
</div> </div>
@ -91,10 +92,11 @@
{#each Object.entries(zindex) as [key, [name, meta, size]]} {#each Object.entries(zindex) as [key, [name, meta, size]]}
<div class="grid"> <div class="grid">
<h5>{name}:</h5> <h5>{name}:</h5>
<InputGroup onStyleChanged={_value => onStyleChanged('position', key, _value)} <InputGroup
values={layout[key] || newValue(meta.length)} onStyleChanged={_value => onStyleChanged('position', key, _value)}
{meta} values={layout[key] || newValue(meta.length)}
{size} /> {meta}
{size} />
</div> </div>
{/each} {/each}
</div> </div>

193
packages/builder/src/userInterface/NewComponent.svelte

@ -1,111 +1,106 @@
<script> <script>
import ComponentSelector from "./ComponentSelector.svelte"; import ComponentSelector from "./ComponentSelector.svelte"
import { store } from "../builderStore"; import { store } from "../builderStore"
import PropsView from "./PropsView.svelte"; import PropsView from "./PropsView.svelte"
import Textbox from "../common/Textbox.svelte"; import Textbox from "../common/Textbox.svelte"
import Button from "../common/Button.svelte"; import Button from "../common/Button.svelte"
import ButtonGroup from "../common/ButtonGroup.svelte"; import ButtonGroup from "../common/ButtonGroup.svelte"
import { pipe } from "../common/core"; import { pipe } from "../common/core"
import UIkit from "uikit"; import UIkit from "uikit"
import { isRootComponent } from "./pagesParsing/searchComponents"; import { isRootComponent } from "./pagesParsing/searchComponents"
import { splitName } from "./pagesParsing/splitRootComponentName.js" import { splitName } from "./pagesParsing/splitRootComponentName.js"
import { import { find, filter, some, map, includes } from "lodash/fp"
find, filter, some, map, includes import { assign } from "lodash"
} from "lodash/fp";
import { assign } from "lodash"; export const show = () => {
UIkit.modal(componentSelectorModal).show()
}
export const show = () => {
UIkit.modal(componentSelectorModal).show(); let componentSelectorModal
} let layoutComponents
let layoutComponent
let componentSelectorModal; let screens
let layoutComponents; let name = ""
let layoutComponent; let saveAttempted = false
let screens;
let name=""; store.subscribe(s => {
let saveAttempted=false; layoutComponents = pipe(s.components, [
filter(c => c.container),
store.subscribe(s => { map(c => ({ name: c.name, ...splitName(c.name) })),
layoutComponents = pipe(s.components, [ ])
filter(c => c.container),
map(c => ({name:c.name, ...splitName(c.name)})) layoutComponent = layoutComponent
]); ? find(c => c.name === layoutComponent.name)(layoutComponents)
: layoutComponents[0]
layoutComponent = layoutComponent
? find(c => c.name === layoutComponent.name)(layoutComponents) screens = s.screens
: layoutComponents[0]; })
screens = s.screens; const save = () => {
}); saveAttempted = true
const save = () => { const isValid =
saveAttempted = true; name.length > 0 && !screenNameExists(name) && layoutComponent
const isValid = name.length > 0 if (!isValid) return
&& !screenNameExists(name)
&& layoutComponent; store.createScreen(name, layoutComponent.name)
UIkit.modal(componentSelectorModal).hide()
if(!isValid) return; }
store.createScreen(name, layoutComponent.name); const cancel = () => {
UIkit.modal(componentSelectorModal).hide(); UIkit.modal(componentSelectorModal).hide()
} }
const cancel = () => { const screenNameExists = name =>
UIkit.modal(componentSelectorModal).hide(); some(s => s.name.toLowerCase() === name.toLowerCase())(screens)
}
const screenNameExists = (name) =>
some(s => s.name.toLowerCase() === name.toLowerCase())(screens)
</script> </script>
<div bind:this={componentSelectorModal} id="new-component-modal" uk-modal> <div bind:this={componentSelectorModal} id="new-component-modal" uk-modal>
<div class="uk-modal-dialog" uk-overflow-auto> <div class="uk-modal-dialog" uk-overflow-auto>
<div class="uk-modal-header"> <div class="uk-modal-header">
<h1>New Screen</h1> <h1>New Screen</h1>
</div> </div>
<div class="uk-modal-body uk-form-horizontal"> <div class="uk-modal-body uk-form-horizontal">
<div class="uk-margin"> <div class="uk-margin">
<label class="uk-form-label">Name</label> <label class="uk-form-label">Name</label>
<div class="uk-form-controls"> <div class="uk-form-controls">
<input class="uk-input uk-form-small" <input
class:uk-form-danger={saveAttempted && (name.length === 0 || screenNameExists(name))} class="uk-input uk-form-small"
bind:value={name} > class:uk-form-danger={saveAttempted && (name.length === 0 || screenNameExists(name))}
</div> bind:value={name} />
</div> </div>
</div>
<div class="uk-margin">
<label class="uk-form-label">Layout Component</label> <div class="uk-margin">
<div class="uk-form-controls"> <label class="uk-form-label">Layout Component</label>
<select class="uk-select uk-form-small" <div class="uk-form-controls">
bind:value={layoutComponent} <select
class:uk-form-danger={saveAttempted && !layoutComponent}> class="uk-select uk-form-small"
{#each layoutComponents as comp} bind:value={layoutComponent}
<option value={comp}> class:uk-form-danger={saveAttempted && !layoutComponent}>
{comp.componentName} - {comp.libName} {#each layoutComponents as comp}
</option> <option value={comp}>
{/each} {comp.componentName} - {comp.libName}
</select> </option>
</div> {/each}
</div> </select>
<ButtonGroup style="float: right;">
<Button color="primary" grouped on:click={save}>Create Screen</Button>
<Button color="tertiary" grouped on:click={cancel}>Cancel</Button>
</ButtonGroup>
</div> </div>
</div>
<ButtonGroup style="float: right;">
<Button color="primary" grouped on:click={save}>Create Screen</Button>
<Button color="tertiary" grouped on:click={cancel}>Cancel</Button>
</ButtonGroup>
</div> </div>
</div>
</div> </div>
<style> <style>
h1 { h1 {
font-size:1.2em; font-size: 1.2em;
} }
</style> </style>

101
packages/builder/src/userInterface/PageView.svelte

@ -1,63 +1,66 @@
<script> <script>
import Textbox from "../common/Textbox.svelte"
import Dropdown from "../common/Dropdown.svelte"
import Button from "../common/Button.svelte"
import { store } from "../builderStore"
import { isRootComponent } from "./pagesParsing/searchComponents"
import { pipe } from "../common/core"
import { filter, find, concat } from "lodash/fp"
import Textbox from "../common/Textbox.svelte"; const notSeletedComponent = { name: "(none selected)" }
import Dropdown from "../common/Dropdown.svelte";
import Button from "../common/Button.svelte"; $: page = $store.pages[$store.currentPageName]
import { store } from "../builderStore"; $: title = page.index.title
import { isRootComponent } from "./pagesParsing/searchComponents"; $: components = pipe($store.components, [
import { pipe } from "../common/core"; filter(store => !isRootComponent($store)),
import { concat([notSeletedComponent]),
filter, find, concat ])
} from "lodash/fp"; $: entryComponent =
find(c => c.name === page.appBody)(components) || notSeletedComponent
const notSeletedComponent = {name:"(none selected)"};
const save = () => {
$: page = $store.pages[$store.currentPageName]; if (!title || !entryComponent || entryComponent === notSeletedComponent)
$: title = page.index.title; return
$: components = pipe($store.components, [ const page = {
filter(store => !isRootComponent($store)), index: {
concat([notSeletedComponent]) title,
]); },
$: entryComponent = find(c => c.name === page.appBody)(components) || notSeletedComponent; appBody: entryComponent.name,
const save = () => {
if(!title || !entryComponent || entryComponent === notSeletedComponent) return;
const page = {
index: {
title
},
appBody: entryComponent.name,
}
store.savePage(page);
} }
store.savePage(page)
}
</script> </script>
<div class="root"> <div class="root">
<h3>{$store.currentPageName}</h3> <h3>{$store.currentPageName}</h3>
<form class="uk-form-horizontal"> <form class="uk-form-horizontal">
<Textbox bind:text={title} label="Title" hasError={!title}/> <Textbox bind:text={title} label="Title" hasError={!title} />
<div class="help-text">The title of your page, displayed in the bowser tab</div> <div class="help-text">
<Dropdown label="App Entry Component" The title of your page, displayed in the bowser tab
options={components} </div>
bind:selected={entryComponent} <Dropdown
textMember={(v) => v.name} /> label="App Entry Component"
options={components}
bind:selected={entryComponent}
textMember={v => v.name} />
<div class="help-text">The component that will be loaded into the body of the page</div> <div class="help-text">
<div style="margin-top: 20px"></div> The component that will be loaded into the body of the page
<Button on:click={save}>Save</Button> </div>
</form> <div style="margin-top: 20px" />
<Button on:click={save}>Save</Button>
</form>
</div> </div>
<style> <style>
.root { .root {
padding: 15px; padding: 15px;
} }
.help-text { .help-text {
color: var(--slate); color: var(--slate);
font-size: 10pt; font-size: 10pt;
} }
</style> </style>

121
packages/builder/src/userInterface/PagesList.svelte

@ -1,73 +1,80 @@
<script> <script>
import { store } from "../builderStore"; import { store } from "../builderStore"
import getIcon from "../common/icon"; import getIcon from "../common/icon"
const getPage = (s, name) => { const getPage = (s, name) => {
const props = s.pages[name]; const props = s.pages[name]
return ({name, props}); return { name, props }
} }
const pages = [{ const pages = [
title: 'Main', {
id: 'main' title: "Main",
}, { id: "main",
title: 'Login', },
id: 'unauthenticated' {
}] title: "Login",
id: "unauthenticated",
},
]
store.setCurrentPage('main') store.setCurrentPage("main")
</script> </script>
<div class="root"> <div class="root">
<select id="page" name="select" on:change={({target}) => store.setCurrentPage(target.value)}> <select
id="page"
name="select"
on:change={({ target }) => store.setCurrentPage(target.value)}>
{#each pages as {title, id}} {#each pages as { title, id }}
<option value="{id}">Page: {title}</option> <option value={id}>Page: {title}</option>
{/each} {/each}
</select> </select>
<span class="arrow">{@html getIcon("chevron-down","24")}</span> <span class="arrow">
{@html getIcon('chevron-down', '24')}
</span>
</div> </div>
<style> <style>
.root { .root {
padding-bottom: 10px; padding-bottom: 10px;
font-size: .9rem; font-size: 0.9rem;
color: var(--secondary50); color: var(--secondary50);
font-weight: bold; font-weight: bold;
position: relative; position: relative;
} }
select {
display: block;
font-size: 16px;
font-family: sans-serif;
font-weight: 700;
color: #444;
line-height: 1.3;
padding: 1em 2.6em 0.9em 1.4em;
width: 100%;
max-width: 100%;
box-sizing: border-box;
margin: 0;
border: none;
border-radius: 0.5em;
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
background-color: #fafafa;
}
select { .arrow {
display: block; position: absolute;
font-size: 16px; right: 10px;
font-family: sans-serif; top: 0;
font-weight: 700; bottom: 0;
color: #444; margin: auto;
line-height: 1.3; width: 30px;
padding: 1em 2.6em 0.9em 1.4em; height: 30px;
width: 100%; pointer-events: none;
max-width: 100%; color: var(--primary100);
box-sizing: border-box; }
margin: 0;
border: none;
border-radius: .5em;
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
background-color: #fafafa;
}
.arrow {
position: absolute;
right: 10px;
top: 0;
bottom: 0;
margin: auto;
width: 30px;
height: 30px;
pointer-events: none;
color: var(--primary100);
}
</style> </style>

86
packages/builder/src/userInterface/PropControl.svelte

@ -1,52 +1,50 @@
<script> <script>
import Checkbox from "../common/Checkbox.svelte"; import Checkbox from "../common/Checkbox.svelte"
import Textbox from "../common/Textbox.svelte"; import Textbox from "../common/Textbox.svelte"
import Dropdown from "../common/Dropdown.svelte"; import Dropdown from "../common/Dropdown.svelte"
import StateBindingControl from "./StateBindingControl.svelte"; import StateBindingControl from "./StateBindingControl.svelte"
export let setProp = () => {}; export let setProp = () => {}
export let index; export let index
export let prop_name; export let prop_name
export let prop_value; export let prop_value
export let prop_type = {}; export let prop_type = {}
$: isOdd = (index % 2 !== 0); $: isOdd = index % 2 !== 0
const setComponentProp = (props) => { const setComponentProp = props => {
setProp(propDef.____name, props); setProp(propDef.____name, props)
} }
</script> </script>
<div class="root">
<div class="root" > {#if prop_type !== 'event'}
{#if prop_type !== "event" }
<h5>{prop_name}</h5> <h5>{prop_name}</h5>
<StateBindingControl value={prop_value} <StateBindingControl
type={prop_type} value={prop_value}
options={prop_type.options} type={prop_type}
onChanged={v => setProp(prop_name, v)}/> options={prop_type.options}
onChanged={v => setProp(prop_name, v)} />
{/if} {/if}
</div> </div>
<style> <style>
.root { .root {
height: 40px; height: 40px;
margin-bottom: 15px; margin-bottom: 15px;
display: grid; display: grid;
grid-template-rows: 1fr; grid-template-rows: 1fr;
grid-template-columns: 70px 1fr; grid-template-columns: 70px 1fr;
grid-gap: 10px; grid-gap: 10px;
} }
h5 { h5 {
word-wrap: break-word; word-wrap: break-word;
font-size: 12px; font-size: 12px;
font-weight: 700; font-weight: 700;
color: #163057; color: #163057;
opacity: 0.6; opacity: 0.6;
padding-top: 12px; padding-top: 12px;
margin-bottom: 0; margin-bottom: 0;
} }
</style> </style>

116
packages/builder/src/userInterface/PropsView.svelte

@ -1,70 +1,72 @@
<script> <script>
import { some, includes, filter } from "lodash/fp"; import { some, includes, filter } from "lodash/fp"
import Textbox from "../common/Textbox.svelte"; import Textbox from "../common/Textbox.svelte"
import Dropdown from "../common/Dropdown.svelte"; import Dropdown from "../common/Dropdown.svelte"
import PropControl from "./PropControl.svelte"; import PropControl from "./PropControl.svelte"
import IconButton from "../common/IconButton.svelte"; import IconButton from "../common/IconButton.svelte"
export let componentInfo; export let componentInfo
export let onPropChanged = () => {}; export let onPropChanged = () => {}
export let components; export let components
let errors = []; let errors = []
let props = {}; let props = {}
const props_to_ignore = ['_component','_children', '_styles', '_code', '_id']; const props_to_ignore = ["_component", "_children", "_styles", "_code", "_id"]
$: propDefs = componentInfo && Object.entries(componentInfo).filter(([name])=> !props_to_ignore.includes(name)); $: propDefs =
componentInfo &&
function find_type(prop_name) { Object.entries(componentInfo).filter(
if(!componentInfo._component) return; ([name]) => !props_to_ignore.includes(name)
return components.find(({name}) => name === componentInfo._component).props[prop_name]; )
}
function find_type(prop_name) {
let setProp = (name, value) => { if (!componentInfo._component) return
onPropChanged(name, value); return components.find(({ name }) => name === componentInfo._component)
} .props[prop_name]
}
const fieldHasError = (propName) =>
some(e => e.propName === propName)(errors); let setProp = (name, value) => {
onPropChanged(name, value)
}
const fieldHasError = propName => some(e => e.propName === propName)(errors)
</script> </script>
<div class="root"> <div class="root">
<form class="uk-form-stacked form-root"> <form class="uk-form-stacked form-root">
{#each propDefs as [prop_name, prop_value], index} {#each propDefs as [prop_name, prop_value], index}
<div class="prop-container">
<div class="prop-container">
<PropControl {setProp} <PropControl
{prop_name} {setProp}
{prop_value} {prop_name}
prop_type={find_type(prop_name)} {prop_value}
{index} prop_type={find_type(prop_name)}
disabled={false} /> {index}
disabled={false} />
</div> </div>
{/each}
{/each} </form>
</form>
</div> </div>
<style> <style>
.root { .root {
font-size:10pt; font-size: 10pt;
width: 100%; width: 100%;
} }
.form-root { .form-root {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
} }
.prop-container { .prop-container {
flex: 1 1 auto; flex: 1 1 auto;
min-width: 250px; min-width: 250px;
} }
</style> </style>

253
packages/builder/src/userInterface/SettingsView.svelte

@ -1,137 +1,134 @@
<script> <script>
import { store } from "../builderStore"; import { store } from "../builderStore"
import Textbox from "../common/Textbox.svelte"; import Textbox from "../common/Textbox.svelte"
import Button from "../common/Button.svelte"; import Button from "../common/Button.svelte"
import IconButton from "../common/IconButton.svelte"; import IconButton from "../common/IconButton.svelte"
import { libraryDependencies } from "./pagesParsing/findDependencies"; import { libraryDependencies } from "./pagesParsing/findDependencies"
import UIkit from "uikit"; import UIkit from "uikit"
let addNewLib = ""; let addNewLib = ""
let addNewStylesheet = ""; let addNewStylesheet = ""
let modalElement; let modalElement
$: components = $store.components; $: components = $store.components
const removeLibrary = lib => { const removeLibrary = lib => {
const dependencies = libraryDependencies(components, lib); const dependencies = libraryDependencies(components, lib)
if(dependencies.length > 0) return; if (dependencies.length > 0) return
store.removeComponentLibrary(lib); store.removeComponentLibrary(lib)
} }
const addLib = () => { const addLib = () => {
store.addComponentLibrary(addNewLib) store.addComponentLibrary(addNewLib).then(() => {
.then(() => { addNewLib = ""
addNewLib = ""; })
}); }
}
const removeStylesheet = stylesheet => {
const removeStylesheet = stylesheet => { store.removeStylesheet(stylesheet)
store.removeStylesheet(stylesheet); }
}
const addStylesheet = () => {
const addStylesheet = () => { if (addNewStylesheet) store.addStylesheet(addNewStylesheet)
if(addNewStylesheet) }
store.addStylesheet(addNewStylesheet);
} export const close = () => {
UIkit.modal(modalElement).hide()
export const close = () => { }
UIkit.modal(modalElement).hide();
} export const show = () => {
UIkit.modal(modalElement).show()
export const show = () => { }
UIkit.modal(modalElement).show();
}
</script> </script>
<div bind:this={modalElement} id="new-component-modal" uk-modal> <div bind:this={modalElement} id="new-component-modal" uk-modal>
<div class="uk-modal-dialog"> <div class="uk-modal-dialog">
<div class="uk-modal-header header"> <div class="uk-modal-header header">
<div>Settings</div> <div>Settings</div>
<div> <div>
<IconButton icon="x" <IconButton icon="x" on:click={close} />
on:click={close}/> </div>
</div> </div>
</div>
<div class="uk-modal-body uk-form-horizontal">
<div class="uk-modal-body uk-form-horizontal">
<div class="section-container">
<div class="section-container"> <p>
<p>Component Libraries Component Libraries
<span> <span>
<input bind:value={addNewLib} /> <input bind:value={addNewLib} />
<Button color="primary-outline" <Button color="primary-outline" on:click={addLib}>Add</Button>
on:click={addLib}>Add</Button> </span>
</span> </p>
</p> {#each $store.pages.componentLibraries as lib}
{#each $store.pages.componentLibraries as lib} <div>
<div> <span class="row-text">{lib}</span>
<span class="row-text">{lib}</span> <IconButton icon="x" on:click={() => removeLibrary(lib)} />
<IconButton icon="x" </div>
on:click={() => removeLibrary(lib)}/> {/each}
</div> </div>
{/each}
</div> <div class="section-container">
<p>
Stylesheets
<div class="section-container"> <span>
<p>Stylesheets <input bind:value={addNewStylesheet} />
<span> <Button color="primary-outline" on:click={addStylesheet}>
<input bind:value={addNewStylesheet} /> Add
<Button color="primary-outline" </Button>
on:click={addStylesheet} >Add</Button> </span>
</span> </p>
</p> {#each $store.pages.stylesheets as stylesheet}
{#each $store.pages.stylesheets as stylesheet} <div>
<div> <span class="row-text">{stylesheet}</span>
<span class="row-text">{stylesheet}</span> <IconButton
<IconButton icon="x" icon="x"
on:click={() => removeStylesheet(stylesheet)}/> on:click={() => removeStylesheet(stylesheet)} />
</div> </div>
{/each} {/each}
</div> </div>
</div>
</div> </div>
</div>
</div> </div>
<style> <style>
.section-container { .section-container {
padding: 15px; padding: 15px;
border-style: dotted; border-style: dotted;
border-width: 1px; border-width: 1px;
border-color: var(--lightslate); border-color: var(--lightslate);
border-radius: 2px; border-radius: 2px;
} }
.section-container:nth-child(1) { .section-container:nth-child(1) {
margin-bottom: 15px; margin-bottom: 15px;
} }
.row-text { .row-text {
margin-right: 15px; margin-right: 15px;
color: var(--primary100); color: var(--primary100);
} }
input { input {
margin-right: 15px; margin-right: 15px;
} }
p > span { p > span {
margin-left: 30px; margin-left: 30px;
} }
.header { .header {
display: grid; display: grid;
grid-template-columns: [title] 1fr [icon] auto; grid-template-columns: [title] 1fr [icon] auto;
} }
.header > div:nth-child(1) { .header > div:nth-child(1) {
grid-column-start: title; grid-column-start: title;
} }
.header > div:nth-child(2) { .header > div:nth-child(2) {
grid-column-start: icon; grid-column-start: icon;
} }
</style> </style>

295
packages/builder/src/userInterface/StateBindingControl.svelte

@ -1,163 +1,160 @@
<script> <script>
import IconButton from "../common/IconButton.svelte"; import IconButton from "../common/IconButton.svelte"
import Input from "../common/Input.svelte"; import Input from "../common/Input.svelte"
import { import { isBinding, getBinding, setBinding } from "../common/binding"
isBinding, getBinding, setBinding
} from "../common/binding"; export let value = ""
export let onChanged = () => {}
export let value=""; export let type = ""
export let onChanged= () => {}; export let options = []
export let type="";
export let options=[]; let isBound = false
let bindingPath = ""
let isBound=false; let bindingFallbackValue = ""
let bindingPath=""; let bindingSource = "store"
let bindingFallbackValue=""; let isExpanded = false
let bindingSource="store"; let forceIsBound = false
let isExpanded = false; let canOnlyBind = false
let forceIsBound = false;
let canOnlyBind = false; $: {
canOnlyBind = type === "state"
$: { if (!forceIsBound && canOnlyBind) forceIsBound = true
canOnlyBind = type === "state";
if(!forceIsBound && canOnlyBind) isBound = forceIsBound || isBinding(value)
forceIsBound = true;
if (isBound) {
isBound= forceIsBound || isBinding(value); const binding = getBinding(value)
bindingPath = binding.path
if(isBound) { bindingFallbackValue = binding.fallback
const binding = getBinding(value); bindingSource = binding.source || "store"
bindingPath= binding.path; } else {
bindingFallbackValue= binding.fallback; bindingPath = ""
bindingSource = binding.source || "store"; bindingFallbackValue = ""
} else { bindingSource = "store"
bindingPath="";
bindingFallbackValue="";
bindingSource="store";
}
} }
}
const clearBinding = () => { const clearBinding = () => {
forceIsBound = false; forceIsBound = false
onChanged(""); onChanged("")
} }
const bind = (path, fallback, source) => {
if(!path) {
clearBinding("");
return;
}
const binding = setBinding({path, fallback, source});
onChanged(binding);
}
const setBindingPath = ev => { const bind = (path, fallback, source) => {
forceIsBound = canOnlyBind; if (!path) {
bind(ev.target.value, bindingFallbackValue, bindingSource) clearBinding("")
return
} }
const binding = setBinding({ path, fallback, source })
const setBindingFallback = ev => { onChanged(binding)
bind(bindingPath, ev.target.value, bindingSource); }
}
const setBindingPath = ev => {
const setBindingSource = ev => { forceIsBound = canOnlyBind
bind(bindingPath, bindingFallbackValue, ev.target.value); bind(ev.target.value, bindingFallbackValue, bindingSource)
} }
const setBindingFallback = ev => {
bind(bindingPath, ev.target.value, bindingSource)
}
const setBindingSource = ev => {
bind(bindingPath, bindingFallbackValue, ev.target.value)
}
</script> </script>
{#if isBound} {#if isBound}
<div> <div>
<div class="bound-header"> <div class="bound-header">
<div>{isExpanded ? "" : bindingPath}</div> <div>{isExpanded ? '' : bindingPath}</div>
<IconButton icon={isExpanded ? "chevron-up" : "chevron-down"} <IconButton
size="12" icon={isExpanded ? 'chevron-up' : 'chevron-down'}
on:click={() => isExpanded=!isExpanded}/> size="12"
{#if !canOnlyBind} on:click={() => (isExpanded = !isExpanded)} />
<IconButton icon="trash" {#if !canOnlyBind}
size="12" <IconButton icon="trash" size="12" on:click={clearBinding} />
on:click={clearBinding}/> {/if}
{/if}
</div>
{#if isExpanded}
<div>
<div class="binding-prop-label">Binding Path</div>
<input class="uk-input uk-form-small"
value={bindingPath}
on:change={setBindingPath} >
<div class="binding-prop-label">Fallback Value</div>
<input class="uk-input uk-form-small"
value={bindingFallbackValue}
on:change={setBindingFallback} >
<div class="binding-prop-label">Binding Source</div>
<select class="uk-select uk-form-small"
value={bindingSource}
on:change={setBindingSource}>
<option>store</option>
<option>context</option>
</select>
</div>
{/if}
</div> </div>
{#if isExpanded}
<div>
<div class="binding-prop-label">Binding Path</div>
<input
class="uk-input uk-form-small"
value={bindingPath}
on:change={setBindingPath} />
<div class="binding-prop-label">Fallback Value</div>
<input
class="uk-input uk-form-small"
value={bindingFallbackValue}
on:change={setBindingFallback} />
<div class="binding-prop-label">Binding Source</div>
<select
class="uk-select uk-form-small"
value={bindingSource}
on:change={setBindingSource}>
<option>store</option>
<option>context</option>
</select>
</div>
{/if}
</div>
{:else} {:else}
<div class="unbound-container"> <div class="unbound-container">
{#if type === "bool"} {#if type === 'bool'}
<div> <div>
<IconButton icon={value == true ? "check-square" : "square"} <IconButton
size="19" icon={value == true ? 'check-square' : 'square'}
on:click={() => onChanged(!value)} /> size="19"
</div> on:click={() => onChanged(!value)} />
{:else if type === "options"} </div>
<select class="uk-select uk-form-small" {:else if type === 'options'}
value={value} <select
on:change={ev => onChanged(ev.target.value)}> class="uk-select uk-form-small"
{#each options as option} {value}
<option value={option}>{option}</option> on:change={ev => onChanged(ev.target.value)}>
{/each} {#each options as option}
</select> <option value={option}>{option}</option>
{:else} {/each}
<Input </select>
on:change={ev => onChanged(ev.target.value)} {:else}
bind:value={value} /> <Input on:change={ev => onChanged(ev.target.value)} bind:value />
{/if} {/if}
</div> </div>
{/if} {/if}
<style> <style>
.unbound-container { .unbound-container {
display:flex; display: flex;
} }
.bound-header { .bound-header {
display: flex; display: flex;
} }
.bound-header > div:nth-child(1) { .bound-header > div:nth-child(1) {
flex: 1 0 auto; flex: 1 0 auto;
width: 30px; width: 30px;
color: var(--secondary50); color: var(--secondary50);
padding-left: 5px; padding-left: 5px;
} }
.binding-prop-label { .binding-prop-label {
color: var(--secondary50); color: var(--secondary50);
} }
input { input {
font-size: 12px; font-size: 12px;
font-weight: 700; font-weight: 700;
color: #163057; color: #163057;
opacity: 0.7; opacity: 0.7;
padding: 5px 10px; padding: 5px 10px;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid #DBDBDB; border: 1px solid #dbdbdb;
border-radius: 2px; border-radius: 2px;
outline: none; outline: none;
} }
</style> </style>

318
packages/builder/src/userInterface/UserInterfaceRoot.svelte

@ -1,180 +1,178 @@
<script> <script>
import ComponentsHierarchy from "./ComponentsHierarchy.svelte"; import ComponentsHierarchy from "./ComponentsHierarchy.svelte"
import PagesList from "./PagesList.svelte" import PagesList from "./PagesList.svelte"
import { store } from "../builderStore"; import { store } from "../builderStore"
import IconButton from "../common/IconButton.svelte"; import IconButton from "../common/IconButton.svelte"
import Modal from "../common/Modal.svelte"; import Modal from "../common/Modal.svelte"
import NewComponent from "./NewComponent.svelte"; import NewComponent from "./NewComponent.svelte"
import CurrentItemPreview from "./CurrentItemPreview.svelte"; import CurrentItemPreview from "./CurrentItemPreview.svelte"
import SettingsView from "./SettingsView.svelte"; import SettingsView from "./SettingsView.svelte"
import PageView from "./PageView.svelte"; import PageView from "./PageView.svelte"
import ComponentsPaneSwitcher from "./ComponentsPaneSwitcher.svelte"; import ComponentsPaneSwitcher from "./ComponentsPaneSwitcher.svelte"
let newComponentPicker; let newComponentPicker
const newComponent = () => { const newComponent = () => {
newComponentPicker.show(); newComponentPicker.show()
} }
let settingsView; let settingsView
const settings = () => { const settings = () => {
settingsView.show(); settingsView.show()
} }
</script> </script>
<div class="root"> <div class="root">
<div class="ui-nav"> <div class="ui-nav">
<div class="pages-list-container"> <div class="pages-list-container">
<div class="nav-group-header"> <div class="nav-group-header">
<span class="navigator-title">Navigator</span> <span class="navigator-title">Navigator</span>
</div> </div>
<div class="nav-items-container"> <div class="nav-items-container">
<PagesList /> <PagesList />
</div> </div>
</div> </div>
<div class="components-list-container"> <div class="components-list-container">
<div class="nav-group-header"> <div class="nav-group-header">
<span class="components-nav-header">Screens</span>
<div>
<button on:click={newComponent}>+</button>
</div>
</div>
<div class="nav-items-container">
<ComponentsHierarchy components={$store.screens}/>
</div>
</div>
<span class="components-nav-header">Screens</span>
<div>
<button on:click={newComponent}>+</button>
</div>
</div>
<div class="nav-items-container">
<ComponentsHierarchy components={$store.screens} />
</div>
</div> </div>
<div class="preview-pane"> </div>
{#if $store.currentFrontEndType === "screen"}
<CurrentItemPreview />
{:else if $store.currentFrontEndType === "page"}
<PageView />
{/if}
</div>
{#if $store.currentFrontEndType === "screen"} <div class="preview-pane">
<div class="components-pane"> {#if $store.currentFrontEndType === 'screen'}
<ComponentsPaneSwitcher /> <CurrentItemPreview />
</div> {:else if $store.currentFrontEndType === 'page'}
<PageView />
{/if} {/if}
</div>
</div> {#if $store.currentFrontEndType === 'screen'}
<div class="components-pane">
<ComponentsPaneSwitcher />
</div>
{/if}
</div>
<NewComponent bind:this={newComponentPicker}/> <NewComponent bind:this={newComponentPicker} />
<SettingsView bind:this={settingsView} /> <SettingsView bind:this={settingsView} />
<style> <style>
button { button {
cursor: pointer; cursor: pointer;
outline: none; outline: none;
border: none; border: none;
border-radius: 5px; border-radius: 5px;
background: var(--background-button); background: var(--background-button);
width: 1.8rem; width: 1.8rem;
height: 1.8rem; height: 1.8rem;
padding-bottom: 10px; padding-bottom: 10px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-size: 1.2rem; font-size: 1.2rem;
font-weight: 700; font-weight: 700;
color: var(--button-text); color: var(--button-text);
} }
.root { .root {
display: grid; display: grid;
grid-template-columns: 290px 1fr 300px; grid-template-columns: 290px 1fr 300px;
height: 100%; height: 100%;
width: 100%; width: 100%;
background: #fafafa; background: #fafafa;
} }
.ui-nav { .ui-nav {
grid-column: 1; grid-column: 1;
background-color: var(--secondary5); background-color: var(--secondary5);
height: 100%; height: 100%;
padding: 0 1.5rem 0rem 1.5rem padding: 0 1.5rem 0rem 1.5rem;
} }
.preview-pane { .preview-pane {
grid-column: 2; grid-column: 2;
margin: 80px 60px; margin: 80px 60px;
background: #fff; background: #fff;
border-radius: 5px; border-radius: 5px;
box-shadow: 0 0px 6px rgba(0,0,0,0.05) box-shadow: 0 0px 6px rgba(0, 0, 0, 0.05);
} }
.components-pane { .components-pane {
grid-column: 3; grid-column: 3;
background-color: var(--secondary5); background-color: var(--secondary5);
min-height: 0px; min-height: 0px;
overflow-y: hidden; overflow-y: hidden;
} }
.components-nav-header { .components-nav-header {
font-size: 0.75rem; font-size: 0.75rem;
color: #999; color: #999;
text-transform: uppercase; text-transform: uppercase;
} }
.nav-group-header { .nav-group-header {
font-size: .9rem; font-size: 0.9rem;
padding-left: 1rem; padding-left: 1rem;
} }
.nav-items-container { .nav-items-container {
padding: 1rem 0rem 0rem 0rem; padding: 1rem 0rem 0rem 0rem;
} }
.nav-group-header { .nav-group-header {
display: flex; display: flex;
padding: 2rem 0 0 0; padding: 2rem 0 0 0;
font-size: .9rem; font-size: 0.9rem;
font-weight: bold; font-weight: bold;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.nav-group-header>div:nth-child(1) { .nav-group-header > div:nth-child(1) {
padding: 0rem .5rem 0rem 0rem; padding: 0rem 0.5rem 0rem 0rem;
vertical-align: bottom; vertical-align: bottom;
grid-column-start: icon; grid-column-start: icon;
margin-right: 5px; margin-right: 5px;
} }
.nav-group-header>span:nth-child(2) { .nav-group-header > span:nth-child(2) {
margin-left:5px; margin-left: 5px;
vertical-align: bottom; vertical-align: bottom;
grid-column-start: title; grid-column-start: title;
margin-top:auto; margin-top: auto;
} }
.nav-group-header>div:nth-child(3) { .nav-group-header > div:nth-child(3) {
vertical-align: bottom; vertical-align: bottom;
grid-column-start: button; grid-column-start: button;
cursor: pointer; cursor: pointer;
color: var(--primary75); color: var(--primary75);
} }
.nav-group-header>div:nth-child(3):hover { .nav-group-header > div:nth-child(3):hover {
color: var(--primary75); color: var(--primary75);
} }
.navigator-title { .navigator-title {
text-transform: uppercase; text-transform: uppercase;
font-weight: 400; font-weight: 400;
color: #999; color: #999;
} }
</style> </style>

42
packages/core/test/recordApi.validate.spec.js

@ -49,7 +49,7 @@ describe("recordApi > validate", () => {
}) })
it("should return error when string field is beyond maxLength", async () => { it("should return error when string field is beyond maxLength", async () => {
const withFieldWithMaxLength = (hierarchy) => { const withFieldWithMaxLength = hierarchy => {
const surname = find( const surname = find(
hierarchy.customerRecord.fields, hierarchy.customerRecord.fields,
f => f.name === "surname" f => f.name === "surname"
@ -61,9 +61,7 @@ describe("recordApi > validate", () => {
withFields, withFields,
withFieldWithMaxLength withFieldWithMaxLength
) )
const { recordApi } = await setupApphierarchy( const { recordApi } = await setupApphierarchy(hierarchyCreator)
hierarchyCreator
)
const record = recordApi.getNew("/customers", "customer") const record = recordApi.getNew("/customers", "customer")
record.surname = "more than 5 chars" record.surname = "more than 5 chars"
@ -74,7 +72,7 @@ describe("recordApi > validate", () => {
}) })
it("should return error when number field is > maxValue", async () => { it("should return error when number field is > maxValue", async () => {
const withFieldWithMaxLength = (hierarchy) => { const withFieldWithMaxLength = hierarchy => {
const age = find(hierarchy.customerRecord.fields, f => f.name === "age") const age = find(hierarchy.customerRecord.fields, f => f.name === "age")
age.typeOptions.maxValue = 10 age.typeOptions.maxValue = 10
age.typeOptions.minValue = 5 age.typeOptions.minValue = 5
@ -84,9 +82,7 @@ describe("recordApi > validate", () => {
withFields, withFields,
withFieldWithMaxLength withFieldWithMaxLength
) )
const { recordApi } = await setupApphierarchy( const { recordApi } = await setupApphierarchy(hierarchyCreator)
hierarchyCreator
)
const tooOldRecord = recordApi.getNew("/customers", "customer") const tooOldRecord = recordApi.getNew("/customers", "customer")
tooOldRecord.age = 11 tooOldRecord.age = 11
@ -97,7 +93,7 @@ describe("recordApi > validate", () => {
}) })
it("should return error when number field is < minValue", async () => { it("should return error when number field is < minValue", async () => {
const withFieldWithMaxLength = (hierarchy) => { const withFieldWithMaxLength = hierarchy => {
const age = find(hierarchy.customerRecord.fields, f => f.name === "age") const age = find(hierarchy.customerRecord.fields, f => f.name === "age")
age.typeOptions.minValue = 5 age.typeOptions.minValue = 5
} }
@ -106,9 +102,7 @@ describe("recordApi > validate", () => {
withFields, withFields,
withFieldWithMaxLength withFieldWithMaxLength
) )
const { recordApi } = await setupApphierarchy( const { recordApi } = await setupApphierarchy(hierarchyCreator)
hierarchyCreator
)
const tooYoungRecord = recordApi.getNew("/customers", "customer") const tooYoungRecord = recordApi.getNew("/customers", "customer")
tooYoungRecord.age = 3 tooYoungRecord.age = 3
@ -128,9 +122,7 @@ describe("recordApi > validate", () => {
withFields, withFields,
withFieldWithMaxLength withFieldWithMaxLength
) )
const { recordApi } = await setupApphierarchy( const { recordApi } = await setupApphierarchy(hierarchyCreator)
hierarchyCreator
)
const record = recordApi.getNew("/customers", "customer") const record = recordApi.getNew("/customers", "customer")
record.age = 3.123 record.age = 3.123
@ -141,7 +133,7 @@ describe("recordApi > validate", () => {
}) })
it("should return error when datetime field is > maxValue", async () => { it("should return error when datetime field is > maxValue", async () => {
const withFieldWithMaxLength = (hierarchy) => { const withFieldWithMaxLength = hierarchy => {
const createddate = find( const createddate = find(
hierarchy.customerRecord.fields, hierarchy.customerRecord.fields,
f => f.name === "createddate" f => f.name === "createddate"
@ -153,9 +145,7 @@ describe("recordApi > validate", () => {
withFields, withFields,
withFieldWithMaxLength withFieldWithMaxLength
) )
const { recordApi } = await setupApphierarchy( const { recordApi } = await setupApphierarchy(hierarchyCreator)
hierarchyCreator
)
const record = recordApi.getNew("/customers", "customer") const record = recordApi.getNew("/customers", "customer")
record.createddate = addHours(new Date(), 1) record.createddate = addHours(new Date(), 1)
@ -166,7 +156,7 @@ describe("recordApi > validate", () => {
}) })
it("should return error when number field is < minValue", async () => { it("should return error when number field is < minValue", async () => {
const withFieldWithMaxLength = (hierarchy) => { const withFieldWithMaxLength = hierarchy => {
const createddate = find( const createddate = find(
hierarchy.customerRecord.fields, hierarchy.customerRecord.fields,
f => f.name === "createddate" f => f.name === "createddate"
@ -178,9 +168,7 @@ describe("recordApi > validate", () => {
withFields, withFields,
withFieldWithMaxLength withFieldWithMaxLength
) )
const { recordApi } = await setupApphierarchy( const { recordApi } = await setupApphierarchy(hierarchyCreator)
hierarchyCreator
)
const record = recordApi.getNew("/customers", "customer") const record = recordApi.getNew("/customers", "customer")
record.createddate = new Date() record.createddate = new Date()
@ -191,7 +179,7 @@ describe("recordApi > validate", () => {
}) })
it("should return error when string IS NOT one of declared values, and only declared values are allowed", async () => { it("should return error when string IS NOT one of declared values, and only declared values are allowed", async () => {
const withFieldWithMaxLength = (hierarchy) => { const withFieldWithMaxLength = hierarchy => {
const surname = find( const surname = find(
hierarchy.customerRecord.fields, hierarchy.customerRecord.fields,
f => f.name === "surname" f => f.name === "surname"
@ -204,9 +192,7 @@ describe("recordApi > validate", () => {
withFields, withFields,
withFieldWithMaxLength withFieldWithMaxLength
) )
const { recordApi } = await setupApphierarchy( const { recordApi } = await setupApphierarchy(hierarchyCreator)
hierarchyCreator
)
const record = recordApi.getNew("/customers", "customer") const record = recordApi.getNew("/customers", "customer")
record.surname = "zeecat" record.surname = "zeecat"
@ -217,7 +203,7 @@ describe("recordApi > validate", () => {
}) })
it("should not return error when string IS one of declared values, and only declared values are allowed", async () => { it("should not return error when string IS one of declared values, and only declared values are allowed", async () => {
const withFieldWithMaxLength = (hierarchy) => { const withFieldWithMaxLength = hierarchy => {
const surname = find( const surname = find(
hierarchy.customerRecord.fields, hierarchy.customerRecord.fields,
f => f.name === "surname" f => f.name === "surname"

6
packages/materialdesign-components/src/Button.svelte

@ -1,8 +1,8 @@
<script> <script>
import "@material/button/mdc-button.scss"; import "@material/button/mdc-button.scss"
export let raised = false; export let raised = false
let c = raised ? "mdc-button mdc-button--raised" : "mdc-button"; let c = raised ? "mdc-button mdc-button--raised" : "mdc-button"
</script> </script>
<button class={c}> <button class={c}>

12
packages/materialdesign-components/src/H1.svelte

@ -1,12 +1,8 @@
<script> <script>
export let text=""; export let text = ""
export let className="" export let className = ""
export let _bb;
export let _bb
</script> </script>
<h1 class={className}> <h1 class={className}>{text}</h1>
{text}
</h1>

32
packages/materialdesign-components/src/Test/TestApp.svelte

@ -1,31 +1,24 @@
<script> <script>
import createApp from "./createApp"; import createApp from "./createApp"
import { props } from "./props"; import { props } from "./props"
let _bb; let _bb
const _appPromise = createApp(); const _appPromise = createApp()
_appPromise.then(a => (_bb = a)); _appPromise.then(a => (_bb = a))
const testProps = props.justAnH1; const testProps = props.justAnH1
const button = props.button; const button = props.button
let currentComponent; let currentComponent
$: { $: {
if (_bb && currentComponent) { if (_bb && currentComponent) {
_bb.hydrateChildren([testProps, button], currentComponent); _bb.hydrateChildren([testProps, button], currentComponent)
} }
} }
</script> </script>
<style>
#current_component {
height: 100%;
width: 100%;
}
</style>
{#await _appPromise} {#await _appPromise}
loading loading
{:then _bb} {:then _bb}
@ -33,3 +26,10 @@
<div id="current_component" bind:this={currentComponent} /> <div id="current_component" bind:this={currentComponent} />
{/await} {/await}
<style>
#current_component {
height: 100%;
width: 100%;
}
</style>

11
packages/server/appPackages/_master/public/main/budibase-client.js

@ -2310,7 +2310,9 @@ var app = (function(exports) {
null != n && null != n &&
!wu(n) !wu(n)
) || ) ||
nn.test(n) || !X.test(n) || (null != t && n in Qu(t)) nn.test(n) ||
!X.test(n) ||
(null != t && n in Qu(t))
) )
} }
function Re(n) { function Re(n) {
@ -12812,7 +12814,8 @@ var app = (function(exports) {
// Non `Object` object instances with different constructors are not equal. // Non `Object` object instances with different constructors are not equal.
if ( if (
objCtor != othCtor && objCtor != othCtor &&
"constructor" in object && "constructor" in other && "constructor" in object &&
"constructor" in other &&
!( !(
typeof objCtor == "function" && typeof objCtor == "function" &&
objCtor instanceof objCtor && objCtor instanceof objCtor &&
@ -13306,7 +13309,9 @@ var app = (function(exports) {
return ( return (
!!length && !!length &&
(type == "number" || (type != "symbol" && reIsUint.test(value))) && (type == "number" || (type != "symbol" && reIsUint.test(value))) &&
value > -1 && value % 1 == 0 && value < length value > -1 &&
value % 1 == 0 &&
value < length
) )
} }

11
packages/server/appPackages/_master/public/unauthenticated/budibase-client.js

@ -2310,7 +2310,9 @@ var app = (function(exports) {
null != n && null != n &&
!wu(n) !wu(n)
) || ) ||
nn.test(n) || !X.test(n) || (null != t && n in Qu(t)) nn.test(n) ||
!X.test(n) ||
(null != t && n in Qu(t))
) )
} }
function Re(n) { function Re(n) {
@ -12812,7 +12814,8 @@ var app = (function(exports) {
// Non `Object` object instances with different constructors are not equal. // Non `Object` object instances with different constructors are not equal.
if ( if (
objCtor != othCtor && objCtor != othCtor &&
"constructor" in object && "constructor" in other && "constructor" in object &&
"constructor" in other &&
!( !(
typeof objCtor == "function" && typeof objCtor == "function" &&
objCtor instanceof objCtor && objCtor instanceof objCtor &&
@ -13306,7 +13309,9 @@ var app = (function(exports) {
return ( return (
!!length && !!length &&
(type == "number" || (type != "symbol" && reIsUint.test(value))) && (type == "number" || (type != "symbol" && reIsUint.test(value))) &&
value > -1 && value % 1 == 0 && value < length value > -1 &&
value % 1 == 0 &&
value < length
) )
} }

11
packages/server/appPackages/testApp/public/main/budibase-client.js

@ -2310,7 +2310,9 @@ var app = (function(exports) {
null != n && null != n &&
!wu(n) !wu(n)
) || ) ||
nn.test(n) || !X.test(n) || (null != t && n in Qu(t)) nn.test(n) ||
!X.test(n) ||
(null != t && n in Qu(t))
) )
} }
function Re(n) { function Re(n) {
@ -12812,7 +12814,8 @@ var app = (function(exports) {
// Non `Object` object instances with different constructors are not equal. // Non `Object` object instances with different constructors are not equal.
if ( if (
objCtor != othCtor && objCtor != othCtor &&
"constructor" in object && "constructor" in other && "constructor" in object &&
"constructor" in other &&
!( !(
typeof objCtor == "function" && typeof objCtor == "function" &&
objCtor instanceof objCtor && objCtor instanceof objCtor &&
@ -13306,7 +13309,9 @@ var app = (function(exports) {
return ( return (
!!length && !!length &&
(type == "number" || (type != "symbol" && reIsUint.test(value))) && (type == "number" || (type != "symbol" && reIsUint.test(value))) &&
value > -1 && value % 1 == 0 && value < length value > -1 &&
value % 1 == 0 &&
value < length
) )
} }

11
packages/server/appPackages/testApp/public/unauthenticated/budibase-client.js

@ -2310,7 +2310,9 @@ var app = (function(exports) {
null != n && null != n &&
!wu(n) !wu(n)
) || ) ||
nn.test(n) || !X.test(n) || (null != t && n in Qu(t)) nn.test(n) ||
!X.test(n) ||
(null != t && n in Qu(t))
) )
} }
function Re(n) { function Re(n) {
@ -12812,7 +12814,8 @@ var app = (function(exports) {
// Non `Object` object instances with different constructors are not equal. // Non `Object` object instances with different constructors are not equal.
if ( if (
objCtor != othCtor && objCtor != othCtor &&
"constructor" in object && "constructor" in other && "constructor" in object &&
"constructor" in other &&
!( !(
typeof objCtor == "function" && typeof objCtor == "function" &&
objCtor instanceof objCtor && objCtor instanceof objCtor &&
@ -13306,7 +13309,9 @@ var app = (function(exports) {
return ( return (
!!length && !!length &&
(type == "number" || (type != "symbol" && reIsUint.test(value))) && (type == "number" || (type != "symbol" && reIsUint.test(value))) &&
value > -1 && value % 1 == 0 && value < length value > -1 &&
value % 1 == 0 &&
value < length
) )
} }

11
packages/server/appPackages/testApp2/public/main/budibase-client.js

@ -2310,7 +2310,9 @@ var app = (function(exports) {
null != n && null != n &&
!wu(n) !wu(n)
) || ) ||
nn.test(n) || !X.test(n) || (null != t && n in Qu(t)) nn.test(n) ||
!X.test(n) ||
(null != t && n in Qu(t))
) )
} }
function Re(n) { function Re(n) {
@ -12812,7 +12814,8 @@ var app = (function(exports) {
// Non `Object` object instances with different constructors are not equal. // Non `Object` object instances with different constructors are not equal.
if ( if (
objCtor != othCtor && objCtor != othCtor &&
"constructor" in object && "constructor" in other && "constructor" in object &&
"constructor" in other &&
!( !(
typeof objCtor == "function" && typeof objCtor == "function" &&
objCtor instanceof objCtor && objCtor instanceof objCtor &&
@ -13306,7 +13309,9 @@ var app = (function(exports) {
return ( return (
!!length && !!length &&
(type == "number" || (type != "symbol" && reIsUint.test(value))) && (type == "number" || (type != "symbol" && reIsUint.test(value))) &&
value > -1 && value % 1 == 0 && value < length value > -1 &&
value % 1 == 0 &&
value < length
) )
} }

11
packages/server/appPackages/testApp2/public/unauthenticated/budibase-client.js

@ -2310,7 +2310,9 @@ var app = (function(exports) {
null != n && null != n &&
!wu(n) !wu(n)
) || ) ||
nn.test(n) || !X.test(n) || (null != t && n in Qu(t)) nn.test(n) ||
!X.test(n) ||
(null != t && n in Qu(t))
) )
} }
function Re(n) { function Re(n) {
@ -12812,7 +12814,8 @@ var app = (function(exports) {
// Non `Object` object instances with different constructors are not equal. // Non `Object` object instances with different constructors are not equal.
if ( if (
objCtor != othCtor && objCtor != othCtor &&
"constructor" in object && "constructor" in other && "constructor" in object &&
"constructor" in other &&
!( !(
typeof objCtor == "function" && typeof objCtor == "function" &&
objCtor instanceof objCtor && objCtor instanceof objCtor &&
@ -13306,7 +13309,9 @@ var app = (function(exports) {
return ( return (
!!length && !!length &&
(type == "number" || (type != "symbol" && reIsUint.test(value))) && (type == "number" || (type != "symbol" && reIsUint.test(value))) &&
value > -1 && value % 1 == 0 && value < length value > -1 &&
value % 1 == 0 &&
value < length
) )
} }

11
packages/standard-components/public/bundle.js

@ -6634,7 +6634,9 @@ var app = (function() {
null != n && null != n &&
!wu(n) !wu(n)
) || ) ||
nn.test(n) || !X.test(n) || (null != t && n in Qu(t)) nn.test(n) ||
!X.test(n) ||
(null != t && n in Qu(t))
) )
} }
function Re(n) { function Re(n) {
@ -16826,7 +16828,8 @@ var app = (function() {
// Non `Object` object instances with different constructors are not equal. // Non `Object` object instances with different constructors are not equal.
if ( if (
objCtor != othCtor && objCtor != othCtor &&
"constructor" in object && "constructor" in other && "constructor" in object &&
"constructor" in other &&
!( !(
typeof objCtor == "function" && typeof objCtor == "function" &&
objCtor instanceof objCtor && objCtor instanceof objCtor &&
@ -17320,7 +17323,9 @@ var app = (function() {
return ( return (
!!length && !!length &&
(type == "number" || (type != "symbol" && reIsUint.test(value))) && (type == "number" || (type != "symbol" && reIsUint.test(value))) &&
value > -1 && value % 1 == 0 && value < length value > -1 &&
value % 1 == 0 &&
value < length
) )
} }

26
packages/standard-components/src/Div.svelte

@ -1,18 +1,14 @@
<script> <script>
export let _children = []
export let _children = []; export let className = ""
export let className=""; export let onLoad
export let onLoad; export let _bb
export let _bb;
let rootDiv
let rootDiv; $: {
$:{ if (_bb && rootDiv && _children && _children.length)
if(_bb && rootDiv && _children && _children.length) _bb.hydrateChildren(_children, rootDiv)
_bb.hydrateChildren(_children, rootDiv); }
}
</script> </script>
<div class="{className}" bind:this={rootDiv}> <div class={className} bind:this={rootDiv} />
</div>

12
packages/standard-components/src/H1.svelte

@ -1,12 +1,8 @@
<script> <script>
export let text=""; export let text = ""
export let className="" export let className = ""
export let _bb;
export let _bb
</script> </script>
<h1 class={className}> <h1 class={className}>{text}</h1>
{text}
</h1>

12
packages/standard-components/src/H2.svelte

@ -1,12 +1,8 @@
<script> <script>
export let text=""; export let text = ""
export let className="" export let className = ""
export let _bb;
export let _bb
</script> </script>
<h1 class={className}> <h1 class={className}>{text}</h1>
{text}
</h1>

12
packages/standard-components/src/H3.svelte

@ -1,12 +1,8 @@
<script> <script>
export let text=""; export let text = ""
export let className="" export let className = ""
export let _bb;
export let _bb
</script> </script>
<h3 class={className}> <h3 class={className}>{text}</h3>
{text}
</h3>

12
packages/standard-components/src/H4.svelte

@ -1,12 +1,8 @@
<script> <script>
export let text=""; export let text = ""
export let className="" export let className = ""
export let _bb;
export let _bb
</script> </script>
<h4 class={className}> <h4 class={className}>{text}</h4>
{text}
</h4>

12
packages/standard-components/src/H5.svelte

@ -1,12 +1,8 @@
<script> <script>
export let text=""; export let text = ""
export let className="" export let className = ""
export let _bb;
export let _bb
</script> </script>
<h6 class={className}> <h6 class={className}>{text}</h6>
{text}
</h6>

14
packages/standard-components/src/H6.svelte

@ -1,14 +1,10 @@
<script> <script>
import {buildStyle} from "./buildStyle"; import { buildStyle } from "./buildStyle"
export let text="";
export let className=""
export let _bb;
export let text = ""
export let className = ""
export let _bb
</script> </script>
<h1 class={className}> <h1 class={className}>{text}</h1>
{text}
</h1>

27
packages/standard-components/src/Input.svelte

@ -1,22 +1,17 @@
<script> <script>
export let value = ""
export let className = ""
export let type = "text"
export let value=""; export let _bb
export let className = "";
export let type = "text";
export let _bb; let actualValue = ""
let actualValue = "";
const onchange = (ev) => {
if(_bb) {
_bb.setStateFromBinding(_bb.props.value, ev.target.value);
}
}
const onchange = ev => {
if (_bb) {
_bb.setStateFromBinding(_bb.props.value, ev.target.value)
}
}
</script> </script>
<input class={className} <input class={className} {type} {value} on:change={onchange} />
type={type}
value={value}
on:change={onchange}/>

255
packages/standard-components/src/Login.svelte

@ -1,125 +1,115 @@
<script> <script>
import Button from "./Button.svelte"
import Button from "./Button.svelte";
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 = ""
export let logo = ""; export let buttonClass = ""
export let buttonClass = ""; export let inputClass = ""
export let inputClass=""
export let _bb
export let _bb;
let username = ""
let username = ""; let password = ""
let password = ""; let busy = false
let busy = false; let incorrect = false
let incorrect = false; let _logo = ""
let _logo = ""; let _buttonClass = ""
let _buttonClass = ""; let _inputClass = ""
let _inputClass = "";
$: {
$: { _logo = _bb.relativeUrl(logo)
_logo = _bb.relativeUrl(logo); _buttonClass = buttonClass || "default-button"
_buttonClass = buttonClass || "default-button"; _inputClass = inputClass || "default-input"
_inputClass = inputClass || "default-input"; }
}
const login = () => {
const login = () => { busy = true
busy = true; _bb.api
_bb.api.post("/api/authenticate", {username, password}) .post("/api/authenticate", { username, password })
.then(r => { .then(r => {
busy = false; busy = false
if(r.status === 200) { if (r.status === 200) {
return r.json(); return r.json()
} else { } else {
incorrect = true; incorrect = true
return; return
} }
}) })
.then(user => { .then(user => {
if(user) { if (user) {
localStorage.setItem("budibase:user", JSON.stringify(user)); localStorage.setItem("budibase:user", JSON.stringify(user))
location.reload(); location.reload()
} }
}) })
} }
</script> </script>
<div class="root"> <div class="root">
<div class="content"> <div class="content">
{#if _logo} {#if _logo}
<div class="logo-container"> <div class="logo-container">
<img src={_logo} alt="logo"/> <img src={_logo} alt="logo" />
</div> </div>
{/if} {/if}
<div class="form-root"> <div class="form-root">
<div class="label"> <div class="label">{usernameLabel}</div>
{usernameLabel} <div class="control">
</div> <input bind:value={username} type="text" class={_inputClass} />
<div class="control"> </div>
<input bind:value={username} type="text" class={_inputClass}/> <div class="label">{passwordLabel}</div>
</div> <div class="control">
<div class="label"> <input bind:value={password} type="password" class={_inputClass} />
{passwordLabel} </div>
</div> </div>
<div class="control">
<input bind:value={password} type="password" class={_inputClass}/>
</div>
</div>
<div class="login-button-container">
<button disabled={busy}
on:click={login}
class={_buttonClass}>
{loginButtonLabel}
</button>
</div>
{#if incorrect}
<div class="incorrect-details-panel">
Incorrect username or password
</div>
{/if}
<div class="login-button-container">
<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>
<style> <style>
.root {
.root {
height: 100%; height: 100%;
display:grid; display: grid;
grid-template-columns: [left] 1fr [middle] auto [right] 1fr; grid-template-columns: [left] 1fr [middle] auto [right] 1fr;
grid-template-rows: [top] 1fr [center] auto [bottom] 1fr; grid-template-rows: [top] 1fr [center] auto [bottom] 1fr;
} }
.content { .content {
grid-column-start: middle; grid-column-start: middle;
grid-row-start: center; grid-row-start: center;
width: 400px; width: 400px;
} }
.logo-container { .logo-container {
margin-bottom: 20px margin-bottom: 20px;
} }
.logo-container > img { .logo-container > img {
max-width: 100%; max-width: 100%;
} }
.login-button-container { .login-button-container {
text-align: right; text-align: right;
margin-top: 20px; margin-top: 20px;
} }
.incorrect-details-panel { .incorrect-details-panel {
margin-top: 30px; margin-top: 30px;
padding: 10px; padding: 10px;
border-style: solid; border-style: solid;
@ -129,53 +119,52 @@ const login = () => {
text-align: center; text-align: center;
color: maroon; color: maroon;
background-color: mistyrose; background-color: mistyrose;
} }
.form-root { .form-root {
display: grid; display: grid;
grid-template-columns: [label] auto [control] 1fr; /* [overflow] auto;*/ grid-template-columns: [label] auto [control] 1fr; /* [overflow] auto;*/
} }
.label { .label {
grid-column-start: label; grid-column-start: label;
padding: 5px 10px; padding: 5px 10px;
vertical-align: middle; vertical-align: middle;
} }
.control { .control {
grid-column-start: control; grid-column-start: control;
padding: 5px 10px; padding: 5px 10px;
} }
.default-input { .default-input {
font-family: inherit; font-family: inherit;
font-size: inherit; font-size: inherit;
padding: 0.4em; padding: 0.4em;
margin: 0 0 0.5em 0; margin: 0 0 0.5em 0;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 2px; border-radius: 2px;
width: 100%; width: 100%;
} }
.default-button { .default-button {
font-family: inherit; font-family: inherit;
font-size: inherit; font-size: inherit;
padding: 0.4em; padding: 0.4em;
margin: 0 0 0.5em 0; margin: 0 0 0.5em 0;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 2px; border-radius: 2px;
color: #333; color: #333;
background-color: #f4f4f4; background-color: #f4f4f4;
outline: none; outline: none;
} }
.default-button:active { .default-button:active {
background-color: #ddd; background-color: #ddd;
} }
.default-button:focus { .default-button:focus {
border-color: #666; border-color: #666;
} }
</style>
</style>

168
packages/standard-components/src/Nav.svelte

@ -1,121 +1,119 @@
<script> <script>
import cssVars from "./cssVars"; import cssVars from "./cssVars"
export let navBarBackground = ""; export let navBarBackground = ""
export let navBarBorder=""; export let navBarBorder = ""
export let navBarColor=""; export let navBarColor = ""
export let selectedItemBackground=""; export let selectedItemBackground = ""
export let selectedItemColor=""; export let selectedItemColor = ""
export let selectedItemBorder=""; export let selectedItemBorder = ""
export let itemHoverBackground=""; export let itemHoverBackground = ""
export let itemHoverColor=""; export let itemHoverColor = ""
export let hideNavBar=false; export let hideNavBar = false
export let selectedItem=""; export let selectedItem = ""
export let _children; export let _children
export let _bb; export let _bb
let selectedIndex = -1; let selectedIndex = -1
let styleVars={}; let styleVars = {}
let components = {}; let components = {}
let componentElements = {} let componentElements = {}
const hasComponentElements = () =>
const hasComponentElements = () => Object.getOwnPropertyNames(componentElements).length > 0
Object.getOwnPropertyNames(componentElements).length > 0;
$: {
$: {
styleVars = { styleVars = {
navBarBackground, navBarBorder, navBarBackground,
navBarColor, selectedItemBackground, navBarBorder,
selectedItemColor, selectedItemBorder, navBarColor,
itemHoverBackground, itemHoverColor selectedItemBackground,
}; selectedItemColor,
selectedItemBorder,
itemHoverBackground,
itemHoverColor,
}
if(_children && _children.length > 0 && hasComponentElements()) { if (_children && _children.length > 0 && hasComponentElements()) {
const currentSelectedItem = selectedIndex > 0 const currentSelectedItem =
? _children[selectedIndex].title selectedIndex > 0 ? _children[selectedIndex].title : ""
: ""; if (selectedItem && currentSelectedItem !== selectedItem) {
if(selectedItem && currentSelectedItem !== selectedItem) { let i = 0
let i=0; for (let child of _children) {
for(let child of _children) { if (child.title === selectedItem) {
if(child.title === selectedItem) { onSelectItem(i)()
onSelectItem(i)(); }
} i++
i++;
}
} else if(!currentSelectedItem) {
onSelectItem(0);
} }
} else if (!currentSelectedItem) {
onSelectItem(0)
}
} }
} }
const onSelectItem = (index) => () => { const onSelectItem = index => () => {
selectedIndex = index; selectedIndex = index
if(!components[index]) { if (!components[index]) {
const comp = _bb.hydrateChildren( const comp = _bb.hydrateChildren(
_children[index].children, componentElements[index]); _children[index].children,
components[index] = comp; componentElements[index]
)
components[index] = comp
} }
} }
</script> </script>
<div class="root" use:cssVars={styleVars}> <div class="root" use:cssVars={styleVars}>
{#if !hideNavBar} {#if !hideNavBar}
<div class="navbar"> <div class="navbar">
{#each _children as navItem, index} {#each _children as navItem, index}
<div class="navitem" <div
on:click={onSelectItem(index)} class="navitem"
class:selected={selectedIndex === index}> on:click={onSelectItem(index)}
{navItem.title} class:selected={selectedIndex === index}>
{navItem.title}
</div> </div>
{/each} {/each}
</div>
{/if}
{#each _children as navItem, index}
<div class="content"
bind:this={componentElements[index]}>
</div> </div>
{/each} {/if}
{#each _children as navItem, index}
<div class="content" bind:this={componentElements[index]} />
{/each}
</div> </div>
<style> <style>
.root {
.root {
height: 100%; height: 100%;
width:100%; width: 100%;
grid-template-columns: [navbar] auto [content] 1fr; grid-template-columns: [navbar] auto [content] 1fr;
display: grid; display: grid;
} }
.navbar { .navbar {
grid-column: navbar; grid-column: navbar;
background: var(--navBarBackground); background: var(--navBarBackground);
border: var(--navBarBorder); border: var(--navBarBorder);
color: var(--navBarColor); color: var(--navBarColor);
} }
.navitem { .navitem {
padding: 10px 17px; padding: 10px 17px;
cursor: pointer; cursor: pointer;
} }
.navitem:hover { .navitem:hover {
background: var(--itemHoverBackground); background: var(--itemHoverBackground);
color: var(--itemHoverColor); color: var(--itemHoverColor);
} }
.navitem.selected { .navitem.selected {
background: var(--selectedItemBackground); background: var(--selectedItemBackground);
border: var(--selectedItemBorder); border: var(--selectedItemBorder);
color: var(--selectedItemColor); color: var(--selectedItemColor);
} }
.content { .content {
grid-column: content; grid-column: content;
} }
</style> </style>

36
packages/standard-components/src/Select.svelte

@ -1,27 +1,23 @@
<script> <script>
export let value = ""
export let className = ""
export let type = "text"
export let options = []
export let value=""; export let _bb
export let className = "";
export let type = "text";
export let options = [];
export let _bb; let actualValue = ""
let actualValue = "";
const onchange = (ev) => {
if(_bb) {
_bb.setStateFromBinding(_bb.props.value, ev.target.value);
}
}
const onchange = ev => {
if (_bb) {
_bb.setStateFromBinding(_bb.props.value, ev.target.value)
}
}
</script> </script>
<select class={className} <select class={className} {value} on:change={onchange}>
value={value} <option />
on:change={onchange}> {#each options as opt}
<option></option> <option id={opt.id ? opt.id : opt.value}>{opt.value}</option>
{#each options as opt} {/each}
<option id={opt.id ? opt.id : opt.value}>{opt.value}</option>
{/each}
</select> </select>

102
packages/standard-components/src/Table.svelte

@ -1,82 +1,74 @@
<script> <script>
export let columns = []
export let data = ""
export let tableClass = ""
export let theadClass = ""
export let tbodyClass = ""
export let trClass = ""
export let thClass = ""
export let onRowClick
export let columns=[]; export let _bb
export let data="";
export let tableClass="";
export let theadClass="";
export let tbodyClass="";
export let trClass="";
export let thClass="";
export let onRowClick;
export let _bb;
const rowClickHandler = (row) => () => {
_bb.call(onRowClick, row);
}
const cellValue = (colIndex, row) => {
const val = _bb.getStateOrValue(
_bb.props.columns[colIndex].value
, row)
return val;
}
const rowClickHandler = row => () => {
_bb.call(onRowClick, row)
}
const cellValue = (colIndex, row) => {
const val = _bb.getStateOrValue(_bb.props.columns[colIndex].value, row)
return val
}
</script> </script>
<table class={tableClass}> <table class={tableClass}>
<thead class={theadClass}> <thead class={theadClass}>
<tr class={trClass}> <tr class={trClass}>
{#each columns as col} {#each columns as col}
<th class={thClass}>{col.title}</th> <th class={thClass}>{col.title}</th>
{/each} {/each}
</tr> </tr>
</thead> </thead>
<tbody class={tbodyClass}> <tbody class={tbodyClass}>
{#if data} {#if data}
{#each data as row} {#each data as row}
<tr class={trClass} <tr class={trClass} on:click={rowClickHandler(row)}>
on:click={rowClickHandler(row)} > {#each columns as col, index}
{#each columns as col, index}
<th class={thClass}>{cellValue(index, row)}</th> <th class={thClass}>{cellValue(index, row)}</th>
{/each} {/each}
</tr> </tr>
{/each} {/each}
{/if} {/if}
</tbody> </tbody>
</table> </table>
<style> <style>
.table-default {
.table-default {
width: 100%; width: 100%;
margin-bottom: 1rem; margin-bottom: 1rem;
color: #212529; color: #212529;
border-collapse: collapse; border-collapse: collapse;
} }
.table-default .thead-default .th-default { .table-default .thead-default .th-default {
vertical-align: bottom; vertical-align: bottom;
border-bottom: 2px solid #dee2e6; border-bottom: 2px solid #dee2e6;
font-weight: bold; font-weight: bold;
} }
.table-default .th-default { .table-default .th-default {
padding: .75rem; padding: 0.75rem;
vertical-align: top; vertical-align: top;
border-top: 1px solid #dee2e6; border-top: 1px solid #dee2e6;
font-weight: normal; font-weight: normal;
} }
.th-default { .th-default {
text-align: inherit; text-align: inherit;
} }
.table-default .tbody-default .tr-default:hover { .table-default .tbody-default .tr-default:hover {
color: #212529; color: #212529;
background-color: rgba(0,0,0,.075); background-color: rgba(0, 0, 0, 0.075);
cursor: pointer; cursor: pointer;
} }
</style>
</style>

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

Loading…
Cancel
Save