Browse Source

#48 restructure. Screens in, user components out. _children static concept

gh-pages
Michael Shanks 7 years ago
parent
commit
eac855bb71
  1. 4
      packages/bootstrap-components/src/Form.svelte
  2. 2
      packages/bootstrap-components/src/Nav.svelte
  3. 2
      packages/bootstrap-components/src/Test/TestApp.svelte
  4. 157
      packages/builder/src/builderStore/store.js
  5. 114
      packages/builder/src/userInterface/ComponentInstanceEditor.svelte
  6. 139
      packages/builder/src/userInterface/ComponentPropSelector.svelte
  7. 6
      packages/builder/src/userInterface/ComponentSearch.svelte
  8. 12
      packages/builder/src/userInterface/ComponentSelector.svelte
  9. 4
      packages/builder/src/userInterface/ComponentsHierarchy.svelte
  10. 142
      packages/builder/src/userInterface/ComponentsList.svelte
  11. 79
      packages/builder/src/userInterface/ComponentsPaneSwitcher.svelte
  12. 5
      packages/builder/src/userInterface/CurrentItemPreview.svelte
  13. 117
      packages/builder/src/userInterface/EditComponentProps.svelte
  14. 161
      packages/builder/src/userInterface/GeneratedComponents.svelte
  15. 138
      packages/builder/src/userInterface/NewComponent.svelte
  16. 2
      packages/builder/src/userInterface/PageView.svelte
  17. 120
      packages/builder/src/userInterface/PropArraySelector.svelte
  18. 25
      packages/builder/src/userInterface/PropControl.svelte
  19. 105
      packages/builder/src/userInterface/PropsView.svelte
  20. 6
      packages/builder/src/userInterface/SettingsView.svelte
  21. 31
      packages/builder/src/userInterface/UserInterfaceRoot.svelte
  22. 65
      packages/builder/src/userInterface/pagesParsing/buildPropsHierarchy.js
  23. 166
      packages/builder/src/userInterface/pagesParsing/createProps.js
  24. 17
      packages/builder/src/userInterface/pagesParsing/findDependencies.js
  25. 6
      packages/builder/src/userInterface/pagesParsing/getRootComponent.js
  26. 34
      packages/builder/src/userInterface/pagesParsing/renameScreen.js
  27. 23
      packages/builder/src/userInterface/pagesParsing/searchComponents.js
  28. 29
      packages/builder/src/userInterface/pagesParsing/types.js
  29. 20
      packages/builder/src/userInterface/pagesParsing/validatePages.js
  30. 133
      packages/builder/src/userInterface/pagesParsing/validateProps.js
  31. 32
      packages/builder/tests/buildPropsHierarchy.spec.js
  32. 32
      packages/builder/tests/componentDependencies.spec.js
  33. 165
      packages/builder/tests/createDefaultProps.spec.js
  34. 56
      packages/builder/tests/expandPropDef.spec.js
  35. 57
      packages/builder/tests/getComponentInfo.spec.js
  36. 79
      packages/builder/tests/renameComponent.spec.js
  37. 61
      packages/builder/tests/renameScreen.spec.js
  38. 51
      packages/builder/tests/searchComponentsProps.spec.js
  39. 184
      packages/builder/tests/testData.js
  40. 26
      packages/builder/tests/validatePages.spec.js
  41. 244
      packages/builder/tests/validateProps.spec.js
  42. 51
      packages/client/src/createApp.js
  43. 4
      packages/client/src/index.js
  44. 71
      packages/server/builder/budibase-client.esm.mjs
  45. 49
      packages/server/builder/bundle.css
  46. 102
      packages/server/builder/bundle.css.map
  47. 65293
      packages/server/builder/bundle.js
  48. 2
      packages/server/builder/bundle.js.map
  49. 26
      packages/server/middleware/routers.js
  50. 38
      packages/server/tests/builder.spec.js
  51. 20
      packages/server/utilities/builder/index.js
  52. 29
      packages/standard-components/components.json
  53. 2
      packages/standard-components/public/bundle.js
  54. 14
      packages/standard-components/src/Div.svelte
  55. 2
      packages/standard-components/src/Form.svelte
  56. 2
      packages/standard-components/src/Grid.svelte
  57. 4
      packages/standard-components/src/If.svelte
  58. 4
      packages/standard-components/src/Nav.svelte
  59. 8
      packages/standard-components/src/Panel.svelte
  60. 4
      packages/standard-components/src/StackPanel.svelte
  61. 2
      packages/standard-components/src/Test/TestApp.svelte
  62. 2
      packages/standard-components/src/button.svelte

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

@ -21,12 +21,12 @@ $ : {
for(let el in htmlElements) {
if(formControls[el].control.controlPosition === "Before Label") {
_bb.insertComponent(
_bb.insertChildren(
_bb.props.formControls[el].control,
htmlElements[el],
htmlElements[el].childNodes.find(n => n.tagName === "LABEL"));
} else {
_bb.appendComponent(
_bb.appendChildren(
_bb.props.formControls[el].control,
htmlElements[el]);
}

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

@ -79,7 +79,7 @@ const SelectItem = (index) => {
}
if(index >= 0)
currentComponent = _bb.hydrateComponent(
currentComponent = _bb.hydrateChildren(
_bb.props.items[index].component, componentElement);
}

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

@ -13,7 +13,7 @@ let currentComponent;
$: {
if(_bb && currentComponent) {
_bb.hydrateComponent(testProps, currentComponent);
_bb.hydrateChildren(testProps, currentComponent);
}
}

157
packages/builder/src/builderStore/store.js

@ -15,9 +15,9 @@ import { defaultPagesObject } from "../userInterface/pagesParsing/defaultPagesOb
import { buildPropsHierarchy } from "../userInterface/pagesParsing/buildPropsHierarchy"
import api from "./api";
import { isRootComponent, getExactComponent } from "../userInterface/pagesParsing/searchComponents";
import { rename } from "../userInterface/pagesParsing/renameComponent";
import { rename } from "../userInterface/pagesParsing/renameScreen";
import {
getComponentInfo, getNewComponentInfo
getNewComponentInfo, getScreenInfo
} from "../userInterface/pagesParsing/createProps";
import {
loadLibs, loadLibUrls, loadGeneratorLibs
@ -36,12 +36,12 @@ export const getStore = () => {
pages:defaultPagesObject(),
mainUi:{},
unauthenticatedUi:{},
allComponents:[],
components:[],
currentFrontEndItem:null,
currentComponentInfo:null,
currentComponentIsNew:false,
currentFrontEndType:"none",
currentPageName: "",
currentComponentProps:null,
currentNodeIsNew: false,
errors: [],
activeNav: "database",
@ -74,14 +74,14 @@ export const getStore = () => {
store.saveLevel = saveLevel(store);
store.deleteLevel = deleteLevel(store);
store.setActiveNav = setActiveNav(store);
store.saveDerivedComponent = saveDerivedComponent(store);
store.saveScreen = saveScreen(store);
store.refreshComponents = refreshComponents(store);
store.addComponentLibrary = addComponentLibrary(store);
store.renameDerivedComponent = renameDerivedComponent(store);
store.deleteDerivedComponent = deleteDerivedComponent(store);
store.setCurrentComponent = setCurrentComponent(store);
store.renameScreen = renameScreen(store);
store.deleteScreen = deleteScreen(store);
store.setCurrentScreen = setCurrentScreen(store);
store.setCurrentPage = setCurrentPage(store);
store.createDerivedComponent = createDerivedComponent(store);
store.createScreen = createScreen(store);
store.removeComponentLibrary =removeComponentLibrary(store);
store.addStylesheet = addStylesheet(store);
store.removeStylesheet = removeStylesheet(store);
@ -120,10 +120,9 @@ const initialise = (store, initial) => async () => {
initial.hasAppPackage = true;
initial.hierarchy = pkg.appDefinition.hierarchy;
initial.accessLevels = pkg.accessLevels;
initial.derivedComponents = values(pkg.derivedComponents);
initial.generators = generatorsArray(pkg.rootComponents.generators);
initial.allComponents = combineComponents(
pkg.derivedComponents, pkg.rootComponents.components);
initial.screens = values(pkg.screens);
initial.generators = generatorsArray(pkg.components.generators);
initial.components = values(pkg.components.components);
initial.actions = values(pkg.appDefinition.actions);
initial.triggers = pkg.appDefinition.triggers;
@ -175,17 +174,6 @@ const showFrontend = store => () => {
})
}
const combineComponents = (root, derived) => {
const all = []
for(let r in root) {
all.push(root[r]);
}
for(let d in derived) {
all.push(derived[d]);
}
return all;
}
const newRecord = (store, useRoot) => () => {
store.update(s => {
s.currentNodeIsNew = true;
@ -440,55 +428,50 @@ const setActiveNav = store => navName => {
const createShadowHierarchy = hierarchy =>
constructHierarchy(JSON.parse(JSON.stringify(hierarchy)));
const saveDerivedComponent = store => (derivedComponent) => {
const saveScreen = store => (screen) => {
store.update(s => {
return _saveScreen(store, s, screen);
})
};
const components = pipe(s.allComponents, [
filter(c => c.name !== derivedComponent.name),
concat([derivedComponent])
]);
const _saveScreen = (store, s, screen) => {
const screens = pipe(s.screens, [
filter(c => c.name !== screen.name),
concat([screen])
]);
const derivedComponents = pipe(s.derivedComponents, [
filter(c => c.name !== derivedComponent.name),
concat([derivedComponent])
]);
s.screens = screens;
s.currentFrontEndItem = screen;
s.currentComponentInfo = getScreenInfo(
s.components, screen);
s.allComponents = components;
s.derivedComponents = derivedComponents;
s.currentFrontEndItem = derivedComponent;
s.currentComponentInfo = getComponentInfo(
s.allComponents, derivedComponent.name);
s.currentComponentIsNew = false;
api.post(`/_builder/api/${s.appname}/derivedcomponent`, derivedComponent)
.then(() => savePackage(store, s));
api.post(`/_builder/api/${s.appname}/screen`, screen)
.then(() => savePackage(store, s));
return s;
})
};
return s;
}
const createDerivedComponent = store => componentName => {
const createScreen = store => (screenName, layoutComponentName) => {
store.update(s => {
const newComponentInfo = getNewComponentInfo(
s.allComponents, componentName);
s.components, layoutComponentName, screenName);
s.currentFrontEndItem = newComponentInfo.component;
s.currentComponentInfo = newComponentInfo;
s.currentFrontEndType = "component";
s.currentComponentIsNew = true;
return s;
s.currentFrontEndType = "screen";
return _saveScreen(store, s, newComponentInfo.component);
});
};
const createGeneratedComponents = store => components => {
store.update(s => {
s.allComponents = [...s.allComponents, ...components];
s.derivedComponents = [...s.derivedComponents, ...components];
s.components = [...s.components, ...components];
s.screens = [...s.screens, ...components];
const doCreate = async () => {
for(let c of components) {
await api.post(`/_builder/api/${s.appname}/derivedcomponent`, c);
await api.post(`/_builder/api/${s.appname}/screen`, c);
}
await savePackage(store, s);
@ -500,55 +483,56 @@ const createGeneratedComponents = store => components => {
});
};
const deleteDerivedComponent = store => name => {
const deleteScreen = store => name => {
store.update(s => {
const allComponents = pipe(s.allComponents, [
const components = pipe(s.components, [
filter(c => c.name !== name)
]);
const derivedComponents = pipe(s.derivedComponents, [
const screens = pipe(s.screens, [
filter(c => c.name !== name)
]);
s.allComponents = allComponents;
s.derivedComponents = derivedComponents;
s.components = components;
s.screens = screens;
if(s.currentFrontEndItem.name === name) {
s.currentFrontEndItem = null;
s.currentFrontEndType = "";
}
api.delete(`/_builder/api/${s.appname}/derivedcomponent/${name}`);
api.delete(`/_builder/api/${s.appname}/screen/${name}`);
return s;
})
}
const renameDerivedComponent = store => (oldname, newname) => {
const renameScreen = store => (oldname, newname) => {
store.update(s => {
const {
allComponents, pages, error, changedComponents
} = rename(s.pages, s.allComponents, oldname, newname);
screens, pages, error, changedScreens
} = rename(s.pages, s.screens, oldname, newname);
if(error) {
// should really do something with this
return s;
}
s.allComponents = allComponents;
s.screens = screens;
s.pages = pages;
if(s.currentFrontEndItem.name === oldname)
s.currentFrontEndItem.name = newname;
const saveAllChanged = async () => {
for(let cname of changedComponents) {
const changedComponent = getExactComponent(allComponents, cname);
await api.post(`/_builder/api/${s.appname}/derivedcomponent`, changedComponent);
for(let screenName of changedScreens) {
const changedScreen
= getExactComponent(screens, screenName);
await api.post(`/_builder/api/${s.appname}/screen`, changedScreen);
}
}
api.patch(`/_builder/api/${s.appname}/derivedcomponent`, {
api.patch(`/_builder/api/${s.appname}/screen`, {
oldname, newname
})
.then(() => saveAllChanged())
@ -597,7 +581,7 @@ const addComponentLibrary = store => async lib => {
componentsArray.push(components[c]);
}
s.allComponents = pipe(s.allComponents, [
s.components = pipe(s.components, [
filter(c => !c.name.startsWith(`${lib}/`)),
concat(componentsArray)
]);
@ -643,20 +627,20 @@ const removeStylesheet = store => stylesheet => {
const refreshComponents = store => async () => {
const components =
await api.get(`/_builder/api/${db.appname}/rootcomponents`).then(r => r.json());
const componentsAndGenerators =
await api.get(`/_builder/api/${db.appname}/components`).then(r => r.json());
const rootComponents = pipe(components.components, [
const components = pipe(componentsAndGenerators.components, [
keys,
map(k => ({...components[k], name:k}))
map(k => ({...componentsAndGenerators[k], name:k}))
]);
store.update(s => {
s.allComponents = pipe(s.allComponents, [
s.components = pipe(s.components, [
filter(c => !isRootComponent(c)),
concat(rootComponents)
concat(components)
]);
s.generators = components.generators;
s.generators = componentsAndGenerators.generators;
return s;
});
};
@ -668,8 +652,14 @@ const savePackage = (store, s) => {
triggers:s.triggers,
actions: keyBy("name")(s.actions),
props: {
main: buildPropsHierarchy(s.allComponents, s.pages.main.appBody),
unauthenticated: buildPropsHierarchy(s.allComponents, s.pages.unauthenticated.appBody)
main: buildPropsHierarchy(
s.components,
s.screens,
s.pages.main.appBody),
unauthenticated: buildPropsHierarchy(
s.components,
s.screens,
s.pages.unauthenticated.appBody)
}
};
@ -682,13 +672,12 @@ const savePackage = (store, s) => {
return api.post(`/_builder/api/${s.appname}/appPackage`, data);
}
const setCurrentComponent = store => componentName => {
const setCurrentScreen = store => screenName => {
store.update(s => {
const component = getExactComponent(s.allComponents, componentName);
s.currentFrontEndItem = component;
s.currentFrontEndType = "component";
s.currentComponentIsNew = false;
s.currentComponentInfo = getComponentInfo(s.allComponents, component.name);
const screen = getExactComponent(s.screens, screenName);
s.currentFrontEndItem = screen;
s.currentFrontEndType = "screen";
s.currentComponentInfo = getScreenInfo(s.components, screen);
return s;
})
}

114
packages/builder/src/userInterface/ComponentInstanceEditor.svelte

@ -1,114 +0,0 @@
<script>
import PropsView from "./PropsView.svelte";
import IconButton from "../common/IconButton.svelte";
import { getComponentInfo } from "./pagesParsing/createProps";
import { store } from "../builderStore";
import {
cloneDeep,
isUndefined
} from "lodash/fp";
import { fade, slide } from 'svelte/transition';
export let title = "";
export let onGoBack = () => {};
export let instanceProps = {};
export let onPropsChanged = () => {};
let editingSubComponentName;
let editingSubComponentProps;
let editingSubComponentArrayIndex;
let editingSubComponentArrayPropName;
let editingSubComponentTitle;
let allComponents;
store.subscribe(s => {
allComponents = s.allComponents;
})
$: componentInfo = getComponentInfo(
allComponents, instanceProps._component);
const onSubComponentGoBack = () => {
editingSubComponentName = null;
editingSubComponentProps = null;
}
const onEditComponentProp = (propName, arrayIndex, arrayPropName) => {
editingSubComponentName = propName;
editingSubComponentTitle = isUndefined(arrayIndex)
? propName
: `${propName}[${arrayIndex}].${arrayPropName}`;
editingSubComponentProps = isUndefined(arrayIndex)
? instanceProps[propName]
: instanceProps[propName][arrayIndex][arrayPropName];
editingSubComponentArrayIndex = arrayIndex;
editingSubComponentArrayPropName = arrayPropName;
};
const onSubComponentPropsChanged = (subProps) => {
const newProps = cloneDeep(instanceProps);
if(isUndefined(editingSubComponentArrayIndex)) {
newProps[editingSubComponentName] = subProps;
} else {
newProps[editingSubComponentName]
[editingSubComponentArrayIndex]
[editingSubComponentArrayPropName] = subProps;
}
instanceProps = newProps;
onPropsChanged(newProps);
}
const propsChanged = newProps => {
instanceProps = newProps;
onPropsChanged(newProps);
}
</script>
<div>
<div class="title">
<IconButton icon="chevron-left"
on:click={onGoBack}/>
<span>{title}</span>
</div>
{#if editingSubComponentName}
<div in:slide={{delay: 250, duration: 300}}
out:fade>
<svelte:self onPropsChanged={onSubComponentPropsChanged}
onGoBack={onSubComponentGoBack}
instanceProps={editingSubComponentProps}
title={editingSubComponentTitle} />
</div>
{:else}
<PropsView {instanceProps}
{componentInfo}
onPropsChanged={propsChanged}
{onEditComponentProp} />
{/if}
</div>
<style>
.title {
padding:3px;
background-color: white;
color: var(--secondary100);
border-style:solid;
border-width: 1px 0 0 0;
border-color: var(--lightslate);
}
.title > span {
margin-left: 10px;
}
</style>

139
packages/builder/src/userInterface/ComponentPropSelector.svelte

@ -1,139 +0,0 @@
<script>
import {
last
} from "lodash/fp";
import IconButton from "../common/IconButton.svelte";
import ComponentSelector from "./ComponentSelector.svelte";
import Button from "../common/Button.svelte";
import ButtonGroup from "../common/ButtonGroup.svelte";
import UIkit from "uikit";
import {
getComponentInfo
} from "./pagesParsing/createProps";
import { store } from "../builderStore";
const emptyProps = () => ({_component:""})
export let props = emptyProps();
export let onValueChanged = () => {};
export let onComponentChosen = () => {};
export let onEdit = () => {};
export let disabled = false;
const CHOOSE_COMPONENT = "choose_component";
const CLEAR_COMPONENT = "clear_component";
let allComponents;
let modalElement;
let modalAction;
store.subscribe(s => {
allComponents = s.allComponents;
});
$: componentSelected = props._component.length > 0;
$: shortName = last(props._component.split("/"));
const chooseComponent = () => {
modalAction = CHOOSE_COMPONENT;
showDialog();
}
const clearComponent = () => {
modalAction = CLEAR_COMPONENT;
showDialog();
}
const componentChosen = (component) => {
const componentInfo = getComponentInfo(allComponents, component.name);
props = componentInfo.fullProps;
onValueChanged(props);
onComponentChosen();
hideDialog();
}
const hideDialog = () => {
UIkit.modal(modalElement).hide();
}
const showDialog = () => {
UIkit.modal(modalElement).show();
}
const confirmClearComponent = () => {
props = emptyProps();
onValueChanged(emptyProps());
hideDialog();
}
</script>
<div class="root uk-form-controls">
<div class:selectedname={componentSelected}>
{componentSelected ? shortName : "(none)"}
</div>
<div>
{#if !disabled && componentSelected}
<IconButton icon="edit"
on:click={() => onEdit()}/>
<IconButton icon="trash"
on:click={() => clearComponent()} />
{:else if !disabled && !componentSelected}
<IconButton icon="plus"
on:click={() => chooseComponent()} />
{/if}
</div>
</div>
<div bind:this={modalElement} uk-modal>
<div class="uk-modal-dialog" uk-overflow-auto>
{#if modalAction === CHOOSE_COMPONENT}
<div class="uk-modal-body">
<ComponentSelector onComponentChosen={componentChosen}
allowGenerators={false} />
</div>
{:else if modalAction === CLEAR_COMPONENT}
<div class="uk-modal-body">
Clear this component ?
</div>
<div class="uk-modal-footer">
<ButtonGroup>
<Button grouped
on:click={hideDialog}
color="secondary" >Cancel</Button>
<Button grouped
on:click={confirmClearComponent}>OK</Button>
</ButtonGroup>
</div>
{/if}
</div>
</div>
<style>
.root {
display: grid;
grid-template-columns: [name] 1fr [actions] auto;
}
.root > div:nth-child(1) {
grid-column-start: name;
color: var(--secondary50);
}
.root > div:nth-child(2) {
grid-column-start: actions;
}
.selectedname {
font-weight: bold;
color: var(--secondary);
}
</style>

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

@ -5,17 +5,17 @@ import { store } from "../builderStore";
export let onComponentChosen = () => {};
let allComponents = [];
let components = [];
let phrase = "";
store.subscribe(s => {
allComponents = s.allComponents;
components = s.components;
});
$: filteredComponents =
!phrase
? []
: searchAllComponents(allComponents, phrase);
: searchAllComponents(components, phrase);
</script>

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

@ -13,7 +13,7 @@ export let onComponentChosen;
export let onGeneratorChosen;
export let allowGenerators;
let derivedComponents=[];
let screens=[];
let componentLibraries=[];
const addRootComponent = (c, all, isGenerator) => {
@ -41,16 +41,16 @@ const addRootComponent = (c, all, isGenerator) => {
store.subscribe(s => {
const newComponentLibraries = [];
const newDerivedComponents = [];
const newscreens = [];
for(let comp of sortBy(["name"])(s.allComponents)) {
for(let comp of sortBy(["name"])(s.components)) {
if(isRootComponent(comp)) {
addRootComponent(
comp,
newComponentLibraries,
false);
} else {
newDerivedComponents.push(comp);
newscreens.push(comp);
}
}
@ -61,7 +61,7 @@ store.subscribe(s => {
true);
}
derivedComponents = sortBy(["name"])(newDerivedComponents);
screens = sortBy(["name"])(newscreens);
componentLibraries = newComponentLibraries;
});
@ -127,7 +127,7 @@ store.subscribe(s => {
<div class="library-container">
{#each derivedComponents as component}
{#each screens as component}
<div class="component"
on:click={() => onComponentChosen(component)}>

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

@ -88,7 +88,7 @@ const expandFolder = folder => {
}
const isComponentSelected = (type, current,c) =>
type==="component"
type==="screen"
&& current
&& current.name === c.name
@ -136,7 +136,7 @@ $: {
{#each componentsThisLevel as component}
<div class="hierarchy-item component" class:selected={isComponentSelected($store.currentFrontEndType, $store.currentFrontEndItem, component.component)}
on:click|stopPropagation={() => store.setCurrentComponent(component.component.name)}>
on:click|stopPropagation={() => store.setCurrentScreen(component.component.name)}>
<span>{@html getIcon("circle", "7")}</span>
<span class="title">{component.title}</span>
</div>

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

@ -0,0 +1,142 @@
<script>
import {
isRootComponent
} from "./pagesParsing/searchComponents"
import { splitName } from "./pagesParsing/splitRootComponentName.js"
import { store } from "../builderStore";
import {
groupBy, keys, find, sortBy
} from "lodash/fp";
import { pipe } from "../common/core";
let componentLibraries=[];
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)
};
const onComponentChosen = (component) => {
};
store.subscribe(s => {
const newComponentLibraries = [];
for(let comp of sortBy(["name"])(s.components)) {
addRootComponent(
comp,
newComponentLibraries);
}
componentLibraries = newComponentLibraries;
});
</script>
<div class="root">
{#each componentLibraries as lib}
<div class="library-header">
{lib.libName}
</div>
<div class="library-container">
<div class="inner-header">
Components
</div>
{#each lib.components as component}
<div class="component"
on:click={() => onComponentChosen(component)}>
<div class="name">
{splitName(component.name).componentName}
</div>
<div class="description">
{component.description}
</div>
</div>
{/each}
</div>
{/each}
</div>
<style>
.root {
display: flex;
flex-direction: column;
}
.library-header {
font-size: 1.1em;
border-color: var(--primary25);
border-width: 1px 0px;
border-style: solid;
background-color: var(--primary10);
padding: 5px 0;
flex: 0 0 auto;
}
.library-container {
padding: 0 0 10px 10px;
flex: 1 1 auto;
min-height: 0px;
}
.inner-header {
font-size: 0.9em;
font-weight: bold;
margin-top: 7px;
margin-bottom: 3px;
}
.component {
padding: 2px 0px;
cursor: pointer;
}
.component:hover {
background-color: var(--lightslate);
}
.component > .name {
color: var(--secondary100);
display: inline-block;
}
.component > .description {
font-size: 0.8em;
color: var(--secondary75);
display: inline-block;
margin-left: 10px;
}
</style>

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

@ -0,0 +1,79 @@
<script>
import EditComponentProps from "./EditComponentProps.svelte";
import ComponentsList from "./ComponentsList.svelte";
let selected="properties";
const isSelected = tab =>
selected === tab;
const selectTab = tab =>
selected = tab;
</script>
<div class="root">
<div class="switcher">
<button
class:selected={selected==="properties"}
on:click={() => selectTab("properties")}>
Properties
</button>
<button
class:selected={selected==="components"}
on:click={() => selectTab("components")}>
Components
</button>
</div>
<div class="panel">
{#if selected==="properties"}
<EditComponentProps />
{/if}
{#if selected==="components"}
<ComponentsList />
{/if}
</div>
</div>
<style>
.root {
height: 100%;
display: flex;
flex-direction: column;
}
.switcher {
flex: 0 0 auto;
}
.switcher > button {
display: inline-block;
background-color: rgba(0,0,0,0);
border-style: solid;
border-color: var(--slate);
margin: 5px;
padding: 5px;
cursor: pointer;
}
.switcher > .selected {
background-color: red;
}
.panel {
flex: 1 1 auto;
height: 0px;
overflow-y: auto;
}
</style>

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

@ -24,7 +24,10 @@ store.subscribe(s => {
]);
appDefinition = {
componentLibraries: s.loadLibraryUrls(),
props: buildPropsHierarchy(s.allComponents, s.currentFrontEndItem),
props: buildPropsHierarchy(
s.components,
s.screens,
s.currentFrontEndItem),
hierarchy: s.hierarchy,
appRootPath: ""
};

117
packages/builder/src/userInterface/EditComponent.svelte → packages/builder/src/userInterface/EditComponentProps.svelte

@ -8,11 +8,10 @@ import Textbox from "../common/Textbox.svelte";
import UIkit from "uikit";
import { pipe } from "../common/core";
import {
getComponentInfo
getScreenInfo
} from "./pagesParsing/createProps";
import Button from "../common/Button.svelte";
import ButtonGroup from "../common/ButtonGroup.svelte";
import ComponentInstanceEditor from "./ComponentInstanceEditor.svelte";
import {
cloneDeep,
@ -30,17 +29,11 @@ let name = "";
let description = "";
let tagsString = "";
let nameInvalid = "";
let componentDetailsExpanded = false;
let componentInfo;
let modalElement
let propsValidationErrors = [];
let editingComponentInstance;
let editingComponentInstancePropName="";
let editingComponentArrayIndex;
let editingComponentArrayPropName;
let editingComponentInstanceTitle;
let originalName="";
let allComponents;
let components;
let ignoreStore = false;
$: shortName = last(name.split("/"));
@ -54,8 +47,7 @@ store.subscribe(s => {
description = component.description;
tagsString = join(", ")(component.tags);
componentInfo = s.currentComponentInfo;
componentDetailsExpanded = s.currentComponentIsNew;
allComponents = s.allComponents;
components = s.components;
});
const save = () => {
@ -73,12 +65,12 @@ const save = () => {
map(s => s.trim())
]);
store.saveDerivedComponent(component);
store.saveScreen(component);
ignoreStore = false;
// now do the rename
if(name !== originalName) {
store.renameDerivedComponent(originalName, name);
store.renameScreen(originalName, name);
}
}
@ -87,7 +79,7 @@ const deleteComponent = () => {
}
const confirmDeleteComponent = () => {
store.deleteDerivedComponent(component.name);
store.deleteScreen(component.name);
hideDialog();
}
@ -99,7 +91,7 @@ const updateComponent = doChange => {
const newComponent = cloneDeep(component);
doChange(newComponent);
component = newComponent;
componentInfo = getComponentInfo(allComponents, newComponent);
componentInfo = getScreenInfo(components, newComponent);
}
const onPropsChanged = newProps => {
@ -128,37 +120,6 @@ const showDialog = () => {
UIkit.modal(modalElement).show();
}
const onEditComponentProp = (propName, arrayIndex, arrayPropName) => {
editingComponentInstance = isUndefined(arrayIndex)
? component.props[propName]
: component.props[propName][arrayIndex][arrayPropName];
editingComponentInstancePropName = propName;
editingComponentInstanceTitle = isUndefined(arrayIndex)
? propName
: `${propName}[${arrayIndex}].${arrayPropName}`;
editingComponentArrayIndex = arrayIndex;
editingComponentArrayPropName = arrayPropName;
}
const componentInstanceCancelEdit = () => {
editingComponentInstance = null;
editingComponentInstancePropName = "";
}
const componentInstancePropsChanged = (instanceProps) => {
updateComponent(newComponent => {
if(isUndefined(editingComponentArrayIndex)) {
newComponent.props[editingComponentInstancePropName] = instanceProps;
} else {
newComponent.props[editingComponentInstancePropName]
[editingComponentArrayIndex]
[editingComponentArrayPropName] = instanceProps;
}
});
}
</script>
<div class="root">
@ -177,53 +138,15 @@ const componentInstancePropsChanged = (instanceProps) => {
</div>
</div>
{#if editingComponentInstance}
<div class="component-props-container">
<ComponentInstanceEditor onGoBack={componentInstanceCancelEdit}
title={editingComponentInstanceTitle}
instanceProps={editingComponentInstance}
onPropsChanged={componentInstancePropsChanged}/>
</div>
{:else}
<div class="component-props-container">
<div class="section-header padding" on:click={() => componentDetailsExpanded = !componentDetailsExpanded}>
<span style="margin-right: 7px">Component Details</span>
<IconButton icon={componentDetailsExpanded ? "chevron-down" : "chevron-right"}/>
</div>
{#if componentDetailsExpanded}
<div class="padding">
<div class="info-text">
<Textbox label="Name"
infoText="use forward slash to store in subfolders"
text={name}
on:change={ev => name = ev.target.value}
hasError={!!nameInvalid}/>
<Textbox label="Description"
on:change={ev => description = ev.target.value}
text={description}/>
<Textbox label="Tags"
infoText="comma separated"
on:change={ev => tagsString = ev.target.value}
text={tagsString}/>
</div>
</div>
{/if}
<div class="section-header padding">
<span>Properties</span>
</div>
<PropsView onValidate={onPropsValidate}
{componentInfo}
{onPropsChanged}
{onEditComponentProp}/>
{onPropsChanged} />
</div>
{/if}
</div>
@ -263,20 +186,13 @@ const componentInstancePropsChanged = (instanceProps) => {
height: 100%;
display: flex;
flex-direction: column;
}
.padding {
padding: 1rem 1rem 0rem 1rem;
}
.info-text {
color: var(--secondary100);
font-size: .8rem !important;
font-weight: bold;
border-style: solid;
border-width: 1px 0 0 0;
border-color: var(--slate);
}
.title {
padding: 2rem 1rem 1rem 1rem;
padding: 1rem;
display: grid;
grid-template-columns: [name] 1fr [actions] auto;
color: var(--secondary100);
@ -293,15 +209,6 @@ const componentInstancePropsChanged = (instanceProps) => {
grid-column-start: actions;
}
.section-header {
display: grid;
grid-template-columns: [name] 1fr [actions] auto;
color: var(--secondary50);
font-size: .9rem;
font-weight: bold;
vertical-align: middle;
}
.component-props-container {
flex: 1 1 auto;
overflow-y: auto;

161
packages/builder/src/userInterface/GeneratedComponents.svelte

@ -1,161 +0,0 @@
<script>
import { store } from "../builderStore";
import { splitName } from "./pagesParsing/splitRootComponentName";
import {
getIndexNodes, getRecordNodes, getIndexSchema, pipe
} from "../common/core";
import {
map, some, filter
} from "lodash/fp";
import Button from "../common/Button.svelte";
import { componentDependencies } from "./pagesParsing/findDependencies";
import { rename } from "./pagesParsing/renameComponent";
import { getExactComponent } from "./pagesParsing/searchComponents";
export let generator;
export let onConfirmGenerate;
let libName;
let componentName;
let libs;
let existingComponents;
let _generator;
let components;
let generateParameter;
let allGeneratedComponents;
let selectedComponents = [];
store.subscribe(s => {
libs = s.generatorLibraries;
generateParameter = {
indexes: getIndexNodes(s.hierarchy),
records: getRecordNodes(s.hierarchy),
helpers: {
indexSchema: getIndexSchema(s.hierarchy)
}
}
existingComponents = s.allComponents;
});
const componentExists = name =>
getExactComponent(existingComponents, name);
const componentsWithDependencies = () => {
const cmp = map(c => {
const dependants = componentDependencies(
{}, [...selectedComponents, ...existingComponents], c);
const exists = componentExists(c.name);
return {
dependants: dependants.dependantComponents,
component:c,
error: exists ? "a component by this name already exists" : ""
};
})(allGeneratedComponents);
components = cmp;
}
$ : {
if(generator && generator !== _generator) {
_generator = generator;
const sp = splitName(generator.name);
libName = sp.libName;
componentName = sp.componentName;
allGeneratedComponents = libs[libName][componentName](generateParameter);
selectedComponents =
filter(c => !componentExists(c.name))(allGeneratedComponents);
componentsWithDependencies();
}
}
const onSelectedChanged = component => ev => {
const newselectedComponents = filter(c => c.name !== component.component.name)(
selectedComponents);
if(ev.target.checked) {
newselectedComponents.push(component.component);
}
selectedComponents = newselectedComponents;
componentsWithDependencies();
}
const onNameChanged = component => ev => {
const newname = ev.target.value;
const oldname = component.component.name;
const result = rename({}, allGeneratedComponents, oldname, newname);
component.error = result.error || "";
allGeneratedComponents = [...result.allComponents];
selectedComponents = map(s => {
if(s.name === oldname) s.name = newname;
return s;
})(selectedComponents);
componentsWithDependencies();
}
const isComponentSelected = component =>
some(c => c.name === component.component.name)(selectedComponents);
</script>
{#each components as c}
<div class="component">
<div class="uk-inline">
<input type="checkbox"
disabled={c.dependants.length > 0}
class="uk-checkbox"
checked={isComponentSelected(c)}
on:change={onSelectedChanged(c)}>
<input type="text"
value={c.component.name}
on:change={onNameChanged(c)}
class="uk-input title {c.error ? 'uk-form-danger' : ''}">
{#if isComponentSelected(c)}
<span class="error">{c.error}</span>
{/if}
</div>
<div class="description">
{c.component.description}
</div>
</div>
{/each}
<div class="button-container">
<Button on:click={() => onConfirmGenerate(selectedComponents)}>Add Components</Button>
</div>
<style>
.component {
padding: 5px 0;
}
.component .title {
width: 300px
}
.component > .description {
font-size: 0.8em;
color: var(--secondary75);
}
.button-container {
text-align: right;
margin-top: 20px;
}
.error {
font-size: 10pt;
color: red;
}
</style>

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

@ -8,100 +8,126 @@ import Button from "../common/Button.svelte";
import ButtonGroup from "../common/ButtonGroup.svelte";
import { pipe } from "../common/core";
import UIkit from "uikit";
import {
getNewComponentInfo
} from "./pagesParsing/createProps";
import { isRootComponent } from "./pagesParsing/searchComponents";
import GeneratedComponents from "./GeneratedComponents.svelte";
import { splitName } from "./pagesParsing/splitRootComponentName.js"
import {
cloneDeep,
join,
split,
map,
keys,
isUndefined
find, filter, some, map, includes
} from "lodash/fp";
import { assign } from "lodash";
export const show = () => {
UIkit.modal(componentSelectorModal).show();
}
let componentSelectorModal;
let generatorOptionsModal;
let allComponents;
let generator;
let layoutComponents;
let layoutComponent;
let screens;
let name="";
let saveAttempted=false;
store.subscribe(s => {
allComponents = s.allComponents;
})
export const close = () => {
UIkit.modal(componentSelectorModal).hide();
if(generatorOptionsModal) UIkit.modal(generatorOptionsModal).hide();
generator = null;
}
layoutComponents = pipe(s.components, [
filter(c => includes("layout")(c.tags)),
map(c => ({name:c.name, ...splitName(c.name)}))
]);
export const show = () => {
UIkit.modal(componentSelectorModal).show();
}
layoutComponent = layoutComponent
? find(c => c.name === layoutComponent.name)(layoutComponents)
: layoutComponents[0];
const onComponentChosen = (c) => {
store.createDerivedComponent(c.name);
close();
}
screens = s.screens;
});
const save = () => {
saveAttempted = true;
const onGeneratorChosen = (g) => {
generator = g;
const isValid = name.length > 0
&& !screenNameExists(name)
&& layoutComponent;
if(!isValid) return;
store.createScreen(name, layoutComponent.name);
UIkit.modal(componentSelectorModal).hide();
UIkit.modal(generatorOptionsModal).show();
}
const onConfirmGenerate = (components) => {
store.createGeneratedComponents(components);
UIkit.modal(generatorOptionsModal).hide();
generator = null;
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-header">
<h1>New Component</h1>
<h1>New Screen</h1>
</div>
<div class="uk-modal-body">
<ComponentSelector onComponentChosen={onComponentChosen}
onGeneratorChosen={onGeneratorChosen}
allowGenerators={true} />
<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>
<div bind:this={generatorOptionsModal} uk-modal>
<div class="uk-modal-dialog" uk-overflow-auto>
{#if generator}
<div class="uk-modal-header">
<h1>Generator - {generator ? generator.name : ""}</h1>
</div>
<style>
h1 {
font-size:1.2em;
}
<div class="uk-modal-body">
<GeneratedComponents generator={generator}
onConfirmGenerate={onConfirmGenerate} />
</div>
.component-option {
border-style: solid;
border-color: var(--slate);
border-width: 0 0 1px 0;
padding: 3px;
}
{/if}
</div>
</div>
.component-option:hover {
background-color: var(--lightslate);
}
.component-name {
font-size: 11pt;
}
<style>
h1 {
font-size:1.2em;
.lib-name {
font-size: 9pt;
}
</style>

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

@ -20,7 +20,7 @@ store.subscribe(s => {
page = s.pages[s.currentPageName];
if(!page) return;
title = page.index.title;
components = pipe(s.allComponents, [
components = pipe(s.components, [
filter(s => !isRootComponent(s)),
concat([notSeletedComponent])
]);

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

@ -1,120 +0,0 @@
<script>
import IconButton from "../common/IconButton.svelte";
import {
createArrayElementProps
} from "./pagesParsing/createProps";
import PropControl from "./PropControl.svelte";
import {
some,
cloneDeep,
} from "lodash/fp";
import { validateProps } from "./pagesParsing/validateProps";
export let parentProps;
export let propDef;
export let onValueChanged;
export let onValidate = () => {};
export let onEditComponentProp = () => {};
let value = [];
let elementDefinitionArray;
let elementErrors = {};
$: {
const elArray = [];
for(let elProp in propDef.elementDefinition) {
if(elProp === "_component") continue;
elArray.push({
...propDef.elementDefinition[elProp],
____name: elProp
});
}
elementDefinitionArray = elArray;
value = parentProps[propDef.____name];
}
const addElement = () => {
const newElement = createArrayElementProps(
propDef.____name,
propDef.elementDefinition).props;
value = [...value, newElement];
onValueChanged(value);
}
const validate = (index, elementProps) => {
elementErrors[index] = validateProps(
propDef.elementDefinition, elementProps, [], true);
onValidate(elementErrors[index]);
return elementErrors[index].length === 0;
}
const setProp = (index) => (name, propValue) => {
const newValue = cloneDeep(value);
const newProps = cloneDeep(newValue[index]);
newProps[name] = propValue;
newValue[index] = newProps;
value = newValue;
if(validate(index, newProps))
onValueChanged(newValue);
}
let fieldHasError = index => propName =>
some(e => e.propName === propName)(elementErrors[index]);
const onEditComponent = (index, propName) => () => {
onEditComponentProp(index, propName);
}
</script>
<div class="root">
<div class="item-container">
{#each value as item, index}
<div class="item-inner-container">
{#each elementDefinitionArray as propDef}
<PropControl setProp={setProp(index)}
fieldHasError={fieldHasError(index)}
{propDef}
props={item}
{index}
onEditComponent={onEditComponent(index, propDef.____name)}
disabled={false} />
{/each}
</div>
{/each}
<div class="addelement-container"
on:click={addElement}>
<IconButton icon="plus"
size="12"/>
</div>
</div>
</div>
<style>
.addelement-container {
cursor: pointer;
padding: 3px 0px;
text-align: center;
}
.addelement-container:hover {
background-color: var(--primary25);
}
.item-container {
padding-left: 3px;
background: var(--secondary10);
}
</style>

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

@ -3,8 +3,6 @@
import Checkbox from "../common/Checkbox.svelte";
import Textbox from "../common/Textbox.svelte";
import Dropdown from "../common/Dropdown.svelte";
import ComponentPropSelector from "./ComponentPropSelector.svelte";
import PropArraySelector from "./PropArraySelector.svelte";
import EventListSelector from "./EventListSelector.svelte";
import StateBindingControl from "./StateBindingControl.svelte";
@ -15,7 +13,6 @@ export let propDef = {};
export let props = {};
export let disabled;
export let index;
export let onEditComponent = () => {};
$: isOdd = (index % 2 !== 0);
@ -28,32 +25,14 @@ const setComponentProp = (props) => {
<div class="root" >
{#if propDef.type === "component"}
<div class="prop-label">{propDef.____name}</div>
<ComponentPropSelector label={propDef.____name}
props={props[propDef.____name]}
{disabled}
onEdit={onEditComponent}
onComponentChosen={onEditComponent}
onValueChanged={setComponentProp}/>
{:else if propDef.type === "array"}
<div class="prop-label">{propDef.____name}</div>
<PropArraySelector parentProps={props}
{propDef}
onValueChanged={setComponentProp}
onEditComponentProp={onEditComponent} />
{:else if propDef.type === "event"}
{#if propDef.type === "event"}
<div class="prop-label">{propDef.____name}</div>
<EventListSelector parentProps={props}
{propDef}
onValueChanged={setComponentProp} />
{:else}
{:else }
<div class="prop-label">{propDef.____name}</div>
<StateBindingControl value={props[propDef.____name]}

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

@ -1,27 +1,16 @@
<script>
import {
keys,
map,
some,
includes,
cloneDeep,
isEqual,
sortBy,
filter,
difference
keys, map, some, includes,
cloneDeep, isEqual, sortBy,
filter, difference
} from "lodash/fp";
import { pipe } from "../common/core";
import {
getComponentInfo ,
getInstanceProps
} from "./pagesParsing/createProps";
import { getExactComponent } from "./pagesParsing/searchComponents";
import { getInstanceProps } from "./pagesParsing/createProps";
import Checkbox from "../common/Checkbox.svelte";
import Textbox from "../common/Textbox.svelte";
import Dropdown from "../common/Dropdown.svelte";
import { validateProps } from "./pagesParsing/validateProps";
import ComponentPropSelector from "./ComponentPropSelector.svelte";
import PropControl from "./PropControl.svelte";
import IconButton from "../common/IconButton.svelte";
@ -30,17 +19,12 @@ export let onValidate = () => {};
export let componentInfo;
export let instanceProps = null;
export let onPropsChanged = () => {};
export let onEditComponentProp = () => {};
let errors = [];
let props = {};
let propsDefinitions = [];
let inheritedPropsDefinitions = [];
let inheritedExpanded = false;
let isInstance = false;
const isPropInherited = name =>
includes(name)(componentInfo.inheritedProps);
$: {
if(componentInfo)
@ -52,14 +36,6 @@ $: {
propsDefinitions = pipe(componentInfo.propsDefinition, [
keys,
filter(k => !isPropInherited(k)),
map(k => ({...componentInfo.propsDefinition[k], ____name:k})),
sortBy("____name")
]);
inheritedPropsDefinitions = pipe(componentInfo.propsDefinition, [
keys,
filter(k => isPropInherited(k)),
map(k => ({...componentInfo.propsDefinition[k], ____name:k})),
sortBy("____name")
]);
@ -92,7 +68,7 @@ let setProp = (name, value) => {
}
const validate = (finalProps) => {
errors = validateProps(componentInfo.propsDefinition, finalProps, [], false);
errors = validateProps(componentInfo.rootComponent, finalProps, [], false);
onValidate(errors);
return errors.length === 0;
}
@ -100,49 +76,26 @@ const validate = (finalProps) => {
const fieldHasError = (propName) =>
some(e => e.propName === propName)(errors);
const onEditComponent = (propName) => (arrayIndex, arrayPropName) => {
onEditComponentProp(propName, arrayIndex, arrayPropName);
}
</script>
<div class="root">
<form class="uk-form-stacked">
<form class="uk-form-stacked form-root">
{#each propsDefinitions as propDef, index}
<PropControl {setProp}
{fieldHasError}
{propDef}
{props}
{index}
onEditComponent={onEditComponent(propDef.____name)}
disabled={false} />
{/each}
<div class="prop-container">
{#if inheritedPropsDefinitions.length > 0}
<div class="inherited-title padding">
<div>Inherited</div>
<div>
<IconButton icon={inheritedExpanded ? "chevron-down" : "chevron-right"}
on:click={() => inheritedExpanded = !inheritedExpanded}/>
</div>
</div>
{/if}
<PropControl {setProp}
{fieldHasError}
{propDef}
{props}
{index}
disabled={false} />
{#if inheritedExpanded}
{#each inheritedPropsDefinitions as propDef, index}
</div>
<PropControl {setProp}
{fieldHasError}
{propDef}
{props}
{index}
disabled={true} />
{/each}
{/if}
{/each}
</form>
@ -155,29 +108,17 @@ const onEditComponent = (propName) => (arrayIndex, arrayPropName) => {
.root {
font-size:10pt;
width: 100%;
}
.padding {
padding: 0 10px;
}
.inherited-title {
padding: 1rem 1rem 1rem 1rem;
display: grid;
grid-template-columns: [name] 1fr [actions] auto;
color: var(--secondary100);
font-size: .9rem;
font-weight: bold;
}
.inherited-title > div:nth-child(1) {
grid-column-start: name;
color: var(--secondary50);
.form-root {
display: flex;
flex-wrap: wrap;
}
.inherited-title > div:nth-child(2) {
grid-column-start: actions;
color: var(--secondary100);
.prop-container {
flex: 1 1 auto;
min-width: 250px;
}
</style>

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

@ -11,14 +11,14 @@ let addNewLib = "";
let addNewStylesheet = "";
let addComponentError = "";
let modalElement;
let allComponents;
let components;
store.subscribe(s => {
allComponents = s.allComponents;
components = s.components;
})
const removeLibrary = lib => {
const dependencies = libraryDependencies(allComponents, lib);
const dependencies = libraryDependencies(components, lib);
if(dependencies.length > 0) return;
store.removeComponentLibrary(lib);
}

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

@ -2,7 +2,6 @@
import ComponentsHierarchy from "./ComponentsHierarchy.svelte";
import PagesList from "./PagesList.svelte"
import EditComponent from "./EditComponent.svelte";
import { store } from "../builderStore";
import getIcon from "../common/icon";
import { isComponent } from "./pagesParsing/searchComponents";
@ -12,6 +11,7 @@ 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 = () => {
@ -32,7 +32,7 @@ const settings = () => {
<div class="components-list-container">
<div class="nav-group-header">
<div>{@html getIcon("sidebar","18")}</div>
<span class="components-nav-header">Components</span>
<span class="components-nav-header">Screens</span>
<div>
<IconButton icon="settings"
size="14px"
@ -42,7 +42,7 @@ const settings = () => {
</div>
</div>
<div class="nav-items-container">
<ComponentsHierarchy components={$store.derivedComponents}/>
<ComponentsHierarchy components={$store.screens}/>
</div>
</div>
@ -58,17 +58,17 @@ const settings = () => {
</div>
<div>
{#if $store.currentFrontEndType === "component"}
<div class="preview-pane">
{#if $store.currentFrontEndType === "screen"}
<CurrentItemPreview />
{:else if $store.currentFrontEndType === "page"}
<PageView />
{/if}
</div>
{#if $store.currentFrontEndType === "component"}
<div class="properties-pane">
<EditComponent />
{#if $store.currentFrontEndType === "screen"}
<div class="components-pane">
<ComponentsPaneSwitcher />
</div>
{/if}
@ -83,22 +83,25 @@ const settings = () => {
.root {
display: grid;
grid-template-columns: [uiNav] 250px [preview] auto [properties] 300px;
grid-template-columns: 250px 1fr 300px;
height: 100%;
width: 100%;
overflow-y: auto;
}
.ui-nav {
grid-column-start: uiNav;
grid-column: 1;
background-color: var(--secondary5);
height: 100%;
}
.properties-pane {
grid-column-start: properties;
.preview-pane {
grid-column: 2;
}
.components-pane {
grid-column: 3;
background-color: var(--secondary5);
height: 100%;
min-height: 0px;
overflow-y: hidden;
}

65
packages/builder/src/userInterface/pagesParsing/buildPropsHierarchy.js

@ -2,51 +2,47 @@ import {
getComponentInfo, createProps, getInstanceProps
} from "./createProps";
export const buildPropsHierarchy = (allComponents, baseComponent) => {
export const buildPropsHierarchy = (components, screens, baseComponent) => {
const buildProps = (componentName, propsDefinition, derivedFromProps) => {
const allComponents = [...components, ...screens];
const {props} = createProps(componentName, propsDefinition, derivedFromProps);
props._component = componentName;
const buildProps = (componentDefinition, derivedFromProps) => {
const {props} = createProps(componentDefinition, derivedFromProps);
const propsDefinition = componentDefinition.props;
props._component = componentDefinition.name;
for(let propName in props) {
if(propName === "_component") continue;
const propDef = propsDefinition[propName];
if(!propDef) continue;
if(propDef.type === "component") {
if(propName === "_children") {
const subComponentProps = props[propName];
const childrenProps = props[propName];
if(!subComponentProps._component) continue;
const propComponentInfo = getComponentInfo(
allComponents, subComponentProps._component);
const subComponentInstanceProps = getInstanceProps(
propComponentInfo,
subComponentProps
);
props[propName] = buildProps(
propComponentInfo.rootComponent.name,
propComponentInfo.propsDefinition,
subComponentInstanceProps);
if(!childrenProps
|| childrenProps.length === 0) {
continue;
}
} else if(propDef.type === "array") {
const propsArray = props[propName];
const newPropsArray = [];
let index = 0;
for(let element of propsArray) {
newPropsArray.push(
props[propName] = [];
for(let child of childrenProps) {
const propComponentInfo = getComponentInfo(
allComponents, child._component);
const subComponentInstanceProps = getInstanceProps(
propComponentInfo,
child
);
props[propName].push(
buildProps(
`${propName}#array_element#`,
propDef.elementDefinition,
element));
index++;
propComponentInfo.rootComponent.name,
propComponentInfo.propsDefinition,
subComponentInstanceProps));
}
props[propName] = newPropsArray;
}
}
}
return props;
@ -58,8 +54,7 @@ export const buildPropsHierarchy = (allComponents, baseComponent) => {
const baseComponentInfo = getComponentInfo(allComponents, baseComponent);
return buildProps(
baseComponentInfo.rootComponent.name,
baseComponentInfo.propsDefinition,
baseComponentInfo.rootComponent,
baseComponentInfo.fullProps);
}

166
packages/builder/src/userInterface/pagesParsing/createProps.js

@ -1,44 +1,12 @@
import {
isString,
isUndefined,
find,
keys,
uniq,
some,
filter,
reduce,
cloneDeep,
includes,
last
isString, isUndefined, find, keys, uniq,
some, filter, reduce, cloneDeep, includes,last
} from "lodash/fp";
import { types, expandPropsDefinition } from "./types";
import { types, expandComponentDefinition } from "./types";
import { assign } from "lodash";
import { pipe } from "../../common/core";
import { isRootComponent } from "./searchComponents";
export const createPropDefinitionForDerived = (allComponents, componentName) => {
const {propDef, derivedProps} = getComponentInfo(allComponents, componentName);
const hasDerivedProp = k => pipe(derivedProps, [
keys,
uniq,
some(key => key === k)
]);
return pipe(propDef, [
keys,
filter(k => !hasDerivedProp(k)),
reduce((obj, k) => {
obj[k] = propDef[k];
return obj;
}, {}),
expandPropsDefinition
])
}
export const traverseForProps = getComponentInfo;
import { ensureShardNameIsInShardMap } from "../../../../core/src/indexing/sharding";
export const getInstanceProps = (componentInfo, props) => {
const finalProps = cloneDeep(componentInfo.fullProps);
@ -50,89 +18,80 @@ export const getInstanceProps = (componentInfo, props) => {
return finalProps;
}
export const getNewComponentInfo = (allComponents, inherits) => {
const parentcomponent = find(c => c.name === inherits)(allComponents);
export const getNewComponentInfo = (components, rootComponent, name) => {
const component = {
name:"",
description:"",
inherits,
props:{},
tags:parentcomponent.tags
name: name || "",
description:"",
props:{
_component: rootComponent
}
};
return getComponentInfo(
allComponents,
inherits,
[component],
{});
components,
component);
}
export const getComponentInfo = (allComponents, comp, stack=[], subComponentProps=null) => {
const component = isString(comp)
? find(c => c.name === comp)(allComponents)
: comp;
const cname = isString(comp) ? comp : comp.name;
if(isRootComponent(component)) {
subComponentProps = subComponentProps||{};
const p = createProps(cname, component.props, subComponentProps);
const rootProps = createProps(cname, component.props);
const inheritedProps = [];
const targetComponent = stack.length > 0
? last(stack)
: component;
if(stack.length > 0) {
for(let prop in subComponentProps) {
const hasProp = pipe(targetComponent.props, [
keys,
includes(prop)]);
if(!hasProp)
inheritedProps.push(prop);
}
}
const unsetProps = pipe(p.props, [
keys,
filter(k => !includes(k)(keys(subComponentProps)) && k !== "_component")
]);
const fullProps = cloneDeep(p.props);
fullProps._component = targetComponent.name;
return ({
propsDefinition:expandPropsDefinition(component.props),
inheritedProps,
rootDefaultProps: rootProps.props,
unsetProps,
fullProps: fullProps,
errors: p.errors,
component: targetComponent,
rootComponent: component
});
}
export const getScreenInfo = (components, screen) => {
return getComponentInfo(
allComponents,
component.inherits,
[component, ...stack],
{...component.props, ...subComponentProps});
components,
screen);
}
export const createProps = (componentName, propsDefinition, derivedFromProps) => {
export const getComponentInfo = (components, comp) => {
const targetComponent = isString(comp)
? find(c => c.name === comp)(components)
: comp;
let component;
let subComponent;
if(isRootComponent(targetComponent)) {
component = targetComponent;
} else {
subComponent = targetComponent;
component = find(c => c.name === subComponent.props._component)(
components);
}
const subComponentProps = subComponent ? subComponent.props : {};
const p = createProps(component, subComponentProps);
const rootProps = createProps(component);
const unsetProps = pipe(p.props, [
keys,
filter(k => !includes(k)(keys(subComponentProps)) && k !== "_component")
]);
const fullProps = cloneDeep(p.props);
fullProps._component = targetComponent.name;
return ({
propsDefinition:expandComponentDefinition(component),
rootDefaultProps: rootProps.props,
unsetProps,
fullProps: fullProps,
errors: p.errors,
component: targetComponent,
rootComponent: component
});
}
export const createProps = (componentDefinition, derivedFromProps) => {
const error = (propName, error) =>
errors.push({propName, error});
const props = {
_component: componentName
_component: componentDefinition.name
};
const errors = [];
if(!componentName)
if(!componentDefinition.name)
error("_component", "Component name not supplied");
for(let propDef in propsDefinition) {
const parsedPropDef = parsePropDef(propsDefinition[propDef]);
const propsDef = componentDefinition.props;
for(let propDef in propsDef) {
const parsedPropDef = parsePropDef(propsDef[propDef]);
if(parsedPropDef.error)
error(propDef, parsedPropDef.error);
else
@ -143,15 +102,16 @@ export const createProps = (componentName, propsDefinition, derivedFromProps) =>
assign(props, derivedFromProps);
}
if(componentDefinition.children !== false
&& isUndefined(props._children)) {
props._children = [];
}
return ({
props, errors
});
}
export const createArrayElementProps = (arrayPropName, elementDefinition) =>
createProps(
`#${arrayPropName}#array_element`,
elementDefinition);
const parsePropDef = propDef => {
const error = message => ({error:message, propDef});

17
packages/builder/src/userInterface/pagesParsing/findDependencies.js

@ -5,7 +5,7 @@ import {
} from "lodash/fp";
import { isRootComponent } from "./searchComponents";
export const libraryDependencies = (allComponents, lib) => {
export const libraryDependencies = (components, lib) => {
const componentDependsOnLibrary = comp => {
if(isRootComponent(comp)) {
@ -13,21 +13,24 @@ export const libraryDependencies = (allComponents, lib) => {
return (libName === lib);
}
return componentDependsOnLibrary(
find(c => c.name === comp.inherits)(allComponents)
find(c => c.name === comp.props._component)(
components)
);
}
return filter(c => !isRootComponent(c)
&& componentDependsOnLibrary(c))(
allComponents
components
);
}
export const componentDependencies = (pages, allComponents, dependsOn) => {
export const componentDependencies = (pages, screens, components, dependsOn) => {
const allComponents = [
...cloneDeep(components),
...cloneDeep(screens)];
pages = cloneDeep(pages);
allComponents = cloneDeep(allComponents);
const dependantComponents = [];
const dependantPages = [];
@ -63,7 +66,7 @@ export const componentDependencies = (pages, allComponents, dependsOn) => {
continue;
}
if(component.inherits === dependsOn.name) {
if(component.props._component === dependsOn.name) {
dependantComponents.push(component);
continue;
}

6
packages/builder/src/userInterface/pagesParsing/getRootComponent.js

@ -1,10 +1,10 @@
import { isRootComponent } from "./searchComponents";
import { find } from "lodash/fp";
export const getRootComponent = (componentName, allComponents) => {
const component = find(c => c.name === componentName)(allComponents);
export const getRootComponent = (componentName, components) => {
const component = find(c => c.name === componentName)(components);
if(isRootComponent(component)) return component;
return getRootComponent(component.inherits, allComponents);
return getRootComponent(component.props._component, components);
}

34
packages/builder/src/userInterface/pagesParsing/renameComponent.js → packages/builder/src/userInterface/pagesParsing/renameScreen.js

@ -2,18 +2,18 @@ import {
isPlainObject, isArray, cloneDeep
} from "lodash/fp";
import {
isRootComponent, getExactComponent
getExactComponent
} from "./searchComponents";
export const rename = (pages, allComponents, oldname, newname) => {
export const rename = (pages, screens, oldname, newname) => {
pages = cloneDeep(pages);
allComponents = cloneDeep(allComponents);
const changedComponents = [];
screens = cloneDeep(screens);
const changedScreens = [];
const existingWithNewName = getExactComponent(allComponents, newname);
const existingWithNewName = getExactComponent(screens, newname);
if(existingWithNewName) return {
allComponents, pages, error: "Component by that name already exists"
components: screens, pages, error: "Component by that name already exists"
};
const traverseProps = (props) => {
@ -38,28 +38,24 @@ export const rename = (pages, allComponents, oldname, newname) => {
}
for(let component of allComponents) {
if(isRootComponent(component)) {
continue;
}
for(let screen of screens) {
let hasEdited = false;
if(component.name === oldname) {
component.name = newname;
if(screen.name === oldname) {
screen.name = newname;
hasEdited = true;
}
if(component.inherits === oldname) {
component.inherits = newname;
if(screen.props._component === oldname) {
screen.props._component = newname;
hasEdited = true;
}
hasEdited = traverseProps(component.props) || hasEdited;
hasEdited = traverseProps(screen.props) || hasEdited;
if(hasEdited && component.name !== newname)
changedComponents.push(component.name);
if(hasEdited && screen.name !== newname)
changedScreens.push(screen.name);
}
for(let pageName in pages) {
@ -69,7 +65,7 @@ export const rename = (pages, allComponents, oldname, newname) => {
}
}
return {allComponents, pages, changedComponents};
return {screens, pages, changedScreens};
}

23
packages/builder/src/userInterface/pagesParsing/searchComponents.js

@ -11,14 +11,15 @@ import {
const normalString = s => (s||"").trim().toLowerCase();
export const isRootComponent = c => isComponent(c) && isUndefined(c.inherits);
export const isRootComponent = c =>
isComponent(c) && isUndefined(c.props._component);
export const isComponent = c => {
const hasProp = (n) => !isUndefined(c[n]);
return hasProp("name") && hasProp("props");
}
export const searchAllComponents = (allComponents, phrase) => {
export const searchAllComponents = (components, phrase) => {
const hasPhrase = (...vals) =>
pipe(vals, [
@ -31,35 +32,35 @@ export const searchAllComponents = (allComponents, phrase) => {
if(isRootComponent(c)) return false;
const parent = getExactComponent(
allComponents,
c.inherits);
components,
c.props._component);
return componentMatches(parent);
}
return filter(componentMatches)(allComponents);
return filter(componentMatches)(components);
}
export const getExactComponent = (allComponents, name) => {
export const getExactComponent = (components, name) => {
const stringEquals = (s1, s2) =>
normalString(s1) === normalString(s2);
return pipe(allComponents,[
return pipe(components,[
find(c => stringEquals(c.name, name))
]);
}
export const getAncestorProps = (allComponents, name, found=[]) => {
export const getAncestorProps = (components, name, found=[]) => {
const thisComponent = getExactComponent(
allComponents, name);
components, name);
if(isRootComponent(thisComponent))
return [thisComponent.props, ...found];
return getAncestorProps(
allComponents,
thisComponent.inherits,
components,
thisComponent.props._component,
[{...thisComponent.props},
...found]);

29
packages/builder/src/userInterface/pagesParsing/types.js

@ -5,7 +5,8 @@ import {
isArray,
isObjectLike,
isPlainObject,
every
every,
isUndefined
} from "lodash/fp";
import {
@ -20,8 +21,7 @@ const defaultDef = typeName => () => ({
type: typeName,
required:false,
default:types[typeName].default(),
options: typeName === "options" ? [] : undefined,
elementDefinition: typeName === "array" ? {} : undefined
options: typeName === "options" ? [] : undefined
});
const propType = (defaultValue, isOfType, defaultDefinition) => ({
@ -42,18 +42,25 @@ const expandSingleProp = propDef => {
}
}
if(p.type === "array") {
p.elementDefinition = expandPropsDefinition(p.elementDefinition);
}
return p;
}
export const expandPropsDefinition = propsDefinition => {
export const expandComponentDefinition = componentDefinition => {
const expandedProps = {};
for(let p in propsDefinition) {
expandedProps[p] = expandSingleProp(propsDefinition[p]);
const expandedComponent = {...componentDefinition};
for(let p in componentDefinition.props) {
expandedProps[p] = expandSingleProp(
componentDefinition.props[p]);
}
expandedComponent.props = expandedProps;
if(expandedComponent.children !== false) {
expandedComponent.children = true;
}
return expandedProps;
return expandedComponent;
}
const isComponent = isObjectLike;
@ -75,9 +82,7 @@ export const types = {
string: propType(() => "", isString, defaultDef("string")),
bool: propType(() => false, isBoolean, defaultDef("bool")),
number: propType(() => 0, isNumber, defaultDef("number")),
array: propType(() => [], isArray, defaultDef("array")),
options: propType(() => "", isString, defaultDef("options")),
component: propType(() => ({_component:""}), isComponent, defaultDef("component")),
asset: propType(() => "", isString, defaultDef("asset")),
event: propType(() => [], isEventList, defaultDef("event")),
state: propType(() => emptyState(), isBound, defaultDef("state"))

20
packages/builder/src/userInterface/pagesParsing/validatePages.js

@ -14,9 +14,9 @@ export const validatePage = (page, getComponent) => {
const errors = [];
const error = message => errors.push(message);
const noIndex = !page.index || !page.index._component;
const noIndex = !page.index;
if(noIndex) {
error("Must choose a component for your index.html");
error("Page does not define an index member");
}
if(!page.appBody
@ -25,14 +25,18 @@ export const validatePage = (page, getComponent) => {
error("App body must be set toa valid JSON file");
}
/* Commenting this for now
* index is a load of static members just now, but maybe useful
for pageLayout props (which is just a pipe dream at time of writing)
const indexHtmlErrors = noIndex
? []
: pipe(
recursivelyValidate(page.index, getComponent), [
map(e => `Index.html: ${e.error}`)
]);
? []
: pipe(
recursivelyValidate(page.index, getComponent), [
map(e => `Index.html: ${e.error}`)
]);
*/
return [...errors, ...indexHtmlErrors];
return errors;
}
export const validatePages = (pages, getComponent) => {

133
packages/builder/src/userInterface/pagesParsing/validateProps.js

@ -23,14 +23,6 @@ const makeError = (errors, propName, stack) => (message) =>
export const recursivelyValidate = (rootProps, getComponent, stack=[]) => {
const getComponentPropsDefinition = componentName => {
if(componentName.includes(":")) {
const [parentComponent, arrayProp] = componentName.split(":");
return getComponent(parentComponent)[arrayProp].elementDefinition;
}
return getComponent(componentName);
}
if(!rootProps._component) {
const errs = [];
makeError(errs, "_component", stack)("Component is not set");
@ -38,79 +30,56 @@ export const recursivelyValidate = (rootProps, getComponent, stack=[]) => {
// this would break everything else anyway
}
const propsDef = getComponentPropsDefinition(
const componentDef = getComponent(
rootProps._component);
const getPropsDefArray = (def) => pipe(def, [
keys,
map(k => def[k].name
? expandPropDef(def[k])
: ({
...expandPropDef(def[k]),
name:k }))
]);
const propsDefArray = getPropsDefArray(propsDef);
const errors = validateProps(
propsDef,
componentDef,
rootProps,
stack,
true);
const validateChildren = (_defArray, _props, _stack) => pipe(_defArray, [
filter(d => d.type === "component"),
map(d => recursivelyValidate(
_props[d.name],
getComponentPropsDefinition,
[..._stack, d.name])),
flatten
]);
const validateChildren = (_props, _stack) =>
!_props._children
? []
: pipe(_props._children, [
map(child => recursivelyValidate(
child,
getComponent,
[..._stack, _props._children.indexOf(child)]))
]);
const childErrors = validateChildren(
propsDefArray, rootProps, stack);
const childArrayErrors = pipe(propsDefArray, [
filter(d => d.type === "array"),
map(d => pipe(rootProps[d.name], [
map(elementProps => pipe(d.elementDefinition, [
getPropsDefArray,
arr => validateChildren(
arr,
elementProps,
[...stack,
`${d.name}[${indexOf(elementProps)(rootProps[d.name])}]`])
]))
]))
]);
rootProps, stack);
return flattenDeep([errors, ...childErrors, ...childArrayErrors]);
return flattenDeep([errors, ...childErrors]);
}
const expandPropDef = propDef => {
const p = isString(propDef)
? types[propDef].defaultDefinition()
: propDef;
if(p.type === "array" && isString(p.elementDefinition)) {
p.elementDefinition = types[p.elementDefinition].defaultDefinition()
}
return p;
}
const expandPropDef = propDef =>
isString(propDef)
? types[propDef].defaultDefinition()
: propDef;
export const validateProps = (propsDefinition, props, stack=[], isFinal=true, isArrayElement=false) => {
export const validateProps = (componentDefinition, props, stack=[], isFinal=true) => {
const errors = [];
if(isFinal && !props._component && !isArrayElement) {
if(isFinal && !props._component) {
makeError(errors, "_component", stack)("Component is not set");
return errors;
// this would break everything else anyway
}
for(let propDefName in propsDefinition) {
const propsDefinition = componentDefinition.props;
for(let propDefName in props) {
if(propDefName === "_component") continue;
if(propDefName === "_children") continue;
if(propDefName === "_layout") continue;
const propDef = expandPropDef(propsDefinition[propDefName]);
@ -129,9 +98,7 @@ export const validateProps = (propsDefinition, props, stack=[], isFinal=true, is
}
if(isBinding(propValue)) {
if(propDef.type === "array"
|| propDef.type === "component"
|| propDef.type === "event") {
if(propDef.type === "event") {
error(`Cannot apply binding to type ${propDef.type}`);
continue;
}
@ -141,23 +108,7 @@ export const validateProps = (propsDefinition, props, stack=[], isFinal=true, is
continue;
}
if(propDef.type === "array") {
let index = 0;
for(let arrayItem of propValue) {
const arrayErrs = validateProps(
propDef.elementDefinition,
arrayItem,
[...stack, `${propDefName}[${index}]`],
isFinal,
true
)
for(let arrErr of arrayErrs) {
errors.push(arrErr);
}
index++;
}
}
if(propDef.type === "options"
&& propValue
&& !isBinding(propValue)
@ -170,33 +121,15 @@ export const validateProps = (propsDefinition, props, stack=[], isFinal=true, is
return errors;
}
export const validatePropsDefinition = (propsDefinition) => {
const { errors } = createProps("dummy_component_name", propsDefinition);
export const validateComponentDefinition = (componentDefinition) => {
const { errors } = createProps(componentDefinition);
const propDefinitions = expandPropDef(componentDefinition.props);
// arrar props without elementDefinition
pipe(propsDefinition, [
keys,
map(k => ({
propDef:propsDefinition[k],
propName:k
})),
filter(d => d.propDef.type === "array" && !d.propDef.elementDefinition),
each(d => makeError(errors, d.propName)(`${d.propName} does not have a definition for it's item props`))
]);
const arrayPropValidationErrors = pipe(propsDefinition, [
keys,
map(k => propsDefinition[k]),
filter(d => d.type === "array" && d.elementDefinition),
map(d => validatePropsDefinition(d.elementDefinition)),
flatten
]);
pipe(propsDefinition, [
pipe(propDefinitions, [
keys,
map(k => ({
propDef:propsDefinition[k],
propDef:propDefinitions[k],
propName:k
})),
filter(d => d.propDef.type === "options"
@ -204,7 +137,7 @@ export const validatePropsDefinition = (propsDefinition) => {
each(d => makeError(errors, d.propName)(`${d.propName} does not have any options`))
]);
return [...errors, ...arrayPropValidationErrors]
return errors;
}

32
packages/builder/tests/buildPropsHierarchy.spec.js

@ -1,4 +1,4 @@
import { allComponents } from "./testData";
import { componentsAndScreens } from "./testData";
import {
find
} from "lodash/fp";
@ -7,43 +7,25 @@ import { buildPropsHierarchy } from "../src/userInterface/pagesParsing/buildProp
describe("buildPropsHierarchy", () => {
it("should build a complex component with arrays and components", () => {
it("should build a complex component children", () => {
const components = allComponents();
const {components, screens} = componentsAndScreens();
const allprops = buildPropsHierarchy(
components, "ButtonGroup");
components, screens, "ButtonGroup");
expect(allprops._component).toEqual("budibase-components/div");
const primaryButtonProps = () => ({
_component: "budibase-components/Button",
css:"btn-primary",
content: {_component:""},
contentText: "",
size:""
_component: "budibase-components/Button"
});
const headerButton = primaryButtonProps();
expect(allprops.header).toEqual(headerButton);
const button1 = primaryButtonProps();
button1.contentText = "Button 1";
expect(allprops.children[0]).toEqual({
_component: "children#array_element#",
control: button1
});
expect(allprops._children[0]).toEqual(button1);
const button2 = primaryButtonProps();
button2.contentText = "Button 2";
expect(allprops.children[1]).toEqual({
_component: "children#array_element#",
control: button2
})
expect(allprops._children[1]).toEqual(button2)
});
});

32
packages/builder/tests/componentDependencies.spec.js

@ -1,12 +1,7 @@
import {
searchAllComponents,
getExactComponent,
getAncestorProps
} from "../src/userInterface/pagesParsing/searchComponents";
import {
componentDependencies
} from "../src/userInterface/pagesParsing/findDependencies";
import { allComponents } from "./testData";
import { componentsAndScreens } from "./testData";
import { some, find } from "lodash/fp"
describe("component dependencies", () => {
@ -19,37 +14,29 @@ describe("component dependencies", () => {
it("should include component that inheirts", () => {
const components = allComponents();
const {components, screens} = componentsAndScreens();
const result = componentDependencies(
{}, components, get(components, "budibase-components/TextBox"));
{}, screens, components,
get([...components, ...screens], "budibase-components/TextBox"));
expect(contains(result.dependantComponents, "common/SmallTextbox")).toBe(true);
});
it("should include component that nests", () => {
const components = allComponents();
const {components, screens} = componentsAndScreens();
const result = componentDependencies(
{}, components, get(components, "PrimaryButton"));
{}, screens, components,
get([...components, ...screens], "budibase-components/Button"));
expect(contains(result.dependantComponents, "ButtonGroup")).toBe(true);
});
it("shouldinclude component that nests inside arrays", () => {
const components = allComponents();
const result = componentDependencies(
{}, components, get(components, "common/PasswordBox"));
expect(contains(result.dependantComponents, "ButtonGroup")).toBe(true);
});
it("should include components n page apbody", () => {
const components = allComponents();
const {components, screens} = componentsAndScreens();
const pages = {
main: {
appBody: "PrimaryButton"
@ -57,7 +44,8 @@ describe("component dependencies", () => {
};
const result = componentDependencies(
pages, components, get(components, "PrimaryButton"));
pages, screens, components,
get([...components, ...screens], "PrimaryButton"));
expect(result.dependantPages).toEqual(["main"]);
});

165
packages/builder/tests/createDefaultProps.spec.js

@ -10,46 +10,45 @@ import {
describe("createDefaultProps", () => {
it("should create a object with single string value, when default string field set", () => {
const propDef = {
const getcomponent = () => ({
name:"some_component",
props: {
fieldName: {type:"string", default:"something"}
};
}
});
const { props, errors } = createProps("some_component",propDef);
it("should create a object with single string value, when default string field set", () => {
const { props, errors } = createProps(getcomponent());
expect(errors).toEqual([]);
expect(props.fieldName).toBeDefined();
expect(props.fieldName).toBe("something");
expect(keys(props).length).toBe(2);
expect(keys(props).length).toBe(3);
});
it("should set component name", () => {
const propDef = {
fieldName: {type:"string", default:"something"}
};
const { props, errors } = createProps("some_component",propDef);
const { props, errors } = createProps(getcomponent());
expect(errors).toEqual([]);
expect(props._component).toBe("some_component");
});
it("should return error when component name not supplied", () => {
const propDef = {
fieldName: {type:"string", default:"something"}
};
const comp = getcomponent();
comp.name = "";
const { errors } = createProps("",propDef);
const { errors } = createProps(comp);
expect(errors.length).toEqual(1);
});
it("should create a object with single blank string value, when no default", () => {
const propDef = {
fieldName: {type:"string"}
};
const comp = getcomponent();
comp.props.fieldName = {type:"string"};
const { props, errors } = createProps("some_component",propDef);
const { props, errors } = createProps(comp);
expect(errors).toEqual([]);
expect(props.fieldName).toBeDefined();
@ -57,11 +56,10 @@ describe("createDefaultProps", () => {
});
it("should create a object with single blank string value, when prop definition is 'string' ", () => {
const propDef = {
fieldName: "string"
};
const comp = getcomponent();
comp.props.fieldName = "string";
const { props, errors } = createProps("some_component",propDef);
const { props, errors } = createProps(comp);
expect(errors).toEqual([]);
expect(props.fieldName).toBeDefined();
@ -69,11 +67,10 @@ describe("createDefaultProps", () => {
});
it("should create a object with single fals value, when prop definition is 'bool' ", () => {
const propDef = {
isVisible: "bool"
};
const comp = getcomponent();
comp.props.isVisible = "bool";
const { props, errors } = createProps("some_component",propDef);
const { props, errors } = createProps(comp);
expect(errors).toEqual([]);
expect(props.isVisible).toBeDefined();
@ -81,35 +78,44 @@ describe("createDefaultProps", () => {
});
it("should create a object with single 0 value, when prop definition is 'number' ", () => {
const propDef = {
width: "number"
};
const comp = getcomponent();
comp.props.width = "number";
const { props, errors } = createProps("some_component",propDef);
const { props, errors } = createProps(comp);
expect(errors).toEqual([]);
expect(props.width).toBeDefined();
expect(props.width).toBe(0);
});
it("should create a object with single empty array, when prop definition is 'array' ", () => {
const propDef = {
columns: "array"
};
it("should create a object with empty _children array, when children===true ", () => {
const comp = getcomponent();
comp.children = true;
const { props, errors } = createProps("some_component",propDef);
const { props, errors } = createProps(comp);
expect(errors).toEqual([]);
expect(props.columns).toBeDefined();
expect(props.columns).toEqual([]);
expect(props._children).toBeDefined();
expect(props._children).toEqual([]);
});
it("should create a object without _children array, when children===false ", () => {
const comp = getcomponent();
comp.children = false;
const { props, errors } = createProps(comp);
expect(errors).toEqual([]);
expect(props._children).not.toBeDefined();
});
it("should create a object with single empty array, when prop definition is 'event' ", () => {
const propDef = {
onClick: "event"
};
const { props, errors } = createProps("some_component",propDef);
const comp = getcomponent();
comp.props.onClick = "event";
const { props, errors } = createProps(comp);
expect(errors).toEqual([]);
expect(props.onClick).toBeDefined();
@ -117,52 +123,72 @@ describe("createDefaultProps", () => {
});
it("should create a object with empty state when prop def is 'state' ", () => {
const propDef = {
data: "state"
};
const { props, errors } = createProps("some_component",propDef);
const comp = getcomponent();
comp.props.data = "state";
const { props, errors } = createProps(comp);
expect(errors).toEqual([]);
expect(props.data[BB_STATE_BINDINGPATH]).toBeDefined();
expect(props.data[BB_STATE_BINDINGPATH]).toBe("");
});
it("should create a object with single empty component props, when prop definition is 'component' ", () => {
const propDef = {
content: "component"
};
it("should create a object children array when children == true ", () => {
const comp = getcomponent();
comp.children = true;
const { props, errors } = createProps("some_component",propDef);
const { props, errors } = createProps(comp);
expect(errors).toEqual([]);
expect(props.content).toBeDefined();
expect(props.content).toEqual({_component:""});
expect(props._children).toBeDefined();
expect(props._children).toEqual([]);
});
it("should create a _children array when children not defined ", () => {
const comp = getcomponent();
const { props, errors } = createProps(comp);
expect(errors).toEqual([]);
expect(props._children).toBeDefined();
expect(props._children).toEqual([]);
});
it("should not create _children array when children=false ", () => {
const comp = getcomponent();
comp.children = false;
const { props, errors } = createProps(comp);
expect(errors).toEqual([]);
expect(props._children).not.toBeDefined();
});
it("should create an object with multiple prop names", () => {
const propDef = {
fieldName: "string",
fieldLength: { type: "number", default: 500 }
};
const comp = getcomponent();
comp.props.fieldName = "string";
comp.props.fieldLength = { type: "number", default: 500 };
const { props, errors } = createProps("some_component",propDef);
const { props, errors } = createProps(comp);
expect(errors).toEqual([]);
expect(props.fieldName).toBeDefined();
expect(props.fieldName).toBe("");
expect(props.fieldLength).toBeDefined();
expect(props.fieldLength).toBe(500);
expect(keys(props).length).toBe(3);
})
it("should return error when invalid type", () => {
const propDef = {
fieldName: "invalid type name",
fieldLength: { type: "invalid type name "}
};
const comp = getcomponent();
comp.props.fieldName = "invalid type name";
comp.props.fieldLength = { type: "invalid type name "};
const { errors } = createProps("some_component",propDef);
const { errors } = createProps(comp);
expect(errors.length).toBe(2);
expect(some(e => e.propName === "fieldName")(errors)).toBeTruthy();
@ -170,11 +196,12 @@ describe("createDefaultProps", () => {
});
it("should return error default value is not of declared type", () => {
const propDef = {
fieldName: {type:"string", default: 1}
};
const { errors } = createProps("some_component",propDef);
const comp = getcomponent();
comp.props.fieldName = {type:"string", default: 1}
const { errors } = createProps(comp);
expect(errors.length).toBe(1);
expect(some(e => e.propName === "fieldName")(errors)).toBeTruthy();
@ -186,11 +213,15 @@ describe("createDefaultProps", () => {
fieldLength: { type: "number", default: 500}
};
const comp = getcomponent();
comp.props.fieldName = "string";
comp.props.fieldLength = { type: "number", default: 500};
const derivedFrom = {
fieldName: "surname"
};
const { props, errors } = createProps("some_component",propDef, derivedFrom);
const { props, errors } = createProps(comp, derivedFrom);
expect(errors.length).toBe(0);
expect(props.fieldName).toBe("surname");

56
packages/builder/tests/expandPropDef.spec.js

@ -1,44 +1,50 @@
import { expandPropsDefinition } from "../src/userInterface/pagesParsing/types";
const propDef = {
label: "string",
width: {type:"number"},
color: {type:"string", required:true},
child: "component",
navitems: {
type: "array",
elementDefinition: {
name: {type:"string"},
height: "number"
}
import { expandComponentDefinition } from "../src/userInterface/pagesParsing/types";
const componentDef = () => ({
name: "comp",
props: {
label: "string",
width: {type:"number"},
color: {type:"string", required:true},
}
}
})
describe("expandPropDefintion", () => {
it("should expand property defined as string, into default for that type", () => {
const result = expandPropsDefinition(propDef);
const result = expandComponentDefinition(componentDef());
expect(result.label.type).toBe("string");
expect(result.label.required).toBe(false);
expect(result.props.label.type).toBe("string");
expect(result.props.label.required).toBe(false);
});
it("should add members to property defined as object, when members do not exist", () => {
const result = expandPropsDefinition(propDef);
expect(result.width.required).toBe(false);
const result = expandComponentDefinition(componentDef());
expect(result.props.width.required).toBe(false);
});
it("should not override existing memebers", () => {
const result = expandPropsDefinition(propDef);
expect(result.color.required).toBe(true);
const result = expandComponentDefinition(componentDef());
expect(result.props.color.required).toBe(true);
});
it("should also expand out elementdefinition of array", () => {
const result = expandPropsDefinition(propDef);
expect(result.navitems.elementDefinition.height.type).toBe("number");
})
it("should set children=true when not included", () => {
const result = expandComponentDefinition(componentDef());
expect(result.children).toBe(true);
});
it("should not change children when specified", () => {
const c = componentDef();
c.children = false;
const result = expandComponentDefinition(c);
expect(result.children).toBe(false);
c.children = true;
const result2 = expandComponentDefinition(c);
expect(result2.children).toBe(true);
});
})

57
packages/builder/tests/getComponentInfo.spec.js

@ -1,11 +1,12 @@
import {
getInstanceProps,
getComponentInfo
getScreenInfo ,
getComponentInfo
} from "../src/userInterface/pagesParsing/createProps";
import {
keys, some
keys, some, find
} from "lodash/fp";
import { allComponents } from "./testData";
import { componentsAndScreens } from "./testData";
@ -13,7 +14,7 @@ describe("getComponentInfo", () => {
it("should return default props for root component", () => {
const result = getComponentInfo(
allComponents(),
componentsAndScreens().components,
"budibase-components/TextBox");
expect(result.errors).toEqual([]);
@ -26,19 +27,10 @@ describe("getComponentInfo", () => {
});
});
it("should return no inherited for root component", () => {
const result = getComponentInfo(
allComponents(),
"budibase-components/TextBox");
expect(result.inheritedProps).toEqual([]);
});
it("getInstanceProps should set supplied props on top of default props", () => {
const result = getInstanceProps(
getComponentInfo(
allComponents(),
componentsAndScreens().components,
"budibase-components/TextBox"),
{size:"small"});
@ -51,11 +43,18 @@ describe("getComponentInfo", () => {
});
});
});
it("should return correct props for derived component", () => {
const result = getComponentInfo(
allComponents(),
"common/SmallTextbox");
describe("getScreenInfo", () => {
const getScreen = (screens, name) =>
find(s => s.name === name)(screens);
it("should return correct props for screen", () => {
const {components, screens} = componentsAndScreens();
const result = getScreenInfo(
components,
getScreen(screens, "common/SmallTextbox"));
expect(result.errors).toEqual([]);
expect(result.fullProps).toEqual({
@ -68,9 +67,10 @@ describe("getComponentInfo", () => {
});
it("should return correct props for twice derived component", () => {
const result = getComponentInfo(
allComponents(),
"common/PasswordBox");
const {components, screens} = componentsAndScreens();
const result = getScreenInfo(
components,
getScreen(screens, "common/PasswordBox"));
expect(result.errors).toEqual([]);
expect(result.fullProps).toEqual({
@ -82,19 +82,12 @@ describe("getComponentInfo", () => {
});
});
it("should list inheirted props as those that are defined in ancestor, derived components", () => {
const result = getComponentInfo(
allComponents(),
"common/PasswordBox");
// size is inherited from SmallTextbox
expect(result.inheritedProps).toEqual(["size"]);
});
it("should list unset props as those that are only defined in root", () => {
const result = getComponentInfo(
allComponents(),
"common/PasswordBox");
const {components, screens} = componentsAndScreens();
const result = getScreenInfo(
components,
getScreen(screens, "common/PasswordBox"));
expect(result.unsetProps).toEqual([
"placeholder", "label"]);

79
packages/builder/tests/renameComponent.spec.js

@ -1,79 +0,0 @@
import {
searchAllComponents,
getExactComponent,
getAncestorProps
} from "../src/userInterface/pagesParsing/searchComponents";
import {
rename
} from "../src/userInterface/pagesParsing/renameComponent";
import { allComponents } from "./testData";
describe("rename component", () => {
it("should change the name of the component, duh", () => {
const components = allComponents();
const result = rename({}, components, "PrimaryButton", "MainButton");
const newComponent = getExactComponent(result.allComponents, "MainButton");
const oldComponent = getExactComponent(result.allComponents, "Primary");
expect(oldComponent).toBeUndefined();
expect(newComponent).toBeDefined();
expect(newComponent.name).toBe("MainButton");
});
it("should chnge name on inherits", () => {
const components = allComponents();
const result = rename({}, components, "common/SmallTextbox", "common/TinyTextbox");
const passwordTextbox = getExactComponent(result.allComponents, "common/PasswordBox");
expect(passwordTextbox.inherits).toBe("common/TinyTextbox");
});
it("should change name of nested _components", () => {
const components = allComponents();
const result = rename({}, components, "PrimaryButton", "MainButton");
const buttonGroup = getExactComponent(result.allComponents, "ButtonGroup");
expect(buttonGroup.props.header._component).toBe("MainButton");
});
it("should change name of nested _components inside arrays", () => {
const components = allComponents();
const result = rename({}, components, "PrimaryButton", "MainButton");
const buttonGroup = getExactComponent(result.allComponents, "ButtonGroup");
expect(buttonGroup.props.children[0].control._component).toBe("MainButton");
});
it("should change name of page appBody", () => {
const components = allComponents();
const pages = {
main: {
appBody: "PrimaryButton"
}
};
const result = rename(pages, components, "PrimaryButton", "MainButton");
expect(result.pages.main.appBody).toBe("MainButton");
});
it("should return a list of changed components", () => {
const components = allComponents();
const result = rename({}, components, "PrimaryButton", "MainButton");
expect(result.changedComponents).toEqual(["ButtonGroup"]);
const result2 = rename({}, components, "common/SmallTextbox", "common/TinyTextBox");
expect(result2.changedComponents).toEqual(["common/PasswordBox"]);
});
})

61
packages/builder/tests/renameScreen.spec.js

@ -0,0 +1,61 @@
import {
getExactComponent
} from "../src/userInterface/pagesParsing/searchComponents";
import {
rename
} from "../src/userInterface/pagesParsing/renameScreen";
import { componentsAndScreens } from "./testData";
describe("rename component", () => {
it("should change the name of the component, duh", () => {
const {screens} = componentsAndScreens();
const result = rename({}, screens, "PrimaryButton", "MainButton");
const newComponent = getExactComponent(result.screens, "MainButton");
const oldComponent = getExactComponent(result.screens, "Primary");
expect(oldComponent).toBeUndefined();
expect(newComponent).toBeDefined();
expect(newComponent.name).toBe("MainButton");
});
/* this may be usefull if we have user defined components
it("should change name of nested _components", () => {
const {screens} = componentsAndScreens();
const result = rename({}, screens, "PrimaryButton", "MainButton");
const buttonGroup = getExactComponent(result.screens, "ButtonGroup");
expect(buttonGroup.props.header[0]._component).toBe("MainButton");
});
*/
it("should change name of page appBody", () => {
const {screens} = componentsAndScreens();
const pages = {
main: {
appBody: "PrimaryButton"
}
};
const result = rename(pages, screens, "PrimaryButton", "MainButton");
expect(result.pages.main.appBody).toBe("MainButton");
});
/* this may be usefull if we have user defined components
it("should return a list of changed components", () => {
const {screens} = componentsAndScreens();
const result = rename({}, screens, "PrimaryButton", "MainButton");
expect(result.changedScreens).toEqual(["ButtonGroup"]);
const result2 = rename({}, screens, "common/SmallTextbox", "common/TinyTextBox");
expect(result2.changedScreens).toEqual(["Field"]);
});
*/
})

51
packages/builder/tests/searchComponentsProps.spec.js

@ -3,42 +3,31 @@ import {
getExactComponent,
getAncestorProps
} from "../src/userInterface/pagesParsing/searchComponents";
import { allComponents } from "./testData";
import { componentsAndScreens } from "./testData";
describe("searchAllComponents", () => {
it("should match derived component by name", () => {
it("should match component by name", () => {
const results = searchAllComponents(
allComponents(),
"password"
componentsAndScreens().components,
"Textbox"
);
expect(results.length).toBe(1);
expect(results[0].name).toBe("common/PasswordBox");
expect(results[0].name).toBe("budibase-components/TextBox");
});
it("should match derived component by tag", () => {
it("should match component by tag", () => {
const results = searchAllComponents(
allComponents(),
"mask"
componentsAndScreens().components,
"record"
);
expect(results.length).toBe(1);
expect(results[0].name).toBe("common/PasswordBox");
});
it("should match component if ancestor matches", () => {
const results = searchAllComponents(
allComponents(),
"smalltext"
);
expect(results.length).toBe(2);
expect(results[0].name).toBe("budibase-components/RecordView");
});
@ -46,8 +35,9 @@ describe("searchAllComponents", () => {
describe("getExactComponent", () => {
it("should get component by name", () => {
const {components, screens} = componentsAndScreens();
const result = getExactComponent(
allComponents(),
[...components, ...screens],
"common/SmallTextbox"
)
@ -56,8 +46,9 @@ describe("getExactComponent", () => {
});
it("should return nothing when no result (should not fail)", () => {
const {components, screens} = componentsAndScreens();
const result = getExactComponent(
allComponents(),
[...components, ...screens],
"bla/bla/bla"
)
@ -71,29 +62,29 @@ describe("getAncestorProps", () => {
it("should return props of root component", () => {
const result = getAncestorProps(
allComponents(),
componentsAndScreens().components,
"budibase-components/TextBox"
);
expect(result).toEqual([
allComponents()[0].props
componentsAndScreens().components[0].props
]);
});
it("should return props of all ancestors and current component, in order", () => {
it("should return props of inherited and current component, in order", () => {
const components = allComponents();
const {components, screens} = componentsAndScreens();
const allComponentsAndScreens = [...components, ...screens];
const result = getAncestorProps(
components,
allComponentsAndScreens,
"common/PasswordBox"
);
expect(result).toEqual([
components[0].props,
{...components[4].props},
{...components[5].props}
allComponentsAndScreens[0].props,
{...allComponentsAndScreens[5].props}
]);
});

184
packages/builder/tests/testData.js

@ -1,98 +1,102 @@
export const allComponents = () => ([
{
name: "budibase-components/TextBox",
tags: ["Text", "input"],
props: {
size: {type:"options", options:["small", "medium", "large"]},
isPassword: "bool",
placeholder: "string",
label:"string"
}
},
{
name: "budibase-components/Button",
tags: ["input"],
props: {
size: {type:"options", options:["small", "medium", "large"]},
css: "string",
content: "component",
contentText: "string"
}
},
{
name: "budibase-components/div",
tags: ["input"],
props: {
width: "number",
header : "component",
children: {
type:"array",
elementDefinition: {
control: "component"
}
export const componentsAndScreens = () => ({
components: [
{
name: "budibase-components/TextBox",
tags: ["Text", "input"],
children: false,
props: {
size: {type:"options", options:["small", "medium", "large"]},
isPassword: "bool",
placeholder: "string",
label:"string"
}
},
{
name: "budibase-components/Button",
tags: ["input"],
children: true,
props: {
size: {type:"options", options:["small", "medium", "large"]},
css: "string",
contentText: "string"
}
},
{
name: "budibase-components/div",
tags: ["input"],
props: {
width: "number",
}
},
{
name:"budibase-components/RecordView",
tags: ["record"],
props: {
data: "state"
}
}
},
{
name:"budibase-components/RecordView",
tags: ["record"],
props: {
data: "state"
}
},
{
inherits:"budibase-components/TextBox",
name: "common/SmallTextbox",
props: {
size: "small"
}
},
{
inherits:"common/SmallTextbox",
name: "common/PasswordBox",
tags: ["mask"],
props: {
isPassword: true
}
},
{
inherits:"budibase-components/Button",
name:"PrimaryButton",
props: {
css:"btn-primary"
}
},
{
inherits:"budibase-components/div",
name:"ButtonGroup",
props: {
],
screens: [
{
name: "common/SmallTextbox",
props: {
_component: "budibase-components/TextBox",
size: "small"
}
},
{
name: "common/PasswordBox",
tags: ["mask"],
props: {
_component: "budibase-components/TextBox",
isPassword: true,
size: "small"
}
},
width: 100,
header: {
_component: "PrimaryButton"
},
children: [
{
control: {
_component: "PrimaryButton",
{
name:"PrimaryButton",
props: {
_component:"budibase-components/Button",
css:"btn-primary"
}
},
{
name:"ButtonGroup",
props: {
_component:"budibase-components/div",
width: 100,
_children: [
{
_component: "budibase-components/Button",
contentText: "Button 1"
}
},
{
control: {
_component: "PrimaryButton",
},
{
_component: "budibase-components/Button",
contentText: "Button 2"
},
{
_component: "budibase-components/TextBox",
isPassword: true,
size: "small"
}
},
{
control: {
_component: "common/PasswordBox",
]
}
},
{
name:"Field",
props: {
_component:"budibase-components/div",
_children:[
{
_component: "common/SmallTextbox"
}
}
]
}
}
])
]
}
},
]
});

26
packages/builder/tests/validatePages.spec.js

@ -6,21 +6,13 @@ import {
const validPages = () => ({
"main" : {
"index" : {
"_component": "testIndexHtml",
"title": "My Cool App",
"customScripts": [
{"url": "MyCustomComponents.js"}
]
"title": "My Cool App"
},
"appBody" : "./main.app.json"
},
"unauthenticated" : {
"index" : {
"_component": "testIndexHtml",
"title": "My Cool App - Login",
"customScripts": [
{"url": "MyCustomComponents.js"}
]
"title": "My Cool App - Login"
},
"appBody" : "./unauthenticated.app.json"
},
@ -29,12 +21,9 @@ const validPages = () => ({
const getComponent = name => ({
testIndexHtml : {
title: "string",
customScripts: {
type:"array",
elementDefinition: {
url: "string"
}
name: "testIndexHtml",
props: {
title: "string",
}
}
}[name])
@ -53,11 +42,6 @@ describe("validate single page", () => {
let page = validPages().main;
delete page.index;
expect(validatePage(page, getComponent).length).toEqual(1);
page.index = {title:"something"}; // no _component
const noComponent = validatePage(page, getComponent);
expect(noComponent.length).toEqual(1);
});
it("should return error when appBody is not set, or set incorrectly", () => {

244
packages/builder/tests/validateProps.spec.js

@ -1,5 +1,5 @@
import {
validatePropsDefinition,
validateComponentDefinition,
validateProps,
recursivelyValidate
} from "../src/userInterface/pagesParsing/validateProps";
@ -11,61 +11,22 @@ import {
// not that allot of this functionality is covered
// in createDefaultProps - as validate props uses that.
describe("validatePropsDefinition", () => {
describe("validateComponentDefinition", () => {
it("should recursively validate array props and return no errors when valid", () => {
const propsDef = {
columns : {
type: "array",
elementDefinition: {
width: "number",
units: {
type: "string",
default: "px"
}
}
}
}
const errors = validatePropsDefinition(propsDef);
expect(errors).toEqual([]);
});
it("should recursively validate array props and return errors when invalid", () => {
const propsDef = {
columns : {
type: "array",
elementDefinition: {
width: "invlid type",
units: {
type: "string",
default: "px"
}
}
}
}
const errors = validatePropsDefinition(propsDef);
expect(errors.length).toEqual(1);
expect(errors[0].propName).toBe("width");
});
it("should return error when no options for options field", () => {
const propsDef = {
size: {
type: "options",
options: []
const compDef = {
name:"some_component",
props: {
size: {
type: "options",
options: []
}
}
}
};
const errors = validatePropsDefinition(propsDef);
const errors = validateComponentDefinition(compDef);
expect(errors.length).toEqual(1);
expect(errors[0].propName).toBe("size");
@ -74,14 +35,17 @@ describe("validatePropsDefinition", () => {
it("should not return error when options field has options", () => {
const propsDef = {
size: {
type: "options",
options: ["small", "medium", "large"]
const compDef = {
name: "some_component",
props: {
size: {
type: "options",
options: ["small", "medium", "large"]
}
}
}
};
const errors = validatePropsDefinition(propsDef);
const errors = validateComponentDefinition(compDef);
expect(errors).toEqual([]);
@ -89,30 +53,34 @@ describe("validatePropsDefinition", () => {
});
const validPropDef = {
size: {
type: "options",
options: ["small", "medium", "large"],
default:"medium"
},
rowCount : "number",
columns : {
type: "array",
elementDefinition: {
width: "number",
units: {
type: "string",
default: "px"
}
}
const validComponentDef = {
name: "some_component",
props: {
size: {
type: "options",
options: ["small", "medium", "large"],
default:"medium"
},
rowCount : "number"
}
};
const childComponentDef = {
name: "child_component",
props: {
width: "number",
units: {
type: "string",
default: "px"
}
}
};
const validProps = () => {
const { props } = createProps("some_component", validPropDef);
props.columns.push(
createProps("childcomponent", validPropDef.columns.elementDefinition).props);
const { props } = createProps(validComponentDef);
props._children.push(
createProps(childComponentDef));
return props;
}
@ -120,7 +88,7 @@ describe("validateProps", () => {
it("should have no errors with a big list of valid props", () => {
const errors = validateProps(validPropDef, validProps(), [], true);
const errors = validateProps(validComponentDef, validProps(), [], true);
expect(errors).toEqual([]);
});
@ -129,7 +97,7 @@ describe("validateProps", () => {
const props = validProps();
props.rowCount = "1";
const errors = validateProps(validPropDef, props, [], true);
const errors = validateProps(validComponentDef, props, [], true);
expect(errors.length).toEqual(1);
expect(errors[0].propName).toBe("rowCount");
@ -139,92 +107,92 @@ describe("validateProps", () => {
const props = validProps();
props.size = "really_small";
const errors = validateProps(validPropDef, props, [], true);
const errors = validateProps(validComponentDef, props, [], true);
expect(errors.length).toEqual(1);
expect(errors[0].propName).toBe("size");
});
it("should return error with invalid array item", () => {
const props = validProps();
props.columns[0].width = "seven";
const errors = validateProps(validPropDef, props, [], true);
expect(errors.length).toEqual(1);
expect(errors[0].propName).toBe("width");
});
it("should not return error when has binding", () => {
const props = validProps();
props.columns[0].width = setBinding({path:"some_path"});
props._children[0].width = setBinding({path:"some_path"});
props.size = setBinding({path:"other path", fallback:"small"});
const errors = validateProps(validPropDef, props, [], true);
const errors = validateProps(validComponentDef, props, [], true);
expect(errors.length).toEqual(0);
});
});
describe("recursivelyValidateProps", () => {
const rootComponent = {
width: "number",
child: "component",
navitems: {
type: "array",
elementDefinition: {
name: "string",
icon: "component"
}
const rootComponent = {
name: "rootComponent",
children: true,
props: {
width: "number"
}
};
const todoListComponent = {
showTitle: "bool",
header: "component"
const todoListComponent = {
name: "todoListComponent",
props:{
showTitle: "bool"
}
};
const headerComponent = {
text: "string"
}
name: "headerComponent",
props: {
text: "string"
}
};
const iconComponent = {
iconName: "string"
}
name: "iconComponent",
props: {
iconName: "string"
}
};
const navItemComponent = {
name: "navItemComponent",
props: {
text: "string"
}
};
const getComponent = name => ({
rootComponent,
todoListComponent,
headerComponent,
iconComponent
iconComponent,
navItemComponent
})[name];
const rootProps = () => ({
_component: "rootComponent",
width: 100,
child: {
_children: [{
_component: "todoListComponent",
showTitle: true,
header: {
_component: "headerComponent",
text: "Your todo list"
}
},
navitems: [
{
name: "Main",
icon: {
_children : [
{
_component: "navItemComponent",
text: "todos"
},
{
_component: "headerComponent",
text: "Your todo list"
},
{
_component: "iconComponent",
iconName:"fa fa-list"
}
},
{
name: "Settings",
icon: {
iconName: "fa fa-list"
},
{
_component: "iconComponent",
iconName:"fa fa-cog"
}
}
]
]
}]
});
it("should return no errors for valid structure", () => {
@ -245,38 +213,20 @@ describe("recursivelyValidateProps", () => {
it("should return error on first nested child component", () => {
const root = rootProps();
root.child.showTitle = "yeeeoooo";
root._children[0].showTitle = "yeeeoooo";
const result = recursivelyValidate(root, getComponent);
expect(result.length).toBe(1);
expect(result[0].stack).toEqual(["child"]);
expect(result[0].stack).toEqual([0]);
expect(result[0].propName).toBe("showTitle");
});
it("should return error on second nested child component", () => {
const root = rootProps();
root.child.header.text = false;
root._children[0]._children[0].text = false;
const result = recursivelyValidate(root, getComponent);
expect(result.length).toBe(1);
expect(result[0].stack).toEqual(["child", "header"]);
expect(result[0].stack).toEqual([0,0]);
expect(result[0].propName).toBe("text");
});
it("should return error on invalid array prop", () => {
const root = rootProps();
root.navitems[1].name = false;
const result = recursivelyValidate(root, getComponent);
expect(result.length).toBe(1);
expect(result[0].propName).toBe("name");
expect(result[0].stack).toEqual(["navitems[1]"]);
});
it("should return error on invalid array child", () => {
const root = rootProps();
root.navitems[1].icon.iconName = false;
const result = recursivelyValidate(root, getComponent);
expect(result.length).toBe(1);
expect(result[0].propName).toBe("iconName");
expect(result[0].stack).toEqual(["navitems[1]", "icon"]);
});
});

51
packages/client/src/createApp.js

@ -15,32 +15,37 @@ import { isBound } from "./state/isState";
export const createApp = (componentLibraries, appDefinition, user) => {
const _initialiseComponent = (parentContext, hydrate) => (props, htmlElement, context, anchor=null) => {
const _initialiseChildren = (parentContext, hydrate) => (childrenProps, htmlElement, context, anchor=null) => {
const {componentName, libName} = splitName(props._component);
const childComponents = [];
if(!componentName || !libName) return;
for(let childProps of childrenProps) {
const {componentName, libName} = splitName(childProps._component);
const {initialProps, bind} = setupBinding(
store, props, coreApi,
context || parentContext, appDefinition.appRootPath);
if(!componentName || !libName) return;
const componentProps = {
...initialProps,
_bb:bb(context || parentContext, props)
};
const {initialProps, bind} = setupBinding(
store, childProps, coreApi,
context || parentContext, appDefinition.appRootPath);
const component = new (componentLibraries[libName][componentName])({
target: htmlElement,
props: componentProps,
hydrate,
anchor
});
const componentProps = {
...initialProps,
_bb:bb(context || parentContext, childProps)
};
const component = new (componentLibraries[libName][componentName])({
target: htmlElement,
props: componentProps,
hydrate,
anchor
});
bind(component);
bind(component);
childComponents.push(component);
}
return component;
return childComponents;
}
const coreApi = createCoreApi(appDefinition, user);
@ -86,10 +91,10 @@ export const createApp = (componentLibraries, appDefinition, user) => {
}
const bb = (context, props) => ({
hydrateComponent: _initialiseComponent(context, true),
appendComponent: _initialiseComponent(context, false),
insertComponent: (props, htmlElement, anchor, context) =>
_initialiseComponent(context, false)(props, htmlElement, context, anchor),
hydrateChildren: _initialiseChildren(context, true),
appendChildren: _initialiseChildren(context, false),
insertChildren: (props, htmlElement, anchor, context) =>
_initialiseChildren(context, false)(props, htmlElement, context, anchor),
store,
relativeUrl,
api,

4
packages/client/src/index.js

@ -34,8 +34,8 @@ export const loadBudibase = async (componentLibraries, props) => {
}
const _app = createApp(componentLibraries, appDefinition, user);
_app.hydrateComponent(
props,
_app.hydrateChildren(
[props],
document.body);
};

71
packages/server/builder/budibase-client.esm.mjs

File diff suppressed because one or more lines are too long

49
packages/server/builder/bundle.css

@ -1,49 +1,46 @@
main.svelte-15fmzor{height:100%;width:100%;font-family:"Roboto", Helvetica, Arial, sans-serif}
.root.svelte-y7jhgd{height:100%;width:100%;display:flex;flex-direction:column}.top-nav.svelte-y7jhgd{flex:0 0 auto;height:25px;background:white;padding:5px;width:100%}.content.svelte-y7jhgd{flex:1 1 auto;width:100%;height:100px}.content.svelte-y7jhgd>div.svelte-y7jhgd{height:100%;width:100%}.topnavitem.svelte-y7jhgd{cursor:pointer;color:var(--secondary50);padding:0px 15px;font-weight:600;font-size:.9rem}.topnavitem.svelte-y7jhgd:hover{color:var(--secondary75);font-weight:600}.active.svelte-y7jhgd{color:var(--primary100);font-weight:900}
.root.svelte-e4n7zy{position:fixed;margin:0 auto;text-align:center;top:20%;width:100%}.inner.svelte-e4n7zy{display:inline-block;margin:auto}.logo.svelte-e4n7zy{width:300px;margin-bottom:40px}.root.svelte-e4n7zy .option{width:250px}.app-link.svelte-e4n7zy{margin-top:10px;display:block}
.root.svelte-y7jhgd{height:100%;width:100%;display:flex;flex-direction:column}.top-nav.svelte-y7jhgd{flex:0 0 auto;height:25px;background:white;padding:5px;width:100%}.content.svelte-y7jhgd{flex:1 1 auto;width:100%;height:100px}.content.svelte-y7jhgd>div.svelte-y7jhgd{height:100%;width:100%}.topnavitem.svelte-y7jhgd{cursor:pointer;color:var(--secondary50);padding:0px 15px;font-weight:600;font-size:.9rem}.topnavitem.svelte-y7jhgd:hover{color:var(--secondary75);font-weight:600}.active.svelte-y7jhgd{color:var(--primary100);font-weight:900}
button.svelte-bxuckr{border-style:none;background-color:rgba(0,0,0,0);cursor:pointer;outline:none}button.svelte-bxuckr:hover{color:var(--hovercolor)}button.svelte-bxuckr:active{outline:none}
.border-normal.svelte-vnon4v{border-radius:var(--borderradiusall)}.border-left.svelte-vnon4v{border-radius:var(--borderradius) 0 0 var(--borderradius)}.border-right.svelte-vnon4v{border-radius:0 var(--borderradius) var(--borderradius) 0}.border-middle.svelte-vnon4v{border-radius:0}button.svelte-vnon4v{border-style:solid;padding:7.5px 15px;cursor:pointer;margin:5px;border-radius:5px}.primary.svelte-vnon4v{background-color:var(--primary100);border-color:var(--primary100);color:var(--white)}.primary.svelte-vnon4v:hover{background-color:var(--primary75);border-color:var(--primary75)}.primary.svelte-vnon4v:active{background-color:var(--primarydark);border-color:var(--primarydark)}.primary-outline.svelte-vnon4v{background-color:var(--white);border-color:var(--primary100);color:var(--primary100)}.primary-outline.svelte-vnon4v:hover{background-color:var(--primary10)}.primary-outline.svelte-vnon4v:pressed{background-color:var(--primary25)}.secondary.svelte-vnon4v{background-color:var(--secondary100);border-color:var(--secondary100);color:var(--white)}.secondary.svelte-vnon4v:hover{background-color:var(--secondary75);border-color:var(--secondary75)}.secondary.svelte-vnon4v:pressed{background-color:var(--secondarydark);border-color:var(--secondarydark)}.secondary-outline.svelte-vnon4v{background-color:var(--white);border-color:var(--secondary100);color:var(--secondary100)}.secondary-outline.svelte-vnon4v:hover{background-color:var(--secondary10)}.secondary-outline.svelte-vnon4v:pressed{background-color:var(--secondary25)}.success.svelte-vnon4v{background-color:var(--success100);border-color:var(--success100);color:var(--white)}.success.svelte-vnon4v:hover{background-color:var(--success75);border-color:var(--success75)}.success.svelte-vnon4v:pressed{background-color:var(--successdark);border-color:var(--successdark)}.success-outline.svelte-vnon4v{background-color:var(--white);border-color:var(--success100);color:var(--success100)}.success-outline.svelte-vnon4v:hover{background-color:var(--success10)}.success-outline.svelte-vnon4v:pressed{background-color:var(--success25)}.deletion.svelte-vnon4v{background-color:var(--deletion100);border-color:var(--deletion100);color:var(--white)}.deletion.svelte-vnon4v:hover{background-color:var(--deletion75);border-color:var(--deletion75)}.deletion.svelte-vnon4v:pressed{background-color:var(--deletiondark);border-color:var(--deletiondark)}.deletion-outline.svelte-vnon4v{background-color:var(--white);border-color:var(--deletion100);color:var(--deletion100)}.deletion-outline.svelte-vnon4v:hover{background-color:var(--deletion10)}.deletion-outline.svelte-vnon4v:pressed{background-color:var(--deletion25)}
.root.svelte-17zel0b{display:grid;grid-template-columns:250px 1fr 300px;height:100%;width:100%}.ui-nav.svelte-17zel0b{grid-column:1;background-color:var(--secondary5);height:100%}.preview-pane.svelte-17zel0b{grid-column:2}.components-pane.svelte-17zel0b{grid-column:3;background-color:var(--secondary5);min-height:0px;overflow-y:hidden}.pages-list-container.svelte-17zel0b{padding-top:2rem}.components-nav-header.svelte-17zel0b{font-size:.9rem}.nav-group-header.svelte-17zel0b{font-size:.9rem;padding-left:1rem}.nav-items-container.svelte-17zel0b{padding:1rem 1rem 0rem 1rem}.nav-group-header.svelte-17zel0b{display:grid;grid-template-columns:[icon] auto [title] 1fr [button] auto;padding:2rem 1rem 0rem 1rem;font-size:.9rem;font-weight:bold}.nav-group-header.svelte-17zel0b>div.svelte-17zel0b:nth-child(1){padding:0rem .5rem 0rem 0rem;vertical-align:bottom;grid-column-start:icon;margin-right:5px}.nav-group-header.svelte-17zel0b>span.svelte-17zel0b:nth-child(2){margin-left:5px;vertical-align:bottom;grid-column-start:title;margin-top:auto}.nav-group-header.svelte-17zel0b>div.svelte-17zel0b:nth-child(3){vertical-align:bottom;grid-column-start:button;cursor:pointer;color:var(--primary75)}.nav-group-header.svelte-17zel0b>div.svelte-17zel0b:nth-child(3):hover{color:var(--primary75)}
.root.svelte-q8uz1n{height:100%;display:flex}.content.svelte-q8uz1n{flex:1 1 auto;height:100%;background-color:var(--white);margin:0}.nav.svelte-q8uz1n{flex:0 1 auto;width:300px;height:100%}
.root.svelte-rjo9m0{display:grid;grid-template-columns:[uiNav] 250px [preview] auto [properties] 300px;height:100%;width:100%;overflow-y:auto}.ui-nav.svelte-rjo9m0{grid-column-start:uiNav;background-color:var(--secondary5);height:100%}.properties-pane.svelte-rjo9m0{grid-column-start:properties;background-color:var(--secondary5);height:100%;overflow-y:hidden}.pages-list-container.svelte-rjo9m0{padding-top:2rem}.components-nav-header.svelte-rjo9m0{font-size:.9rem}.nav-group-header.svelte-rjo9m0{font-size:.9rem;padding-left:1rem}.nav-items-container.svelte-rjo9m0{padding:1rem 1rem 0rem 1rem}.nav-group-header.svelte-rjo9m0{display:grid;grid-template-columns:[icon] auto [title] 1fr [button] auto;padding:2rem 1rem 0rem 1rem;font-size:.9rem;font-weight:bold}.nav-group-header.svelte-rjo9m0>div.svelte-rjo9m0:nth-child(1){padding:0rem .5rem 0rem 0rem;vertical-align:bottom;grid-column-start:icon;margin-right:5px}.nav-group-header.svelte-rjo9m0>span.svelte-rjo9m0:nth-child(2){margin-left:5px;vertical-align:bottom;grid-column-start:title;margin-top:auto}.nav-group-header.svelte-rjo9m0>div.svelte-rjo9m0:nth-child(3){vertical-align:bottom;grid-column-start:button;cursor:pointer;color:var(--primary75)}.nav-group-header.svelte-rjo9m0>div.svelte-rjo9m0:nth-child(3):hover{color:var(--primary75)}
.root.svelte-117bbrk{padding-bottom:10px;padding-left:10px;font-size:.9rem;color:var(--secondary50);font-weight:bold}.hierarchy-item.svelte-117bbrk{cursor:pointer;padding:5px 0px}.hierarchy-item.svelte-117bbrk:hover{color:var(--secondary100)}.component.svelte-117bbrk{margin-left:5px}.selected.svelte-117bbrk{color:var(--primary100);font-weight:bold}.title.svelte-117bbrk{margin-left:10px}
.uk-modal-dialog.svelte-91ta29{border-radius:.3rem}
.root.svelte-1r2dipt{color:var(--secondary50);font-size:.9rem;font-weight:bold}.hierarchy-item.svelte-1r2dipt{cursor:pointer;padding:5px 0px}.hierarchy-item.svelte-1r2dipt:hover{color:var(--secondary)}.component.svelte-1r2dipt{margin-left:5px}.currentfolder.svelte-1r2dipt{color:var(--secondary100)}.selected.svelte-1r2dipt{color:var(--primary100);font-weight:bold}.title.svelte-1r2dipt{margin-left:10px}
.root.svelte-r1aen3{height:100%;display:flex;flex-direction:column;border-style:solid;border-width:1px 0 0 0;border-color:var(--slate)}.title.svelte-r1aen3{padding:1rem;display:grid;grid-template-columns:[name] 1fr [actions] auto;color:var(--secondary100);font-size:.9rem;font-weight:bold}.title.svelte-r1aen3>div.svelte-r1aen3:nth-child(1){grid-column-start:name;color:var(--secondary100)}.title.svelte-r1aen3>div.svelte-r1aen3:nth-child(2){grid-column-start:actions}.component-props-container.svelte-r1aen3{flex:1 1 auto;overflow-y:auto}
.section-container.svelte-yk1mmr{padding:15px;border-style:dotted;border-width:1px;border-color:var(--lightslate);border-radius:2px}.section-container.svelte-yk1mmr:nth-child(1){margin-bottom:15px}.row-text.svelte-yk1mmr{margin-right:15px;color:var(--primary100)}input.svelte-yk1mmr{margin-right:15px}p.svelte-yk1mmr>span.svelte-yk1mmr{margin-left:30px}.header.svelte-yk1mmr{display:grid;grid-template-columns:[title] 1fr [icon] auto}.header.svelte-yk1mmr>div.svelte-yk1mmr:nth-child(1){grid-column-start:title}.header.svelte-yk1mmr>div.svelte-yk1mmr:nth-child(2){grid-column-start:icon}
.root.svelte-18ccx5u{display:flex;flex-direction:column}.library-header.svelte-18ccx5u{font-size:1.1em;border-color:var(--primary25);border-width:1px 0px;border-style:solid;background-color:var(--primary10);padding:5px 0;flex:0 0 auto}.library-container.svelte-18ccx5u{padding:0 0 10px 10px;flex:1 1 auto;min-height:0px}.inner-header.svelte-18ccx5u{font-size:0.9em;font-weight:bold;margin-top:7px;margin-bottom:3px}.component.svelte-18ccx5u{padding:2px 0px;cursor:pointer}.component.svelte-18ccx5u:hover{background-color:var(--lightslate)}.component.svelte-18ccx5u>.name.svelte-18ccx5u{color:var(--secondary100);display:inline-block}.component.svelte-18ccx5u>.description.svelte-18ccx5u{font-size:0.8em;color:var(--secondary75);display:inline-block;margin-left:10px}
h1.svelte-11kb98w{font-size:1.2em}
.component-container.svelte-12kdu9y{grid-row-start:middle;grid-column-start:middle;position:relative;overflow:hidden;padding-top:56.25%;margin:auto}.component-container.svelte-12kdu9y iframe.svelte-12kdu9y{border:0;height:100%;left:0;position:absolute;top:0;width:100%}
h4.svelte-sqtlby{margin-top:20px}
.root.svelte-wfv60d{height:100%;position:relative;padding:1.5rem}.actions-header.svelte-wfv60d{flex:0 1 auto}.node-view.svelte-wfv60d{overflow-y:auto;flex:1 1 auto}
.root.svelte-1ersoxu{padding:15px}.help-text.svelte-1ersoxu{color:var(--slate);font-size:10pt}
.items-root.svelte-19lmivt{display:flex;flex-direction:column;max-height:100%;height:100%;background-color:var(--secondary5)}.nav-group-header.svelte-19lmivt{display:grid;grid-template-columns:[icon] auto [title] 1fr [button] auto;padding:2rem 1rem 0rem 1rem;font-size:.9rem;font-weight:bold}.nav-group-header.svelte-19lmivt>div.svelte-19lmivt:nth-child(1){padding:0rem .7rem 0rem 0rem;vertical-align:bottom;grid-column-start:icon;margin-right:5px}.nav-group-header.svelte-19lmivt>div.svelte-19lmivt:nth-child(2){margin-left:5px;vertical-align:bottom;grid-column-start:title;margin-top:auto}.nav-group-header.svelte-19lmivt>div.svelte-19lmivt:nth-child(3){vertical-align:bottom;grid-column-start:button;cursor:pointer;color:var(--primary75)}.nav-group-header.svelte-19lmivt>div.svelte-19lmivt:nth-child(3):hover{color:var(--primary75)}.hierarchy-title.svelte-19lmivt{flex:auto 1 1}.hierarchy.svelte-19lmivt{display:flex;flex-direction:column;flex:1 0 auto;height:100px}.hierarchy-items-container.svelte-19lmivt{flex:1 1 auto;overflow-y:auto}
.root.svelte-wfv60d{height:100%;position:relative;padding:1.5rem}.actions-header.svelte-wfv60d{flex:0 1 auto}.node-view.svelte-wfv60d{overflow-y:auto;flex:1 1 auto}
.root.svelte-nd1yft{height:100%;position:relative;padding:1.5rem}
.root.svelte-apja7r{height:100%;position:relative}.actions-header.svelte-apja7r{flex:0 1 auto}.node-view.svelte-apja7r{overflow-y:auto;flex:1 1 auto}
.root.svelte-117bbrk{padding-bottom:10px;padding-left:10px;font-size:.9rem;color:var(--secondary50);font-weight:bold}.hierarchy-item.svelte-117bbrk{cursor:pointer;padding:5px 0px}.hierarchy-item.svelte-117bbrk:hover{color:var(--secondary100)}.component.svelte-117bbrk{margin-left:5px}.selected.svelte-117bbrk{color:var(--primary100);font-weight:bold}.title.svelte-117bbrk{margin-left:10px}
h1.svelte-16jkjx9{font-size:1.2em}
.root.svelte-1r2dipt{color:var(--secondary50);font-size:.9rem;font-weight:bold}.hierarchy-item.svelte-1r2dipt{cursor:pointer;padding:5px 0px}.hierarchy-item.svelte-1r2dipt:hover{color:var(--secondary)}.component.svelte-1r2dipt{margin-left:5px}.currentfolder.svelte-1r2dipt{color:var(--secondary100)}.selected.svelte-1r2dipt{color:var(--primary100);font-weight:bold}.title.svelte-1r2dipt{margin-left:10px}
.uk-modal-dialog.svelte-vwwrf9{border-radius:.3rem}
.root.svelte-1abif7s{height:100%;display:flex;flex-direction:column}.padding.svelte-1abif7s{padding:1rem 1rem 0rem 1rem}.info-text.svelte-1abif7s{color:var(--secondary100);font-size:.8rem !important;font-weight:bold}.title.svelte-1abif7s{padding:2rem 1rem 1rem 1rem;display:grid;grid-template-columns:[name] 1fr [actions] auto;color:var(--secondary100);font-size:.9rem;font-weight:bold}.title.svelte-1abif7s>div.svelte-1abif7s:nth-child(1){grid-column-start:name;color:var(--secondary100)}.title.svelte-1abif7s>div.svelte-1abif7s:nth-child(2){grid-column-start:actions}.section-header.svelte-1abif7s{display:grid;grid-template-columns:[name] 1fr [actions] auto;color:var(--secondary50);font-size:.9rem;font-weight:bold;vertical-align:middle}.component-props-container.svelte-1abif7s{flex:1 1 auto;overflow-y:auto}
.root.svelte-1ersoxu{padding:15px}.help-text.svelte-1ersoxu{color:var(--slate);font-size:10pt}
.section-container.svelte-yk1mmr{padding:15px;border-style:dotted;border-width:1px;border-color:var(--lightslate);border-radius:2px}.section-container.svelte-yk1mmr:nth-child(1){margin-bottom:15px}.row-text.svelte-yk1mmr{margin-right:15px;color:var(--primary100)}input.svelte-yk1mmr{margin-right:15px}p.svelte-yk1mmr>span.svelte-yk1mmr{margin-left:30px}.header.svelte-yk1mmr{display:grid;grid-template-columns:[title] 1fr [icon] auto}.header.svelte-yk1mmr>div.svelte-yk1mmr:nth-child(1){grid-column-start:title}.header.svelte-yk1mmr>div.svelte-yk1mmr:nth-child(2){grid-column-start:icon}
.component-container.svelte-teqoiq{grid-row-start:middle;grid-column-start:middle;position:relative;overflow:hidden;padding-top:56.25%;margin:auto}.component-container.svelte-teqoiq iframe.svelte-teqoiq{border:0;height:100%;left:0;position:absolute;top:0;width:100%}
.root.svelte-x3bf9z{display:flex}.root.svelte-x3bf9z:last-child{border-radius:0 var(--borderradius) var(--borderradius) 0}.root.svelte-x3bf9z:first-child{border-radius:var(--borderradius) 0 0 var(--borderradius)}.root.svelte-x3bf9z:not(:first-child):not(:last-child){border-radius:0}
.edit-button.svelte-zm41av{cursor:pointer;color:var(--secondary25)}.title.svelte-zm41av{margin:3rem 0rem 0rem 0rem;font-weight:700}.table-content.svelte-zm41av{font-weight:500;font-size:.9rem}tr.svelte-zm41av:hover .edit-button.svelte-zm41av{color:var(--secondary75)}
.library-header.svelte-chhyel{font-size:1.1em;border-color:var(--primary25);border-width:1px 0px;border-style:solid;background-color:var(--primary10);padding:5px 0}.library-container.svelte-chhyel{padding:0 0 10px 10px}.inner-header.svelte-chhyel{font-size:0.9em;font-weight:bold;margin-top:7px;margin-bottom:3px}.component.svelte-chhyel{padding:2px 0px;cursor:pointer}.component.svelte-chhyel:hover{background-color:var(--lightslate)}.component.svelte-chhyel>.name.svelte-chhyel{color:var(--secondary100);display:inline-block}.component.svelte-chhyel>.description.svelte-chhyel{font-size:0.8em;color:var(--secondary75);display:inline-block;margin-left:10px}
.info-text.svelte-1gx0gkl{font-size:0.7rem;color:var(--secondary50)}
.root.svelte-t6vms4{font-size:10pt;width:100%}.form-root.svelte-t6vms4{display:flex;flex-wrap:wrap}.prop-container.svelte-t6vms4{flex:1 1 auto;min-width:250px}
.nav-item.svelte-1i5jqm7{padding:1.5rem 1rem 0rem 1rem;font-size:.9rem;font-weight:bold;cursor:pointer;flex:0 0 auto}.nav-item.svelte-1i5jqm7:hover{background-color:var(--primary10)}.active.svelte-1i5jqm7{background-color:var(--primary10)}
.dropdown-background.svelte-11ifkop{position:fixed;top:0;left:0;width:100vw;height:100vh}.root.svelte-11ifkop{cursor:pointer;z-index:1}.dropdown-content.svelte-11ifkop{position:absolute;background-color:var(--white);min-width:160px;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);z-index:1;font-weight:normal;border-style:solid;border-width:1px;border-color:var(--secondary10)}.dropdown-content.svelte-11ifkop:not(:focus){display:none}.action-row.svelte-11ifkop{padding:7px 10px;cursor:pointer}.action-row.svelte-11ifkop:hover{background-color:var(--primary100);color:var(--white)}
.edit-button.svelte-lhfdtn{cursor:pointer;color:var(--secondary25)}tr.svelte-lhfdtn:hover .edit-button.svelte-lhfdtn{color:var(--secondary75)}.title.svelte-lhfdtn{margin:3rem 0rem 0rem 0rem;font-weight:700}.table-content.svelte-lhfdtn{font-weight:500;font-size:.9rem}
.root.svelte-17ju2r{display:block;font-size:.9rem;width:100%;cursor:pointer;color:var(--secondary50);font-weight:500}.title.svelte-17ju2r{padding-top:.5rem;padding-right:.5rem}.title.svelte-17ju2r:hover{background-color:var(--secondary10)}.active.svelte-17ju2r{background-color:var(--primary10)}
.nav-item.svelte-1i5jqm7{padding:1.5rem 1rem 0rem 1rem;font-size:.9rem;font-weight:bold;cursor:pointer;flex:0 0 auto}.nav-item.svelte-1i5jqm7:hover{background-color:var(--primary10)}.active.svelte-1i5jqm7{background-color:var(--primary10)}
.root.svelte-18xd5y3{height:100%;padding:2rem}.settings-title.svelte-18xd5y3{font-weight:700}.title.svelte-18xd5y3{margin:3rem 0rem 0rem 0rem;font-weight:700}.recordkey.svelte-18xd5y3{font-size:14px;font-weight:600;color:var(--primary100)}.fields-table.svelte-18xd5y3{margin:1rem 1rem 0rem 0rem;border-collapse:collapse}.add-field-button.svelte-18xd5y3{cursor:pointer}.edit-button.svelte-18xd5y3{cursor:pointer;color:var(--secondary25)}.edit-button.svelte-18xd5y3:hover{cursor:pointer;color:var(--secondary75)}th.svelte-18xd5y3{text-align:left}td.svelte-18xd5y3{padding:1rem 5rem 1rem 0rem;margin:0;font-size:14px;font-weight:500}.field-label.svelte-18xd5y3{font-size:14px;font-weight:500}thead.svelte-18xd5y3>tr.svelte-18xd5y3{border-width:0px 0px 1px 0px;border-style:solid;border-color:var(--secondary75);margin-bottom:20px}tbody.svelte-18xd5y3>tr.svelte-18xd5y3{border-width:0px 0px 1px 0px;border-style:solid;border-color:var(--primary10)}tbody.svelte-18xd5y3>tr.svelte-18xd5y3:hover{background-color:var(--primary10)}tbody.svelte-18xd5y3>tr:hover .edit-button.svelte-18xd5y3{color:var(--secondary75)}.index-container.svelte-18xd5y3{border-style:solid;border-width:0 0 1px 0;border-color:var(--secondary25);padding:10px;margin-bottom:5px}.index-label.svelte-18xd5y3{color:var(--slate)}.index-name.svelte-18xd5y3{font-weight:bold;color:var(--primary100)}.index-container.svelte-18xd5y3 code.svelte-18xd5y3{margin:0;display:inline;background-color:var(--primary10);color:var(--secondary100);padding:3px}.index-field-row.svelte-18xd5y3{margin:1rem 0rem 0rem 0rem}.no-indexes.svelte-18xd5y3{margin:1rem 0rem 0rem 0rem;font-family:var(--fontnormal);font-size:14px}
.root.svelte-ehsf0i{display:block;font-size:.9rem;width:100%;cursor:pointer;font-weight:bold}.title.svelte-ehsf0i{font:var(--fontblack);padding-top:10px;padding-right:5px;padding-bottom:10px;color:var(--secondary100)}.title.svelte-ehsf0i:hover{background-color:var(--secondary10)}
.edit-button.svelte-zm41av{cursor:pointer;color:var(--secondary25)}.title.svelte-zm41av{margin:3rem 0rem 0rem 0rem;font-weight:700}.table-content.svelte-zm41av{font-weight:500;font-size:.9rem}tr.svelte-zm41av:hover .edit-button.svelte-zm41av{color:var(--secondary75)}
.edit-button.svelte-lhfdtn{cursor:pointer;color:var(--secondary25)}tr.svelte-lhfdtn:hover .edit-button.svelte-lhfdtn{color:var(--secondary75)}.title.svelte-lhfdtn{margin:3rem 0rem 0rem 0rem;font-weight:700}.table-content.svelte-lhfdtn{font-weight:500;font-size:.9rem}
.root.svelte-wgyofl{padding:1.5rem;width:100%;align-items:right}
.root.svelte-pq2tmv{height:100%;padding:15px}.allowed-records.svelte-pq2tmv{margin:20px 0px}.allowed-records.svelte-pq2tmv>span.svelte-pq2tmv{margin-right:30px}
.info-text.svelte-1gx0gkl{font-size:0.7rem;color:var(--secondary50)}
.library-header.svelte-chhyel{font-size:1.1em;border-color:var(--primary25);border-width:1px 0px;border-style:solid;background-color:var(--primary10);padding:5px 0}.library-container.svelte-chhyel{padding:0 0 10px 10px}.inner-header.svelte-chhyel{font-size:0.9em;font-weight:bold;margin-top:7px;margin-bottom:3px}.component.svelte-chhyel{padding:2px 0px;cursor:pointer}.component.svelte-chhyel:hover{background-color:var(--lightslate)}.component.svelte-chhyel>.name.svelte-chhyel{color:var(--secondary100);display:inline-block}.component.svelte-chhyel>.description.svelte-chhyel{font-size:0.8em;color:var(--secondary75);display:inline-block;margin-left:10px}
.component.svelte-3sgo90{padding:5px 0}.component.svelte-3sgo90 .title.svelte-3sgo90{width:300px
}.component.svelte-3sgo90>.description.svelte-3sgo90{font-size:0.8em;color:var(--secondary75)}.button-container.svelte-3sgo90{text-align:right;margin-top:20px}.error.svelte-3sgo90{font-size:10pt;color:red}
.root.svelte-47ohpz{font-size:10pt}.padding.svelte-47ohpz{padding:0 10px}.inherited-title.svelte-47ohpz{padding:1rem 1rem 1rem 1rem;display:grid;grid-template-columns:[name] 1fr [actions] auto;color:var(--secondary100);font-size:.9rem;font-weight:bold}.inherited-title.svelte-47ohpz>div.svelte-47ohpz:nth-child(1){grid-column-start:name;color:var(--secondary50)}.inherited-title.svelte-47ohpz>div.svelte-47ohpz:nth-child(2){grid-column-start:actions;color:var(--secondary100)}
.title.svelte-dhe1ge{padding:3px;background-color:white;color:var(--secondary100);border-style:solid;border-width:1px 0 0 0;border-color:var(--lightslate)}.title.svelte-dhe1ge>span.svelte-dhe1ge{margin-left:10px}
.root.svelte-16sjty9{padding:2rem;border-radius:2rem}.uk-grid-small.svelte-16sjty9{padding:1rem}.option-container.svelte-16sjty9{border-style:dotted;border-width:1px;border-color:var(--primary75);padding:3px;margin-right:5px}
.root.svelte-1hs1zh2{height:100%;padding:2rem}.settings-title.svelte-1hs1zh2{font-weight:700}.title.svelte-1hs1zh2{margin:3rem 0rem 0rem 0rem;font-weight:700}.recordkey.svelte-1hs1zh2{font-size:14px;font-weight:600;color:var(--primary100)}.fields-table.svelte-1hs1zh2{margin:1rem 1rem 0rem 0rem;border-collapse:collapse}.add-field-button.svelte-1hs1zh2{cursor:pointer}.edit-button.svelte-1hs1zh2{cursor:pointer;color:var(--secondary25)}.edit-button.svelte-1hs1zh2:hover{cursor:pointer;color:var(--secondary75)}th.svelte-1hs1zh2{text-align:left}td.svelte-1hs1zh2{padding:1rem 5rem 1rem 0rem;margin:0;font-size:14px;font-weight:500}.field-label.svelte-1hs1zh2{font-size:14px;font-weight:500}thead.svelte-1hs1zh2>tr.svelte-1hs1zh2{border-width:0px 0px 1px 0px;border-style:solid;border-color:var(--secondary75);margin-bottom:20px}tbody.svelte-1hs1zh2>tr.svelte-1hs1zh2{border-width:0px 0px 1px 0px;border-style:solid;border-color:var(--primary10)}tbody.svelte-1hs1zh2>tr.svelte-1hs1zh2:hover{background-color:var(--primary10)}tbody.svelte-1hs1zh2>tr:hover .edit-button.svelte-1hs1zh2{color:var(--secondary75)}.index-container.svelte-1hs1zh2{border-style:solid;border-width:0 0 1px 0;border-color:var(--secondary25);padding:10px;margin-bottom:5px}.index-label.svelte-1hs1zh2{color:var(--slate)}.index-name.svelte-1hs1zh2{font-weight:bold;color:var(--primary100)}.index-container.svelte-1hs1zh2 code.svelte-1hs1zh2{margin:0;display:inline;background-color:var(--primary10);color:var(--secondary100);padding:3px}.index-field-row.svelte-1hs1zh2{margin:1rem 0rem 0rem 0rem}.no-indexes.svelte-1hs1zh2{margin:1rem 0rem 0rem 0rem;font-family:var(--fontnormal);font-size:14px}
input.svelte-9fre0g{margin-right:7px}
.root.svelte-1v0yya9{padding:1rem 1rem 0rem 1rem}.prop-label.svelte-1v0yya9{font-size:0.8rem;color:var(--secondary100);font-weight:bold}
.error-container.svelte-ole1mk{padding:10px;border-style:solid;border-color:var(--deletion100);border-radius:var(--borderradiusall);background:var(--deletion75)}.error-row.svelte-ole1mk{padding:5px 0px}
.root.svelte-16sjty9{padding:2rem;border-radius:2rem}.uk-grid-small.svelte-16sjty9{padding:1rem}.option-container.svelte-16sjty9{border-style:dotted;border-width:1px;border-color:var(--primary75);padding:3px;margin-right:5px}
textarea.svelte-di7k4b{padding:3px;margin-top:5px;margin-bottom:10px;background:var(--lightslate);color:var(--white);font-family:'Courier New', Courier, monospace;width:95%;height:100px;border-radius:5px}
.root.svelte-1v0yya9{padding:1rem 1rem 0rem 1rem}.prop-label.svelte-1v0yya9{font-size:0.8rem;color:var(--secondary100);font-weight:bold}
.root.svelte-ogh8o0{display:grid;grid-template-columns:[name] 1fr [actions] auto}.root.svelte-ogh8o0>div.svelte-ogh8o0:nth-child(1){grid-column-start:name;color:var(--secondary50)}.root.svelte-ogh8o0>div.svelte-ogh8o0:nth-child(2){grid-column-start:actions}.selectedname.svelte-ogh8o0{font-weight:bold;color:var(--secondary)}
textarea.svelte-1kv2xk7{width:300px;height:200px}
.addelement-container.svelte-r1ft9p{cursor:pointer;padding:3px 0px;text-align:center}.addelement-container.svelte-r1ft9p:hover{background-color:var(--primary25);margin-top:5px}.control-container.svelte-r1ft9p{padding-left:3px;background:var(--secondary10)}.separator.svelte-r1ft9p{width:60%;margin:10px auto;border-style:solid;border-width:1px 0 0 0;border-color:var(--primary25)}
.unbound-container.svelte-jubmd5{display:flex;margin:.5rem 0rem .5rem 0rem}.unbound-container.svelte-jubmd5>.svelte-jubmd5:nth-child(1){width:auto;flex:1 0 auto;font-size:0.8rem;color:var(--secondary100);border-radius:.2rem}.bound-header.svelte-jubmd5{display:flex}.bound-header.svelte-jubmd5>div.svelte-jubmd5:nth-child(1){flex:1 0 auto;width:30px;color:var(--secondary50);padding-left:5px}.binding-prop-label.svelte-jubmd5{color:var(--secondary50)}
.addelement-container.svelte-199q8jr{cursor:pointer;padding:3px 0px;text-align:center}.addelement-container.svelte-199q8jr:hover{background-color:var(--primary25)}.item-container.svelte-199q8jr{padding-left:3px;background:var(--secondary10)}
textarea.svelte-1kv2xk7{width:300px;height:200px}
.type-selector-container.svelte-1b6pj9u{display:flex}.type-selector.svelte-1b6pj9u{border-color:var(--primary50);border-radius:2px;width:50px;flex:1 0 auto}
.root.svelte-rj4q22{height:100%;display:flex;flex-direction:column}.switcher.svelte-rj4q22{flex:0 0 auto}.switcher.svelte-rj4q22>button.svelte-rj4q22{display:inline-block;background-color:rgba(0,0,0,0);border-style:solid;border-color:var(--slate);margin:5px;padding:5px;cursor:pointer}.switcher.svelte-rj4q22>.selected.svelte-rj4q22{background-color:red}.panel.svelte-rj4q22{flex:1 1 auto;height:0px;overflow-y:auto}
/*# sourceMappingURL=bundle.css.map */

102
packages/server/builder/bundle.css.map

File diff suppressed because one or more lines are too long

65293
packages/server/builder/bundle.js

File diff suppressed because one or more lines are too long

2
packages/server/builder/bundle.js.map

File diff suppressed because one or more lines are too long

26
packages/server/middleware/routers.js

@ -6,12 +6,12 @@ const { resolve } = require("path");
const send = require('koa-send');
const {
getPackageForBuilder,
getRootComponents,
getComponents,
savePackage,
getApps,
saveDerivedComponent,
renameDerivedComponent,
deleteDerivedComponent,
saveScreen,
renameScreen,
deleteScreen,
componentLibraryInfo
} = require("../utilities/builder");
@ -162,9 +162,9 @@ module.exports = (config, app) => {
ctx.request.body);
ctx.response.status = StatusCodes.OK;
})
.get("/_builder/api/:appname/rootcomponents", async (ctx) => {
.get("/_builder/api/:appname/components", async (ctx) => {
try {
ctx.body = getRootComponents(
ctx.body = getComponents(
config,
ctx.params.appname,
ctx.query.lib);
@ -194,26 +194,26 @@ module.exports = (config, app) => {
ctx.body = info.generators;
ctx.response.status = StatusCodes.OK;
})
.post("/_builder/api/:appname/derivedcomponent", async (ctx) => {
await saveDerivedComponent(
.post("/_builder/api/:appname/screen", async (ctx) => {
await saveScreen(
config,
ctx.params.appname,
ctx.request.body);
ctx.response.status = StatusCodes.OK;
})
.patch("/_builder/api/:appname/derivedcomponent", async (ctx) => {
await renameDerivedComponent(
.patch("/_builder/api/:appname/screen", async (ctx) => {
await renameScreen(
config,
ctx.params.appname,
ctx.request.body.oldname,
ctx.request.body.newname);
ctx.response.status = StatusCodes.OK;
})
.delete("/_builder/api/:appname/derivedcomponent/*", async (ctx) => {
.delete("/_builder/api/:appname/screen/*", async (ctx) => {
const name = ctx.request.path.replace(
`/_builder/api/${ctx.params.appname}/derivedcomponent/`, "");
`/_builder/api/${ctx.params.appname}/screen/`, "");
await deleteDerivedComponent(
await deleteScreen(
config,
ctx.params.appname,
decodeURI(name));

38
packages/server/tests/builder.spec.js

@ -5,8 +5,8 @@ const testPages = require("../appPackages/testApp/pages.json");
const testComponents = require("../appPackages/testApp/customComponents/components.json");
const testMoreComponents = require("../appPackages/testApp/moreCustomComponents/components.json");
const statusCodes = require("../utilities/statusCodes");
const derivedComponent1 = require("../appPackages/testApp/components/myTextBox.json");
const derivedComponent2 = require("../appPackages/testApp/components/subfolder/otherTextBox.json");
const screen1 = require("../appPackages/testApp/components/myTextBox.json");
const screen2 = require("../appPackages/testApp/components/subfolder/otherTextBox.json");
const { readJSON, pathExists, unlink } = require("fs-extra");
const app = require("./testApp")();
@ -49,36 +49,36 @@ it("/apppackage should get pages", async () => {
expect(body.pages).toEqual(testPages);
});
it("/apppackage should get rootComponents", async () => {
it("/apppackage should get components", async () => {
const {body} = await app.get("/_builder/api/testApp/appPackage")
.expect(statusCodes.OK);
expect(body.rootComponents["./customComponents/textbox"]).toBeDefined();
expect(body.rootComponents["./moreCustomComponents/textbox"]).toBeDefined();
expect(body.components["./customComponents/textbox"]).toBeDefined();
expect(body.components["./moreCustomComponents/textbox"]).toBeDefined();
expect(body.rootComponents["./customComponents/textbox"])
expect(body.components["./customComponents/textbox"])
.toEqual(testComponents.textbox);
expect(body.rootComponents["./moreCustomComponents/textbox"])
expect(body.components["./moreCustomComponents/textbox"])
.toEqual(testMoreComponents.textbox);
});
it("/apppackage should get derivedComponents", async () => {
it("/apppackage should get screens", async () => {
const {body} = await app.get("/_builder/api/testApp/appPackage")
.expect(statusCodes.OK);
const expectedComponents = {
"myTextBox" : {...derivedComponent1, name:"myTextBox"},
"subfolder/otherTextBox": {...derivedComponent2, name:"subfolder/otherTextBox"}
"myTextBox" : {...screen1, name:"myTextBox"},
"subfolder/otherTextBox": {...screen2, name:"subfolder/otherTextBox"}
};
expect(body.derivedComponents).toEqual(expectedComponents);
expect(body.screens).toEqual(expectedComponents);
});
it("should be able to create new derived component", async () => {
const newDerivedComponent = {
const newscreen = {
name: "newTextBox",
inherits: "./customComponents/textbox",
props: {
@ -86,17 +86,17 @@ it("should be able to create new derived component", async () => {
}
};
await app.post("/_builder/api/testApp/derivedcomponent", newDerivedComponent)
await app.post("/_builder/api/testApp/screen", newscreen)
.expect(statusCodes.OK);
const componentFile = "./appPackages/testApp/components/newTextBox.json";
expect(await pathExists(componentFile)).toBe(true);
expect(await readJSON(componentFile)).toEqual(newDerivedComponent);
expect(await readJSON(componentFile)).toEqual(newscreen);
});
it("should be able to update derived component", async () => {
const updatedDerivedComponent = {
const updatedscreen = {
name: "newTextBox",
inherits: "./customComponents/textbox",
props: {
@ -104,15 +104,15 @@ it("should be able to update derived component", async () => {
}
};
await app.post("/_builder/api/testApp/derivedcomponent", updatedDerivedComponent)
await app.post("/_builder/api/testApp/screen", updatedscreen)
.expect(statusCodes.OK);
const componentFile = "./appPackages/testApp/components/newTextBox.json";
expect(await readJSON(componentFile)).toEqual(updatedDerivedComponent);
expect(await readJSON(componentFile)).toEqual(updatedscreen);
});
it("should be able to rename derived component", async () => {
await app.patch("/_builder/api/testApp/derivedcomponent", {
await app.patch("/_builder/api/testApp/screen", {
oldname: "newTextBox", newname: "anotherSubFolder/newTextBox"
}).expect(statusCodes.OK);
@ -124,7 +124,7 @@ it("should be able to rename derived component", async () => {
});
it("should be able to delete derived component", async () => {
await app.delete("/_builder/api/testApp/derivedcomponent/anotherSubFolder/newTextBox")
await app.delete("/_builder/api/testApp/screen/anotherSubFolder/newTextBox")
.expect(statusCodes.OK);
const componentFile = "./appPackages/testApp/components/anotherSubFolder/newTextBox.json";

20
packages/server/utilities/builder/index.js

@ -37,10 +37,10 @@ module.exports.getPackageForBuilder = async (config, appname) => {
pages,
rootComponents: await getRootComponents(appPath, pages),
components: await getComponents(appPath, pages),
derivedComponents: keyBy("name")(
await fetchDerivedComponents(appPath))
screens: keyBy("name")(
await fetchscreens(appPath))
});
}
@ -60,7 +60,7 @@ module.exports.getApps = async (config, master) => {
const componentPath = (appPath, name) =>
join(appPath, "components", name + ".json");
module.exports.saveDerivedComponent = async (config, appname, component) => {
module.exports.saveScreen = async (config, appname, component) => {
const appPath = appPackageFolder(config, appname);
const compPath = componentPath(appPath, component.name);
await ensureDir(dirname(compPath));
@ -70,7 +70,7 @@ module.exports.saveDerivedComponent = async (config, appname, component) => {
{encoding:"utf8", flag:"w", spaces:2});
}
module.exports.renameDerivedComponent = async (config, appname, oldName, newName) => {
module.exports.renameScreen = async (config, appname, oldName, newName) => {
const appPath = appPackageFolder(config, appname);
const oldComponentPath = componentPath(
@ -85,7 +85,7 @@ module.exports.renameDerivedComponent = async (config, appname, oldName, newName
newComponentPath);
}
module.exports.deleteDerivedComponent = async (config, appname, name) => {
module.exports.deleteScreen = async (config, appname, name) => {
const appPath = appPackageFolder(config, appname);
const componentFile = componentPath(appPath, name);
await unlink(componentFile);
@ -102,7 +102,7 @@ module.exports.componentLibraryInfo = async (config, appname, lib) => {
};
const getRootComponents = async (appPath, pages ,lib) => {
const getComponents = async (appPath, pages ,lib) => {
let libs;
if(!lib) {
@ -131,7 +131,7 @@ const getRootComponents = async (appPath, pages ,lib) => {
return {components, generators};
}
const fetchDerivedComponents = async (appPath, relativePath = "") => {
const fetchscreens = async (appPath, relativePath = "") => {
const currentDir = join(appPath, "components", relativePath);
@ -159,7 +159,7 @@ const fetchDerivedComponents = async (appPath, relativePath = "") => {
components.push(component);
} else {
const childComponents = await fetchDerivedComponents(
const childComponents = await fetchscreens(
appPath, join(relativePath, item)
);
@ -172,4 +172,4 @@ const fetchDerivedComponents = async (appPath, relativePath = "") => {
return components;
}
module.exports.getRootComponents = getRootComponents;
module.exports.getComponents = getComponents;

29
packages/standard-components/components.json

@ -33,7 +33,6 @@
"description": "an html <button />",
"props": {
"contentText": { "type": "string", "default": "Button" },
"contentComponent": "component",
"className": {"type": "string", "default": "default"},
"disabled": "bool",
"onClick": "event",
@ -71,8 +70,7 @@
"formControls": {
"type":"array",
"elementDefinition": {
"label": "string",
"control":"component"
"label": "string"
}
}
},
@ -136,18 +134,11 @@
"options": ["horizontal", "vertical"],
"default":"horizontal"
},
"children": {
"type":"array",
"elementDefinition": {
"control":"component"
}
},
"width": {"type":"string","default":"auto"},
"height": {"type":"string","default":"auto"},
"containerClass":"string",
"itemContainerClass":"string",
"data": "state",
"dataItemComponent": "component",
"onLoad": "event"
},
"tags": ["div", "container", "layout", "panel"]
@ -162,7 +153,6 @@
"children": {
"type":"array",
"elementDefinition": {
"component":"component",
"gridColumnStart":"string",
"gridColumnEnd":"string",
"gridRowStart":"string",
@ -220,7 +210,6 @@
"description": "A stylable div with a component inside",
"props" : {
"text": "string",
"component": "component",
"containerClass": "string",
"background": "string",
"border": "string",
@ -260,8 +249,7 @@
"items": {
"type": "array",
"elementDefinition" : {
"title": "string",
"component": "component"
"title": "string"
}
},
"selectedItem":"string",
@ -300,13 +288,11 @@
"children": {
"type":"array",
"elementDefinition": {
"component":"component",
"className": "string"
}
},
"className":"string",
"data": "state",
"dataItemComponent": "component",
"onLoad": "event"
},
"tags": ["div", "container", "layout"]
@ -370,16 +356,5 @@
"className":"string"
},
"tags": []
},
"if": {
"importPath": "if",
"name": "If",
"description": "An if condition.. if (CONDITION) THEN [display component A] ELSE [display component B]",
"props" : {
"condition": "string",
"thenComponent":{"type":"component", "required":true},
"elseComponent":"component"
},
"tags": []
}
}

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

@ -25338,7 +25338,7 @@ var app = (function () {
const bb = (bindings, context) => ({
hydrateComponent: _initialiseComponent(context, true),
appendComponent: _initialiseComponent(context, false),
appendChildren: _initialiseComponent(context, false),
insertComponent: (props, htmlElement, anchor, context) =>
_initialiseComponent(context, false)(props, htmlElement, context, anchor),
store,

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

@ -43,18 +43,18 @@ $: {
let index = 0;
for(let child of _bb.props.children) {
if(child.className) {
_bb.hydrateComponent(
child.component,
_bb.hydrateChildren(
child.children,
staticHtmlElements[index]);
} else {
const anchor = getStaticAnchor(index);
if(!anchor) {
_bb.appendComponent(
child.component,
_bb.appendChildren(
child.children,
rootDiv);
} else {
_bb.insertComponent(
child.component,
_bb.insertChildren(
child.children,
rootDiv,
anchor);
}
@ -76,7 +76,7 @@ $: {
if(hasData()) {
let index = 0;
for(let dataItem of data) {
_bb.appendComponent(
_bb.appendChildren(
_bb.props.dataItemComponent,
rootDiv,
dataItem

2
packages/standard-components/src/Form.svelte

@ -16,7 +16,7 @@ $ : {
if(_bb && htmlElements) {
for(let el in htmlElements) {
_bb.hydrateComponent(
_bb.hydrateChildren(
_bb.props.formControls[el].control,
htmlElements[el]
);

2
packages/standard-components/src/Grid.svelte

@ -22,7 +22,7 @@ let isInitilised = false;
$ : {
if(!isInitilised && _bb && htmlElements && Object.keys(htmlElements).length > 0) {
for(let el in htmlElements) {
_bb.hydrateComponent(
_bb.hydrateChildren(
_bb.props.children[el].component,
htmlElements[el]
);

4
packages/standard-components/src/If.svelte

@ -32,10 +32,10 @@ $: {
}
if(result) {
currentComponent = _bb.hydrateComponent(
currentComponent = _bb.hydrateChildren(
_bb.props.thenComponent,element);
} else if(elseComponent && elseComponent._component) {
currentComponent = _bb.hydrateComponent(
currentComponent = _bb.hydrateChildren(
_bb.props.elseComponent,element);
}

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

@ -53,8 +53,8 @@ $: {
const onSelectItem = (index) => () => {
selectedIndex = index;
if(!components[index]) {
const comp = _bb.hydrateComponent(
_bb.props.items[index].component, componentElements[index]);
const comp = _bb.hydrateChildren(
_bb.props.items[index].children, componentElements[index]);
components[index] = comp;
}
}

8
packages/standard-components/src/Panel.svelte

@ -2,7 +2,7 @@
import {buildStyle} from "./buildStyle";
import cssVars from "./cssVars";
export let component="";
export let children="";
export let text="";
export let containerClass="";
export let background="";
@ -36,8 +36,8 @@ $: {
cursor: onClick ? "pointer" : "none"
});
if(_bb && component && componentElement && !componentInitialised) {
_bb.hydrateComponent(_bb.props.component, componentElement);
if(_bb && children && children.length > 0 && componentElement && !componentInitialised) {
_bb.hydrateChildren(_bb.props.children, componentElement);
componentInitialised = true;
}
@ -60,7 +60,7 @@ const clickHandler = () => {
use:cssVars={styleVars}
bind:this={componentElement}
on:click={clickHandler}>
{component && component._component ? "" : text}
{children && children.length === 0 ? "" : text}
</div>
<style>

4
packages/standard-components/src/StackPanel.svelte

@ -43,7 +43,7 @@ $: {
}
for(let el in staticHtmlElements) {
staticComponents[el] = _bb.hydrateComponent(
staticComponents[el] = _bb.hydrateChildren(
_bb.props.children[el].control,
staticHtmlElements[el]
);
@ -61,7 +61,7 @@ $: {
if(hasData()) {
let index = 0;
for(let d in dataBoundElements) {
_bb.hydrateComponent(
_bb.hydrateChildren(
_bb.props.dataItemComponent,
dataBoundElements[d],
data[parseInt(d)]

2
packages/standard-components/src/Test/TestApp.svelte

@ -13,7 +13,7 @@ let currentComponent;
$: {
if(_bb && currentComponent) {
_bb.hydrateComponent(testProps, currentComponent);
_bb.hydrateChildren(testProps, currentComponent);
}
}

2
packages/standard-components/src/button.svelte

@ -38,7 +38,7 @@ const createClasses = (classes) => {
$:{
if(_bb && contentComponentContainer && contentComponent._component)
_bb.hydrateComponent(_bb.props.contentComponent, contentComponentContainer);
_bb.hydrateChildren(_bb.props.contentComponent, contentComponentContainer);
cssVariables = {
hoverColor, hoverBorder,

Loading…
Cancel
Save