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

This commit is contained in:
Michael Shanks 2020-01-17 23:06:42 +00:00
parent f2221e32e2
commit 4e09043e3a
62 changed files with 66610 additions and 1992 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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>

View File

@ -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>

View File

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

View File

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

View File

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

View File

@ -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>

View File

@ -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>

View File

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

View File

@ -8,11 +8,10 @@ import Textbox from "../common/Textbox.svelte";
import UIkit from "uikit"; import UIkit from "uikit";
import { pipe } from "../common/core"; import { pipe } from "../common/core";
import { import {
getComponentInfo getScreenInfo
} from "./pagesParsing/createProps"; } from "./pagesParsing/createProps";
import Button from "../common/Button.svelte"; import Button from "../common/Button.svelte";
import ButtonGroup from "../common/ButtonGroup.svelte"; import ButtonGroup from "../common/ButtonGroup.svelte";
import ComponentInstanceEditor from "./ComponentInstanceEditor.svelte";
import { import {
cloneDeep, cloneDeep,
@ -30,17 +29,11 @@ let name = "";
let description = ""; let description = "";
let tagsString = ""; let tagsString = "";
let nameInvalid = ""; let nameInvalid = "";
let componentDetailsExpanded = false;
let componentInfo; let componentInfo;
let modalElement let modalElement
let propsValidationErrors = []; let propsValidationErrors = [];
let editingComponentInstance;
let editingComponentInstancePropName="";
let editingComponentArrayIndex;
let editingComponentArrayPropName;
let editingComponentInstanceTitle;
let originalName=""; let originalName="";
let allComponents; let components;
let ignoreStore = false; let ignoreStore = false;
$: shortName = last(name.split("/")); $: shortName = last(name.split("/"));
@ -54,8 +47,7 @@ store.subscribe(s => {
description = component.description; description = component.description;
tagsString = join(", ")(component.tags); tagsString = join(", ")(component.tags);
componentInfo = s.currentComponentInfo; componentInfo = s.currentComponentInfo;
componentDetailsExpanded = s.currentComponentIsNew; components = s.components;
allComponents = s.allComponents;
}); });
const save = () => { const save = () => {
@ -73,12 +65,12 @@ const save = () => {
map(s => s.trim()) map(s => s.trim())
]); ]);
store.saveDerivedComponent(component); store.saveScreen(component);
ignoreStore = false; ignoreStore = false;
// now do the rename // now do the rename
if(name !== originalName) { if(name !== originalName) {
store.renameDerivedComponent(originalName, name); store.renameScreen(originalName, name);
} }
} }
@ -87,7 +79,7 @@ const deleteComponent = () => {
} }
const confirmDeleteComponent = () => { const confirmDeleteComponent = () => {
store.deleteDerivedComponent(component.name); store.deleteScreen(component.name);
hideDialog(); hideDialog();
} }
@ -99,7 +91,7 @@ const updateComponent = doChange => {
const newComponent = cloneDeep(component); const newComponent = cloneDeep(component);
doChange(newComponent); doChange(newComponent);
component = newComponent; component = newComponent;
componentInfo = getComponentInfo(allComponents, newComponent); componentInfo = getScreenInfo(components, newComponent);
} }
const onPropsChanged = newProps => { const onPropsChanged = newProps => {
@ -128,37 +120,6 @@ const showDialog = () => {
UIkit.modal(modalElement).show(); 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> </script>
<div class="root"> <div class="root">
@ -177,53 +138,15 @@ const componentInstancePropsChanged = (instanceProps) => {
</div> </div>
</div> </div>
{#if editingComponentInstance}
<div class="component-props-container"> <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} <PropsView onValidate={onPropsValidate}
{componentInfo} {componentInfo}
{onPropsChanged} {onPropsChanged} />
{onEditComponentProp}/>
</div> </div>
{/if}
</div> </div>
@ -263,20 +186,13 @@ const componentInstancePropsChanged = (instanceProps) => {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} border-style: solid;
border-width: 1px 0 0 0;
.padding { border-color: var(--slate);
padding: 1rem 1rem 0rem 1rem;
}
.info-text {
color: var(--secondary100);
font-size: .8rem !important;
font-weight: bold;
} }
.title { .title {
padding: 2rem 1rem 1rem 1rem; padding: 1rem;
display: grid; display: grid;
grid-template-columns: [name] 1fr [actions] auto; grid-template-columns: [name] 1fr [actions] auto;
color: var(--secondary100); color: var(--secondary100);
@ -293,15 +209,6 @@ const componentInstancePropsChanged = (instanceProps) => {
grid-column-start: actions; 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 { .component-props-container {
flex: 1 1 auto; flex: 1 1 auto;
overflow-y: auto; overflow-y: auto;

View File

@ -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>

View File

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

View File

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

View File

@ -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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,9 +14,9 @@ export const validatePage = (page, getComponent) => {
const errors = []; const errors = [];
const error = message => errors.push(message); const error = message => errors.push(message);
const noIndex = !page.index || !page.index._component; const noIndex = !page.index;
if(noIndex) { if(noIndex) {
error("Must choose a component for your index.html"); error("Page does not define an index member");
} }
if(!page.appBody if(!page.appBody
@ -25,14 +25,18 @@ export const validatePage = (page, getComponent) => {
error("App body must be set toa valid JSON file"); 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 const indexHtmlErrors = noIndex
? [] ? []
: pipe( : pipe(
recursivelyValidate(page.index, getComponent), [ recursivelyValidate(page.index, getComponent), [
map(e => `Index.html: ${e.error}`) map(e => `Index.html: ${e.error}`)
]); ]);
*/
return [...errors, ...indexHtmlErrors]; return errors;
} }
export const validatePages = (pages, getComponent) => { export const validatePages = (pages, getComponent) => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"]);
});
})

View File

@ -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"]);
});
*/
})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@ -1,49 +1,46 @@
main.svelte-15fmzor{height:100%;width:100%;font-family:"Roboto", Helvetica, Arial, sans-serif} 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-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} 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)} .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-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} 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} .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-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-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} .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}
.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)} .info-text.svelte-1gx0gkl{font-size:0.7rem;color:var(--secondary50)}
.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-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}
.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)} .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} .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)}
.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)}
.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)} .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-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} .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)} .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}
.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}
input.svelte-9fre0g{margin-right:7px} input.svelte-9fre0g{margin-right:7px}
.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}
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-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)} .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}
textarea.svelte-1kv2xk7{width:300px;height:200px} .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}
.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)} .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)} .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} .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 */ /*# sourceMappingURL=bundle.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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