Browse Source

include svelte files in prettier

pull/4023/head
Martin McKeaveney 6 years ago
parent
commit
927502eb38
  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,
"singleQuote": false,
"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",
"lerna": "^3.14.1",
"prettier": "^1.19.1",
"prettier-plugin-svelte": "^0.7.0"
"prettier-plugin-svelte": "^0.7.0",
"svelte": "^3.18.1"
},
"dependencies": {},
"scripts": {
@ -19,6 +20,6 @@
"test": "lerna run test",
"lint": "eslint 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 &&
!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) {
@ -18897,7 +18899,8 @@ var app = (function() {
// Non `Object` object instances with different constructors are not equal.
if (
objCtor != othCtor &&
"constructor" in object && "constructor" in other &&
"constructor" in object &&
"constructor" in other &&
!(
typeof objCtor == "function" &&
objCtor instanceof objCtor &&
@ -19391,7 +19394,9 @@ var app = (function() {
return (
!!length &&
(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>
export let formControls = [];
export let _bb;
let htmlElements = {};
let labelElements = {};
let labels = {};
let isInitialised = false;
$ : {
if(_bb && htmlElements && !isInitialised) {
let cIndex = 0;
for(let c of formControls) {
labels[cIndex] = c.label;
cIndex++;
}
for(let el in htmlElements) {
if(formControls[el].control.controlPosition === "Before Label") {
_bb.insertChildren(
_bb.props.formControls[el].control,
htmlElements[el],
htmlElements[el].childNodes.find(n => n.tagName === "LABEL"));
} else {
_bb.appendChildren(
_bb.props.formControls[el].control,
htmlElements[el]);
}
export let formControls = []
export let _bb
let htmlElements = {}
let labelElements = {}
let labels = {}
let isInitialised = false
$: {
if (_bb && htmlElements && !isInitialised) {
let cIndex = 0
for (let c of formControls) {
labels[cIndex] = c.label
cIndex++
}
for (let el in htmlElements) {
if (formControls[el].control.controlPosition === "Before Label") {
_bb.insertChildren(
_bb.props.formControls[el].control,
htmlElements[el],
htmlElements[el].childNodes.find(n => n.tagName === "LABEL")
)
} else {
_bb.appendChildren(
_bb.props.formControls[el].control,
htmlElements[el]
)
}
isInitialised = true;
}
isInitialised = true
}
}
}
</script>
<form>
{#each formControls as child, idx}
{#each formControls as child, idx}
<div class="form-group" bind:this={htmlElements[idx]}>
<label>{labels[idx]}</label>
<label>{labels[idx]}</label>
</div>
{/each}
{/each}
</form>

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

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

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

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

69
packages/builder/src/App.svelte

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

75
packages/builder/src/BackendRoot.svelte

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

63
packages/builder/src/NoPackage.svelte

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

130
packages/builder/src/PackageRoot.svelte

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

31
packages/builder/src/Settings.svelte

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,11 +1,22 @@
<script>
export let disabled = false;
export let hidden = false;
export let primary = true;
export let alert = false;
export let warning = false;
export let disabled = false
export let hidden = false
export let primary = true
export let alert = false
export let warning = false
</script>
<button
on:click
class="button"
class:hidden
class:primary
class:alert
class:warning
{disabled}>
<slot />
</button>
<style>
.primary {
color: #0055ff;
@ -14,7 +25,7 @@
.alert {
color: rgba(255, 0, 31, 1);
background: rgba(255, 0, 31, 0.1);;
background: rgba(255, 0, 31, 0.1);
}
.button {
@ -40,14 +51,3 @@
visibility: hidden;
}
</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>
export let color = "primary"
export let className = ""
export let style = ""
export let groupPosition = ""
export let grouped = false
export let color = "primary";
export let className = "";
export let style = "";
export let groupPosition = "";
export let grouped = false;
$: borderClass = grouped
? ""
: "border-normal";
$: borderClass = grouped ? "" : "border-normal"
</script>
<button class="{color} {className} {borderClass} {grouped ? "grouped" : ""}"
style="{style}"
on:click >
<slot/>
<button
class="{color}
{className}
{borderClass}
{grouped ? 'grouped' : ''}"
{style}
on:click>
<slot />
</button>
<style>
.border-normal { 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-middle { border-radius: 0; }
button {
border-style: solid;
.border-normal {
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-middle {
border-radius: 0;
}
button {
border-style: solid;
padding: 7.5px 15px;
cursor:pointer;
margin:5px;
cursor: pointer;
margin: 5px;
border-radius: 5px;
}
}
/* ---- PRIMARY ----*/
.primary {
/* ---- PRIMARY ----*/
.primary {
background-color: var(--primary100);
border-color: var(--primary100);
color: var(--white);
}
}
.primary:hover {
.primary:hover {
background-color: var(--primary75);
border-color: var(--primary75);
}
}
.primary:active {
.primary:active {
background-color: var(--primarydark);
border-color: var(--primarydark);
}
}
.primary-outline {
.primary-outline {
background-color: var(--white);
border-color: var(--primary100);
color: var(--primary100);
}
}
.primary-outline:hover {
.primary-outline:hover {
background-color: var(--primary10);
}
}
.primary-outline:pressed {
.primary-outline:pressed {
background-color: var(--primary25);
}
}
/* ---- secondary ----*/
/* ---- secondary ----*/
.secondary {
.secondary {
background-color: var(--secondary100);
border-color: var(--secondary100);
color: var(--white);
}
}
.secondary:hover {
.secondary:hover {
background-color: var(--secondary75);
border-color: var(--secondary75);
}
}
.secondary:pressed {
.secondary:pressed {
background-color: var(--secondarydark);
border-color: var(--secondarydark);
}
}
.secondary-outline {
.secondary-outline {
background-color: var(--white);
border-color: var(--secondary100);
color: var(--secondary100);
}
}
.secondary-outline:hover {
.secondary-outline:hover {
background-color: var(--secondary10);
}
}
.secondary-outline:pressed {
.secondary-outline:pressed {
background-color: var(--secondary25);
}
}
/* ---- success ----*/
.success {
/* ---- success ----*/
.success {
background-color: var(--success100);
border-color: var(--success100);
color: var(--white);
}
}
.success:hover {
.success:hover {
background-color: var(--success75);
border-color: var(--success75);
}
}
.success:pressed {
.success:pressed {
background-color: var(--successdark);
border-color: var(--successdark);
}
}
.success-outline {
.success-outline {
background-color: var(--white);
border-color: var(--success100);
color: var(--success100);
}
}
.success-outline:hover {
.success-outline:hover {
background-color: var(--success10);
}
}
.success-outline:pressed {
.success-outline:pressed {
background-color: var(--success25);
}
}
/* ---- deletion ----*/
.deletion {
/* ---- deletion ----*/
.deletion {
background-color: var(--deletion100);
border-color: var(--deletion100);
color: var(--white);
}
}
.deletion:hover {
.deletion:hover {
background-color: var(--deletion75);
border-color: var(--deletion75);
}
}
.deletion:pressed {
.deletion:pressed {
background-color: var(--deletiondark);
border-color: var(--deletiondark);
}
}
.deletion-outline {
.deletion-outline {
background-color: var(--white);
border-color: var(--deletion100);
color: var(--deletion100);
}
}
.deletion-outline:hover {
.deletion-outline:hover {
background-color: var(--deletion10);
}
}
.deletion-outline:pressed {
.deletion-outline:pressed {
background-color: var(--deletion25);
}
</style>
}
</style>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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">
<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
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="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>

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"/>
<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
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>

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"/>
<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
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>

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 {
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">
<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
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="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>

Before

Width:  |  Height:  |  Size: 346 B

After

Width:  |  Height:  |  Size: 372 B

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

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

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

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

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

@ -1,29 +1,38 @@
<script>
import UIkit from "uikit";
import UIkit from "uikit"
export let isOpen = false;
export let onClosed = () => {};
export let id = "";
export let isOpen = false
export let onClosed = () => {}
export let id = ""
let ukModal;
let listenerAdded = false;
let ukModal
let listenerAdded = false
$: {
if (ukModal && !listenerAdded) {
listenerAdded = true;
ukModal.addEventListener("hidden", onClosed);
listenerAdded = true
ukModal.addEventListener("hidden", onClosed)
}
if (ukModal) {
if (isOpen) {
UIkit.modal(ukModal).show();
UIkit.modal(ukModal).show()
} else {
UIkit.modal(ukModal).hide();
UIkit.modal(ukModal).hide()
}
}
}
</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>
.uk-modal-dialog {
border-radius: 0.3rem;
@ -33,12 +42,3 @@
flex-direction: column;
}
</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>
export let value
export let label
export let value;
export let label;
const inputChanged = ev => {
const inputChanged = ev => {
try {
value = Number(ev.target.value);
} catch(_) {
value = null;
value = Number(ev.target.value)
} catch (_) {
value = null
}
}
let numberText = value === null || value === undefined
? "" : value.toString();
}
let numberText = value === null || value === undefined ? "" : value.toString()
</script>
<div class="uk-margin">
<label class="uk-form-label">{label}</label>
<div class="uk-form-controls">
<input class="uk-input" value={value} on:change={inputChanged} >
</div>
<label class="uk-form-label">{label}</label>
<div class="uk-form-controls">
<input class="uk-input" {value} on:change={inputChanged} />
</div>
</div>

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

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

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

@ -1,8 +1,17 @@
<script>
import getIcon from "./icon";
export let value;
import getIcon from "./icon"
export let value
</script>
<div class="select-container">
<select on:change {value}>
<slot />
</select>
<span class="arrow">
{@html getIcon('chevron-down', '24')}
</span>
</div>
<style>
.select-container {
padding-bottom: 10px;
@ -44,12 +53,3 @@
color: var(--primary100);
}
</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>
export let text = "";
export let label = "";
export let width = "medium";
export let size = "small";
export let margin = true;
export let infoText = "";
export let hasError = false;
export let disabled = false;
export let text = ""
export let label = ""
export let width = "medium"
export let size = "small"
export let margin = true
export let infoText = ""
export let hasError = false
export let disabled = false
</script>
<div class:uk-margin={margin}>
<label class="uk-form-label">{label}</label>
<div class="uk-form-controls">
<input class="uk-input uk-form-width-{width} uk-form-{size}"
class:uk-form-danger={hasError}
on:change
bind:value={text}
{disabled}>
</div>
{#if infoText}
<label class="uk-form-label">{label}</label>
<div class="uk-form-controls">
<input
class="uk-input uk-form-width-{width} uk-form-{size}"
class:uk-form-danger={hasError}
on:change
bind:value={text}
{disabled} />
</div>
{#if infoText}
<div class="info-text">{infoText}</div>
{/if}
{/if}
</div>
<style>
.info-text {
.info-text {
font-size: 0.7rem;
color: var(--secondary50);
}
}
</style>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,83 +1,84 @@
<script>
import { store } from "../builderStore/store";
import UIkit from "uikit";
import Button from "../common/Button.svelte";
import ButtonGroup from "../common/ButtonGroup.svelte";
import CodeMirror from "codemirror";
import "codemirror/mode/javascript/javascript.js";
export let onCodeChanged;
export let code;
export const 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 || "");
import { store } from "../builderStore/store"
import UIkit from "uikit"
import Button from "../common/Button.svelte"
import ButtonGroup from "../common/ButtonGroup.svelte"
import CodeMirror from "codemirror"
import "codemirror/mode/javascript/javascript.js"
export let onCodeChanged
export let code
export const show = () => {
UIkit.modal(codeModal).show()
}
}
const cancel = () => {
UIkit.modal(codeModal).hide();
currentCode = originalCode;
}
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 save = () => {
originalCode = currentCode;
onCodeChanged(currentCode);
UIkit.modal(codeModal).hide();
}
const cancel = () => {
UIkit.modal(codeModal).hide()
currentCode = originalCode
}
const save = () => {
originalCode = currentCode
onCodeChanged(currentCode)
UIkit.modal(codeModal).hide()
}
</script>
<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">
<h3>Code</h3>
</div>
<div class="uk-modal-header">
<h3>Code</h3>
</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 javascript.</p>
<div>
<div class="editor-code-surround">function(render, context, store) {"{"}</div>
<div class="editor">
<textarea bind:this={editor}></textarea>
</div>
<div class="editor-code-surround">
{"}"}
</div>
<p>
Use the code box below to control how this component is displayed, with
javascript.
</p>
<div>
<div class="editor-code-surround">
function(render, context, store) {'{'}
</div>
<div class="editor">
<textarea bind:this={editor} />
</div>
<div class="editor-code-surround">{'}'}</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>
<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>
<style>
@ -105,5 +106,4 @@ const save = () => {
.editor-code-surround {
font-family: "Courier New", Courier, monospace;
}
</style>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,107 +1,64 @@
<script>
import Modal from "../../common/Modal.svelte";
import HandlerSelector from "./HandlerSelector.svelte";
import IconButton from "../../common/IconButton.svelte";
import ActionButton from "../../common/ActionButton.svelte";
import PlusButton from "../../common/PlusButton.svelte";
import Select from "../../common/Select.svelte";
import Input from "../../common/Input.svelte";
import getIcon from "../../common/icon";
import Modal from "../../common/Modal.svelte"
import HandlerSelector from "./HandlerSelector.svelte"
import IconButton from "../../common/IconButton.svelte"
import ActionButton from "../../common/ActionButton.svelte"
import PlusButton from "../../common/PlusButton.svelte"
import Select from "../../common/Select.svelte"
import Input from "../../common/Input.svelte"
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 eventOptions;
export let open;
export let onClose;
export let onPropChanged;
export let event
export let eventOptions
export let open
export let onClose
export let onPropChanged
let eventType = "onClick";
let draftEventHandler = { parameters: [] };
let eventType = "onClick"
let draftEventHandler = { parameters: [] }
$: eventData = event || { handlers: [] };
$: eventData = event || { handlers: [] }
const closeModal = () => {
onClose();
draftEventHandler = { parameters: [] };
eventData = { handlers: [] };
};
onClose()
draftEventHandler = { parameters: [] }
eventData = { handlers: [] }
}
const updateEventHandler = (updatedHandler, index) => {
eventData.handlers[index] = updatedHandler;
};
eventData.handlers[index] = updatedHandler
}
const updateDraftEventHandler = updatedHandler => {
draftEventHandler = updatedHandler;
};
draftEventHandler = updatedHandler
}
const deleteEventHandler = index => {
eventData.handlers.splice(index, 1);
eventData = eventData;
};
eventData.handlers.splice(index, 1)
eventData = eventData
}
const createNewEventHandler = handler => {
const newHandler = handler || {
parameters: {},
[EVENT_TYPE_MEMBER_NAME]: ""
};
eventData.handlers.push(newHandler);
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_TYPE_MEMBER_NAME]: "",
}
eventData.handlers.push(newHandler)
eventData = eventData
}
.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;
const deleteEvent = () => {
onPropChanged(eventType, [])
closeModal()
}
a {
color: rgba(22, 48, 87, 0.6);
font-size: 12px;
margin-top: 0;
const saveEventData = () => {
onPropChanged(eventType, eventData.handlers)
closeModal()
}
</style>
</script>
<Modal bind:isOpen={open} onClosed={closeModal}>
<h2>
@ -133,8 +90,8 @@
newHandler
onChanged={updateDraftEventHandler}
onCreate={() => {
createNewEventHandler(draftEventHandler);
draftEventHandler = { parameters: [] };
createNewEventHandler(draftEventHandler)
draftEventHandler = { parameters: [] }
}}
handler={draftEventHandler} />
{#if eventData}
@ -162,3 +119,46 @@
</ActionButton>
</div>
</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,
sortBy,
filter,
difference
} from "lodash/fp";
import { pipe } from "../../common/core";
import Checkbox from "../../common/Checkbox.svelte";
import Textbox from "../../common/Textbox.svelte";
import Dropdown from "../../common/Dropdown.svelte";
import PlusButton from "../../common/PlusButton.svelte";
import IconButton from "../../common/IconButton.svelte";
import Modal from "../../common/Modal.svelte";
import EventEditorModal from "./EventEditorModal.svelte";
import HandlerSelector from "./HandlerSelector.svelte";
import { PencilIcon } from "../../common/Icons";
import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers";
export const EVENT_TYPE = "event";
export let componentInfo;
export let onPropChanged = () => {};
export let components;
let modalOpen = false;
let events = [];
let selectedEvent = null;
$: {
difference,
} from "lodash/fp"
import { pipe } from "../../common/core"
import Checkbox from "../../common/Checkbox.svelte"
import Textbox from "../../common/Textbox.svelte"
import Dropdown from "../../common/Dropdown.svelte"
import PlusButton from "../../common/PlusButton.svelte"
import IconButton from "../../common/IconButton.svelte"
import Modal from "../../common/Modal.svelte"
import EventEditorModal from "./EventEditorModal.svelte"
import HandlerSelector from "./HandlerSelector.svelte"
import { PencilIcon } from "../../common/Icons"
import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers"
export const EVENT_TYPE = "event"
export let componentInfo
export let onPropChanged = () => {}
export let components
let modalOpen = false
let events = []
let selectedEvent = null
$: {
events = Object.keys(componentInfo)
.filter(key => findType(key) === EVENT_TYPE)
.map(key => ({ name: key, handlers: componentInfo[key] }));
.filter(key => findType(key) === EVENT_TYPE)
.map(key => ({ name: key, handlers: componentInfo[key] }))
}
function findType(propName) {
if (!componentInfo._component) return;
if (!componentInfo._component) return
return components.find(({ name }) => name === componentInfo._component)
.props[propName];
.props[propName]
}
const openModal = event => {
selectedEvent = event;
modalOpen = true;
};
selectedEvent = event
modalOpen = true
}
const closeModal = () => {
selectedEvent = null;
modalOpen = false;
};
selectedEvent = null
modalOpen = false
}
</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>
h3 {
text-transform: uppercase;
@ -123,30 +149,3 @@
background: var(--background-button) !important;
}
</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>
import IconButton from "../../common/IconButton.svelte";
import PlusButton from "../../common/PlusButton.svelte";
import Select from "../../common/Select.svelte";
import StateBindingControl from "../StateBindingControl.svelte";
import { find, map, keys, reduce, keyBy } from "lodash/fp";
import { pipe, userWithFullAccess } from "../../common/core";
import IconButton from "../../common/IconButton.svelte"
import PlusButton from "../../common/PlusButton.svelte"
import Select from "../../common/Select.svelte"
import StateBindingControl from "../StateBindingControl.svelte"
import { find, map, keys, reduce, keyBy } from "lodash/fp"
import { pipe, userWithFullAccess } from "../../common/core"
import {
EVENT_TYPE_MEMBER_NAME,
allHandlers
} from "../../common/eventHandlers";
import { store } from "../../builderStore";
allHandlers,
} from "../../common/eventHandlers"
import { store } from "../../builderStore"
export let handler;
export let onCreate;
export let onChanged;
export let onRemoved;
export let handler
export let onCreate
export let onChanged
export let onRemoved
export let index;
export let newHandler;
export let index
export let newHandler
let eventOptions;
let handlerType;
let parameters = [];
let eventOptions
let handlerType
let parameters = []
$: eventOptions = allHandlers(
{ hierarchy: $store.hierarchy },
userWithFullAccess({
hierarchy: $store.hierarchy,
actions: keyBy("name")($store.actions)
actions: keyBy("name")($store.actions),
})
);
)
$: {
if (handler) {
handlerType = handler[EVENT_TYPE_MEMBER_NAME];
handlerType = handler[EVENT_TYPE_MEMBER_NAME]
parameters = Object.entries(handler.parameters).map(([name, value]) => ({
name,
value
}));
value,
}))
} else {
// Empty Handler
handlerType = "";
parameters = [];
// Empty Handler
handlerType = ""
parameters = []
}
}
const handlerChanged = (type, params) => {
const handlerParams = {};
const handlerParams = {}
for (let param of params) {
handlerParams[param.name] = param.value;
handlerParams[param.name] = param.value
}
const updatedHandler = {
[EVENT_TYPE_MEMBER_NAME]: type,
parameters: handlerParams
};
parameters: handlerParams,
}
onChanged(updatedHandler, index);
};
onChanged(updatedHandler, index)
}
const handlerTypeChanged = e => {
const handlerType = eventOptions.find(
handler => handler.name === e.target.value
);
)
const defaultParams = handlerType.parameters.map(param => ({
name: param,
value: ""
}));
value: "",
}))
handlerChanged(handlerType.name, defaultParams);
};
handlerChanged(handlerType.name, defaultParams)
}
const onParameterChanged = index => value => {
const newParams = [...parameters];
newParams[index].value = value;
handlerChanged(handlerType, newParams);
};
const newParams = [...parameters]
newParams[index].value = value
handlerChanged(handlerType, newParams)
}
</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>
.type-selector-container {
display: flex;
@ -113,36 +146,3 @@
margin-bottom: 5px;
}
</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>
import InputGroup from '../common/Inputs/InputGroup.svelte';
import InputGroup from "../common/Inputs/InputGroup.svelte"
export let onStyleChanged = () => {};
export let componentInfo;
export let onStyleChanged = () => {}
export let componentInfo
const tbrl = [
{ placeholder: 'T' },
{ placeholder: 'R' },
{ placeholder: 'B' },
{ placeholder: 'L' }
];
const se = [
{ placeholder: 'START' },
{ placeholder: 'END' },
{ placeholder: "T" },
{ placeholder: "R" },
{ placeholder: "B" },
{ placeholder: "L" },
]
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 = {
templaterows: ['Grid Rows', single],
templatecolumns: ['Grid Columns', single],
};
templaterows: ["Grid Rows", single],
templatecolumns: ["Grid Columns", single],
}
$: positions = {
column: ['Column', se],
row: ['Row', se],
};
column: ["Column", se],
row: ["Row", se],
}
$: spacing = {
margin: ['Margin', tbrl, 'small'],
padding: ['Padding', tbrl, 'small']
};
margin: ["Margin", tbrl, "small"],
padding: ["Padding", tbrl, "small"],
}
$: zindex = {
zindex: ['Z-Index', single]
zindex: ["Z-Index", single],
}
const newValue = n => Array(n).fill('');
const newValue = n => Array(n).fill("")
</script>
<h3>Styles</h3>
<h4>Positioning</h4>
<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">
<h5>{name}:</h5>
<InputGroup onStyleChanged={_value => onStyleChanged('layout',key, _value)}
values={layout[key] || newValue(meta.length)}
{meta}
{size}
type="text"/>
<InputGroup
onStyleChanged={_value => onStyleChanged('layout', key, _value)}
values={layout[key] || newValue(meta.length)}
{meta}
{size}
type="text" />
</div>
{/each}
</div>
<h4>Positioning</h4>
<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">
<h5>{name}:</h5>
<InputGroup onStyleChanged={_value => onStyleChanged('position',key, _value)}
values={layout[key] || newValue(meta.length)}
{meta}
{size} />
<InputGroup
onStyleChanged={_value => onStyleChanged('position', key, _value)}
values={layout[key] || newValue(meta.length)}
{meta}
{size} />
</div>
{/each}
</div>
@ -78,10 +78,11 @@
{#each Object.entries(spacing) as [key, [name, meta, size]]}
<div class="grid">
<h5>{name}:</h5>
<InputGroup onStyleChanged={_value => onStyleChanged('position', key, _value)}
values={layout[key] || newValue(meta.length)}
{meta}
{size} />
<InputGroup
onStyleChanged={_value => onStyleChanged('position', key, _value)}
values={layout[key] || newValue(meta.length)}
{meta}
{size} />
</div>
{/each}
</div>
@ -91,10 +92,11 @@
{#each Object.entries(zindex) as [key, [name, meta, size]]}
<div class="grid">
<h5>{name}:</h5>
<InputGroup onStyleChanged={_value => onStyleChanged('position', key, _value)}
values={layout[key] || newValue(meta.length)}
{meta}
{size} />
<InputGroup
onStyleChanged={_value => onStyleChanged('position', key, _value)}
values={layout[key] || newValue(meta.length)}
{meta}
{size} />
</div>
{/each}
</div>

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

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

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

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

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

@ -1,73 +1,80 @@
<script>
import { store } from "../builderStore";
import getIcon from "../common/icon";
import { store } from "../builderStore"
import getIcon from "../common/icon"
const getPage = (s, name) => {
const props = s.pages[name];
return ({name, props});
}
const getPage = (s, name) => {
const props = s.pages[name]
return { name, props }
}
const pages = [{
title: 'Main',
id: 'main'
}, {
title: 'Login',
id: 'unauthenticated'
}]
const pages = [
{
title: "Main",
id: "main",
},
{
title: "Login",
id: "unauthenticated",
},
]
store.setCurrentPage('main')
store.setCurrentPage("main")
</script>
<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}}
<option value="{id}">Page: {title}</option>
{#each pages as { title, id }}
<option value={id}>Page: {title}</option>
{/each}
</select>
<span class="arrow">{@html getIcon("chevron-down","24")}</span>
</select>
<span class="arrow">
{@html getIcon('chevron-down', '24')}
</span>
</div>
<style>
.root {
padding-bottom: 10px;
font-size: .9rem;
color: var(--secondary50);
font-weight: bold;
position: relative;
}
.root {
padding-bottom: 10px;
font-size: 0.9rem;
color: var(--secondary50);
font-weight: bold;
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 {
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: .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);
}
.arrow {
position: absolute;
right: 10px;
top: 0;
bottom: 0;
margin: auto;
width: 30px;
height: 30px;
pointer-events: none;
color: var(--primary100);
}
</style>

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

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

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

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

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

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

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

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

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

@ -1,180 +1,178 @@
<script>
import ComponentsHierarchy from "./ComponentsHierarchy.svelte";
import PagesList from "./PagesList.svelte"
import { store } from "../builderStore";
import IconButton from "../common/IconButton.svelte";
import Modal from "../common/Modal.svelte";
import NewComponent from "./NewComponent.svelte";
import CurrentItemPreview from "./CurrentItemPreview.svelte";
import SettingsView from "./SettingsView.svelte";
import PageView from "./PageView.svelte";
import ComponentsPaneSwitcher from "./ComponentsPaneSwitcher.svelte";
let newComponentPicker;
const newComponent = () => {
newComponentPicker.show();
}
let settingsView;
const settings = () => {
settingsView.show();
}
import ComponentsHierarchy from "./ComponentsHierarchy.svelte"
import PagesList from "./PagesList.svelte"
import { store } from "../builderStore"
import IconButton from "../common/IconButton.svelte"
import Modal from "../common/Modal.svelte"
import NewComponent from "./NewComponent.svelte"
import CurrentItemPreview from "./CurrentItemPreview.svelte"
import SettingsView from "./SettingsView.svelte"
import PageView from "./PageView.svelte"
import ComponentsPaneSwitcher from "./ComponentsPaneSwitcher.svelte"
let newComponentPicker
const newComponent = () => {
newComponentPicker.show()
}
let settingsView
const settings = () => {
settingsView.show()
}
</script>
<div class="root">
<div class="ui-nav">
<div class="ui-nav">
<div class="pages-list-container">
<div class="nav-group-header">
<div class="pages-list-container">
<div class="nav-group-header">
<span class="navigator-title">Navigator</span>
</div>
<div class="nav-items-container">
<PagesList />
</div>
</div>
<span class="navigator-title">Navigator</span>
</div>
<div class="nav-items-container">
<PagesList />
</div>
</div>
<div class="components-list-container">
<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>
<div class="components-list-container">
<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>
<div class="preview-pane">
{#if $store.currentFrontEndType === "screen"}
<CurrentItemPreview />
{:else if $store.currentFrontEndType === "page"}
<PageView />
{/if}
</div>
</div>
{#if $store.currentFrontEndType === "screen"}
<div class="components-pane">
<ComponentsPaneSwitcher />
</div>
<div class="preview-pane">
{#if $store.currentFrontEndType === 'screen'}
<CurrentItemPreview />
{:else if $store.currentFrontEndType === 'page'}
<PageView />
{/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} />
<style>
button {
cursor: pointer;
outline: none;
border: none;
border-radius: 5px;
background: var(--background-button);
width: 1.8rem;
height: 1.8rem;
padding-bottom: 10px;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.2rem;
font-weight: 700;
color: var(--button-text);
}
.root {
display: grid;
grid-template-columns: 290px 1fr 300px;
height: 100%;
width: 100%;
background: #fafafa;
}
.ui-nav {
grid-column: 1;
background-color: var(--secondary5);
height: 100%;
padding: 0 1.5rem 0rem 1.5rem
}
.preview-pane {
grid-column: 2;
margin: 80px 60px;
background: #fff;
border-radius: 5px;
box-shadow: 0 0px 6px rgba(0,0,0,0.05)
}
.components-pane {
grid-column: 3;
background-color: var(--secondary5);
min-height: 0px;
overflow-y: hidden;
}
.components-nav-header {
font-size: 0.75rem;
color: #999;
text-transform: uppercase;
}
.nav-group-header {
font-size: .9rem;
padding-left: 1rem;
}
.nav-items-container {
padding: 1rem 0rem 0rem 0rem;
}
.nav-group-header {
display: flex;
padding: 2rem 0 0 0;
font-size: .9rem;
font-weight: bold;
justify-content: space-between;
align-items: center;
}
.nav-group-header>div:nth-child(1) {
padding: 0rem .5rem 0rem 0rem;
vertical-align: bottom;
grid-column-start: icon;
margin-right: 5px;
}
.nav-group-header>span:nth-child(2) {
margin-left:5px;
vertical-align: bottom;
grid-column-start: title;
margin-top:auto;
}
.nav-group-header>div:nth-child(3) {
vertical-align: bottom;
grid-column-start: button;
cursor: pointer;
color: var(--primary75);
}
.nav-group-header>div:nth-child(3):hover {
color: var(--primary75);
}
.navigator-title {
text-transform: uppercase;
font-weight: 400;
color: #999;
}
button {
cursor: pointer;
outline: none;
border: none;
border-radius: 5px;
background: var(--background-button);
width: 1.8rem;
height: 1.8rem;
padding-bottom: 10px;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.2rem;
font-weight: 700;
color: var(--button-text);
}
.root {
display: grid;
grid-template-columns: 290px 1fr 300px;
height: 100%;
width: 100%;
background: #fafafa;
}
.ui-nav {
grid-column: 1;
background-color: var(--secondary5);
height: 100%;
padding: 0 1.5rem 0rem 1.5rem;
}
.preview-pane {
grid-column: 2;
margin: 80px 60px;
background: #fff;
border-radius: 5px;
box-shadow: 0 0px 6px rgba(0, 0, 0, 0.05);
}
.components-pane {
grid-column: 3;
background-color: var(--secondary5);
min-height: 0px;
overflow-y: hidden;
}
.components-nav-header {
font-size: 0.75rem;
color: #999;
text-transform: uppercase;
}
.nav-group-header {
font-size: 0.9rem;
padding-left: 1rem;
}
.nav-items-container {
padding: 1rem 0rem 0rem 0rem;
}
.nav-group-header {
display: flex;
padding: 2rem 0 0 0;
font-size: 0.9rem;
font-weight: bold;
justify-content: space-between;
align-items: center;
}
.nav-group-header > div:nth-child(1) {
padding: 0rem 0.5rem 0rem 0rem;
vertical-align: bottom;
grid-column-start: icon;
margin-right: 5px;
}
.nav-group-header > span:nth-child(2) {
margin-left: 5px;
vertical-align: bottom;
grid-column-start: title;
margin-top: auto;
}
.nav-group-header > div:nth-child(3) {
vertical-align: bottom;
grid-column-start: button;
cursor: pointer;
color: var(--primary75);
}
.nav-group-header > div:nth-child(3):hover {
color: var(--primary75);
}
.navigator-title {
text-transform: uppercase;
font-weight: 400;
color: #999;
}
</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 () => {
const withFieldWithMaxLength = (hierarchy) => {
const withFieldWithMaxLength = hierarchy => {
const surname = find(
hierarchy.customerRecord.fields,
f => f.name === "surname"
@ -61,9 +61,7 @@ describe("recordApi > validate", () => {
withFields,
withFieldWithMaxLength
)
const { recordApi } = await setupApphierarchy(
hierarchyCreator
)
const { recordApi } = await setupApphierarchy(hierarchyCreator)
const record = recordApi.getNew("/customers", "customer")
record.surname = "more than 5 chars"
@ -74,7 +72,7 @@ describe("recordApi > validate", () => {
})
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")
age.typeOptions.maxValue = 10
age.typeOptions.minValue = 5
@ -84,9 +82,7 @@ describe("recordApi > validate", () => {
withFields,
withFieldWithMaxLength
)
const { recordApi } = await setupApphierarchy(
hierarchyCreator
)
const { recordApi } = await setupApphierarchy(hierarchyCreator)
const tooOldRecord = recordApi.getNew("/customers", "customer")
tooOldRecord.age = 11
@ -97,7 +93,7 @@ describe("recordApi > validate", () => {
})
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")
age.typeOptions.minValue = 5
}
@ -106,9 +102,7 @@ describe("recordApi > validate", () => {
withFields,
withFieldWithMaxLength
)
const { recordApi } = await setupApphierarchy(
hierarchyCreator
)
const { recordApi } = await setupApphierarchy(hierarchyCreator)
const tooYoungRecord = recordApi.getNew("/customers", "customer")
tooYoungRecord.age = 3
@ -128,9 +122,7 @@ describe("recordApi > validate", () => {
withFields,
withFieldWithMaxLength
)
const { recordApi } = await setupApphierarchy(
hierarchyCreator
)
const { recordApi } = await setupApphierarchy(hierarchyCreator)
const record = recordApi.getNew("/customers", "customer")
record.age = 3.123
@ -141,7 +133,7 @@ describe("recordApi > validate", () => {
})
it("should return error when datetime field is > maxValue", async () => {
const withFieldWithMaxLength = (hierarchy) => {
const withFieldWithMaxLength = hierarchy => {
const createddate = find(
hierarchy.customerRecord.fields,
f => f.name === "createddate"
@ -153,9 +145,7 @@ describe("recordApi > validate", () => {
withFields,
withFieldWithMaxLength
)
const { recordApi } = await setupApphierarchy(
hierarchyCreator
)
const { recordApi } = await setupApphierarchy(hierarchyCreator)
const record = recordApi.getNew("/customers", "customer")
record.createddate = addHours(new Date(), 1)
@ -166,7 +156,7 @@ describe("recordApi > validate", () => {
})
it("should return error when number field is < minValue", async () => {
const withFieldWithMaxLength = (hierarchy) => {
const withFieldWithMaxLength = hierarchy => {
const createddate = find(
hierarchy.customerRecord.fields,
f => f.name === "createddate"
@ -178,9 +168,7 @@ describe("recordApi > validate", () => {
withFields,
withFieldWithMaxLength
)
const { recordApi } = await setupApphierarchy(
hierarchyCreator
)
const { recordApi } = await setupApphierarchy(hierarchyCreator)
const record = recordApi.getNew("/customers", "customer")
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 () => {
const withFieldWithMaxLength = (hierarchy) => {
const withFieldWithMaxLength = hierarchy => {
const surname = find(
hierarchy.customerRecord.fields,
f => f.name === "surname"
@ -204,9 +192,7 @@ describe("recordApi > validate", () => {
withFields,
withFieldWithMaxLength
)
const { recordApi } = await setupApphierarchy(
hierarchyCreator
)
const { recordApi } = await setupApphierarchy(hierarchyCreator)
const record = recordApi.getNew("/customers", "customer")
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 () => {
const withFieldWithMaxLength = (hierarchy) => {
const withFieldWithMaxLength = hierarchy => {
const surname = find(
hierarchy.customerRecord.fields,
f => f.name === "surname"

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

@ -1,8 +1,8 @@
<script>
import "@material/button/mdc-button.scss";
export let raised = false;
import "@material/button/mdc-button.scss"
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>
<button class={c}>

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

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

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

@ -1,31 +1,24 @@
<script>
import createApp from "./createApp";
import { props } from "./props";
import createApp from "./createApp"
import { props } from "./props"
let _bb;
let _bb
const _appPromise = createApp();
_appPromise.then(a => (_bb = a));
const _appPromise = createApp()
_appPromise.then(a => (_bb = a))
const testProps = props.justAnH1;
const button = props.button;
const testProps = props.justAnH1
const button = props.button
let currentComponent;
let currentComponent
$: {
if (_bb && currentComponent) {
_bb.hydrateChildren([testProps, button], currentComponent);
_bb.hydrateChildren([testProps, button], currentComponent)
}
}
</script>
<style>
#current_component {
height: 100%;
width: 100%;
}
</style>
{#await _appPromise}
loading
{:then _bb}
@ -33,3 +26,10 @@
<div id="current_component" bind:this={currentComponent} />
{/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 &&
!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) {
@ -12812,7 +12814,8 @@ var app = (function(exports) {
// Non `Object` object instances with different constructors are not equal.
if (
objCtor != othCtor &&
"constructor" in object && "constructor" in other &&
"constructor" in object &&
"constructor" in other &&
!(
typeof objCtor == "function" &&
objCtor instanceof objCtor &&
@ -13306,7 +13309,9 @@ var app = (function(exports) {
return (
!!length &&
(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 &&
!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) {
@ -12812,7 +12814,8 @@ var app = (function(exports) {
// Non `Object` object instances with different constructors are not equal.
if (
objCtor != othCtor &&
"constructor" in object && "constructor" in other &&
"constructor" in object &&
"constructor" in other &&
!(
typeof objCtor == "function" &&
objCtor instanceof objCtor &&
@ -13306,7 +13309,9 @@ var app = (function(exports) {
return (
!!length &&
(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 &&
!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) {
@ -12812,7 +12814,8 @@ var app = (function(exports) {
// Non `Object` object instances with different constructors are not equal.
if (
objCtor != othCtor &&
"constructor" in object && "constructor" in other &&
"constructor" in object &&
"constructor" in other &&
!(
typeof objCtor == "function" &&
objCtor instanceof objCtor &&
@ -13306,7 +13309,9 @@ var app = (function(exports) {
return (
!!length &&
(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 &&
!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) {
@ -12812,7 +12814,8 @@ var app = (function(exports) {
// Non `Object` object instances with different constructors are not equal.
if (
objCtor != othCtor &&
"constructor" in object && "constructor" in other &&
"constructor" in object &&
"constructor" in other &&
!(
typeof objCtor == "function" &&
objCtor instanceof objCtor &&
@ -13306,7 +13309,9 @@ var app = (function(exports) {
return (
!!length &&
(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 &&
!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) {
@ -12812,7 +12814,8 @@ var app = (function(exports) {
// Non `Object` object instances with different constructors are not equal.
if (
objCtor != othCtor &&
"constructor" in object && "constructor" in other &&
"constructor" in object &&
"constructor" in other &&
!(
typeof objCtor == "function" &&
objCtor instanceof objCtor &&
@ -13306,7 +13309,9 @@ var app = (function(exports) {
return (
!!length &&
(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 &&
!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) {
@ -12812,7 +12814,8 @@ var app = (function(exports) {
// Non `Object` object instances with different constructors are not equal.
if (
objCtor != othCtor &&
"constructor" in object && "constructor" in other &&
"constructor" in object &&
"constructor" in other &&
!(
typeof objCtor == "function" &&
objCtor instanceof objCtor &&
@ -13306,7 +13309,9 @@ var app = (function(exports) {
return (
!!length &&
(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 &&
!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) {
@ -16826,7 +16828,8 @@ var app = (function() {
// Non `Object` object instances with different constructors are not equal.
if (
objCtor != othCtor &&
"constructor" in object && "constructor" in other &&
"constructor" in object &&
"constructor" in other &&
!(
typeof objCtor == "function" &&
objCtor instanceof objCtor &&
@ -17320,7 +17323,9 @@ var app = (function() {
return (
!!length &&
(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>
export let _children = [];
export let className="";
export let onLoad;
export let _bb;
let rootDiv;
$:{
if(_bb && rootDiv && _children && _children.length)
_bb.hydrateChildren(_children, rootDiv);
}
export let _children = []
export let className = ""
export let onLoad
export let _bb
let rootDiv
$: {
if (_bb && rootDiv && _children && _children.length)
_bb.hydrateChildren(_children, rootDiv)
}
</script>
<div class="{className}" bind:this={rootDiv}>
</div>
<div class={className} bind:this={rootDiv} />

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save