Browse Source

48 builder frontend 2 (#70)

* Implement collapsing component hierarchy.

* Save screen when adding new components.

* Allow creation of nested child components.

* Fix level-based indentation of hierarchy.

* Rename updateComponentProps to setComponentProps

* Save layout information to the disk.

* Cleanup: switch to autosubscriptions to prevent memory leaks, remove unused imports.

* Remove unused css.

* Fix incorrect subscription.
gh-pages
pngwn 7 years ago
committed by Michael Shanks
parent
commit
7e16e23803
  1. 51
      packages/builder/src/builderStore/store.js
  2. 4
      packages/builder/src/common/Icons/ArrowDown.svelte
  3. 1
      packages/builder/src/common/Icons/index.js
  4. 10
      packages/builder/src/common/Inputs/InputGroup.svelte
  5. 76
      packages/builder/src/userInterface/ComponentPanel.svelte
  6. 30
      packages/builder/src/userInterface/ComponentSearch.svelte
  7. 116
      packages/builder/src/userInterface/ComponentSelector.svelte
  8. 149
      packages/builder/src/userInterface/ComponentsHierarchy.svelte
  9. 47
      packages/builder/src/userInterface/ComponentsHierarchyChildren.svelte
  10. 83
      packages/builder/src/userInterface/ComponentsList.svelte
  11. 16
      packages/builder/src/userInterface/ComponentsPaneSwitcher.svelte
  12. 79
      packages/builder/src/userInterface/CurrentItemPreview.svelte
  13. 197
      packages/builder/src/userInterface/EditComponentProps.svelte
  14. 128
      packages/builder/src/userInterface/EventListSelector.svelte
  15. 150
      packages/builder/src/userInterface/EventSelector.svelte
  16. 92
      packages/builder/src/userInterface/LayoutEditor.svelte
  17. 147
      packages/builder/src/userInterface/NewComponent.svelte
  18. 80
      packages/builder/src/userInterface/PageView.svelte
  19. 105
      packages/builder/src/userInterface/PagesList.svelte
  20. 83
      packages/builder/src/userInterface/PropControl.svelte
  21. 94
      packages/builder/src/userInterface/PropsView.svelte
  22. 180
      packages/builder/src/userInterface/SettingsView.svelte
  23. 235
      packages/builder/src/userInterface/StateBindingControl.svelte
  24. 269
      packages/builder/src/userInterface/UserInterfaceRoot.svelte

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

@ -4,7 +4,7 @@ import {
import { import {
filter, cloneDeep, sortBy, filter, cloneDeep, sortBy,
map, last, keys, concat, keyBy, map, last, keys, concat, keyBy,
find, isEmpty, reduce, values find, isEmpty, reduce, values, isEqual
} from "lodash/fp"; } from "lodash/fp";
import { import {
pipe, getNode, validate, pipe, getNode, validate,
@ -93,8 +93,8 @@ export const getStore = () => {
store.createGeneratedComponents = createGeneratedComponents(store); store.createGeneratedComponents = createGeneratedComponents(store);
store.addChildComponent = addChildComponent(store); store.addChildComponent = addChildComponent(store);
store.selectComponent = selectComponent(store); store.selectComponent = selectComponent(store);
store.updateComponentProp = updateComponentProp(store); store.setComponentProp = setComponentProp(store);
store.setComponentStyle = setComponentStyle(store);
return store; return store;
} }
@ -456,6 +456,10 @@ const _saveScreen = (store, s, screen) => {
return s; return s;
} }
const _save = (appname, screen, store, s) =>
api.post(`/_builder/api/${appname}/screen`, screen)
.then(() => savePackage(store, s));
const createScreen = store => (screenName, layoutComponentName) => { const createScreen = store => (screenName, layoutComponentName) => {
store.update(s => { store.update(s => {
const newComponentInfo = getNewComponentInfo( const newComponentInfo = getNewComponentInfo(
@ -597,8 +601,6 @@ const addComponentLibrary = store => async lib => {
return s; return s;
}) })
} }
const removeComponentLibrary = store => lib => { const removeComponentLibrary = store => lib => {
@ -701,17 +703,31 @@ const addChildComponent = store => component => {
const newComponent = getNewComponentInfo( const newComponent = getNewComponentInfo(
s.components, component); s.components, component);
const children = s.currentFrontEndItem.props._children; let children = s.currentComponentInfo.component ?
s.currentComponentInfo.component.props._children :
s.currentComponentInfo._children;
const component_definition = Object.assign( const component_definition = Object.assign(
cloneDeep(newComponent.fullProps), { cloneDeep(newComponent.fullProps), {
_component: component, _component: component,
_layout: {}
}) })
s.currentFrontEndItem.props._children = if (children) {
children ? if (s.currentComponentInfo.component) {
children.concat(component_definition) : s.currentComponentInfo.component.props._children = children.concat(component_definition);
[component_definition]; } else {
s.currentComponentInfo._children = children.concat(component_definition)
}
} else {
if (s.currentComponentInfo.component) {
s.currentComponentInfo.component.props._children = [component_definition];
} else {
s.currentComponentInfo._children = [component_definition]
}
}
_saveScreen(store, s, s.currentFrontEndItem);
return s; return s;
}) })
@ -725,7 +741,7 @@ const selectComponent = store => component => {
} }
const updateComponentProp = store => (name, value) => { const setComponentProp = store => (name, value) => {
store.update(s => { store.update(s => {
const current_component = s.currentComponentInfo; const current_component = s.currentComponentInfo;
s.currentComponentInfo[name] = value; s.currentComponentInfo[name] = value;
@ -733,5 +749,18 @@ const updateComponentProp = store => (name, value) => {
s.currentComponentInfo = current_component; s.currentComponentInfo = current_component;
return s; return s;
}) })
}
const setComponentStyle = store => (name, value) => {
store.update(s => {
if (!s.currentComponentInfo._layout) {
s.currentComponentInfo._layout = {};
}
s.currentComponentInfo._layout[name] = value;
// save without messing with the store
_save(s.appname, s.currentFrontEndItem, store, s)
return s;
})
} }

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

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

After

Width:  |  Height:  |  Size: 228 B

1
packages/builder/src/common/Icons/index.js

@ -3,3 +3,4 @@ export { default as PaintIcon } from './Paint.svelte';
export { default as TerminalIcon } from './Terminal.svelte'; export { default as TerminalIcon } from './Terminal.svelte';
export { default as InputIcon } from './Input.svelte'; export { default as InputIcon } from './Input.svelte';
export { default as ImageIcon } from './Image.svelte'; export { default as ImageIcon } from './Image.svelte';
export { default as ArrowDownIcon } from './ArrowDown.svelte';

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

@ -2,11 +2,19 @@
export let meta = []; export let meta = [];
export let size = ''; export let size = '';
export let values = []; export let values = [];
export let onStyleChanged = () => {};
let _values = values.map(v => v);
$: onStyleChanged(_values);
</script> </script>
<div class="inputs {size}"> <div class="inputs {size}">
{#each meta as { placeholder }, i} {#each meta as { placeholder }, i}
<input type="number" placeholder="{placeholder}" bind:value={values[i]}/> <input type="number"
placeholder="{placeholder}"
value={values[i]}
on:input={(e) => _values[i] = e.target.value} />
{/each} {/each}
</div> </div>

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

@ -1,58 +1,22 @@
<script> <script>
import PropsView from "./PropsView.svelte";
import PropsView from "./PropsView.svelte"; import { store } from "../builderStore";
import { store } from "../builderStore"; import IconButton from "../common/IconButton.svelte";
import { isRootComponent } from "./pagesParsing/searchComponents"; import { LayoutIcon, PaintIcon, TerminalIcon } from '../common/Icons/';
import IconButton from "../common/IconButton.svelte"; import CodeEditor from './CodeEditor.svelte';
import Textbox from "../common/Textbox.svelte"; import LayoutEditor from './LayoutEditor.svelte';
import { pipe } from "../common/core";
import { let current_view = 'props';
getScreenInfo
} from "./pagesParsing/createProps"; $: component = $store.currentComponentInfo;
import { LayoutIcon, PaintIcon, TerminalIcon } from '../common/Icons/'; $: originalName = component.name;
import CodeEditor from './CodeEditor.svelte'; $: name = component.name;
import LayoutEditor from './LayoutEditor.svelte'; $: description = component.description;
$: componentInfo = $store.currentComponentInfo;
import { $: components = $store.components;
cloneDeep,
join, const onPropChanged = store.setComponentProp;
split, const onStyleChanged = store.setComponentStyle;
map,
keys,
isUndefined,
last
} from "lodash/fp";
import { assign } from "lodash";
let component;
let name = "";
let description = "";
let tagsString = "";
let nameInvalid = "";
let componentInfo = {};
let modalElement
let propsValidationErrors = [];
let originalName="";
let components;
let ignoreStore = false;
// $: shortName = last(name.split("/"));
store.subscribe(s => {
if(ignoreStore) return;
component = s.currentComponentInfo;
if(!component) return;
originalName = component.name;
name = component.name;
description = component.description;
tagsString = join(", ")(component.tags);
componentInfo = s.currentComponentInfo;
components = s.components;
});
const onPropsChanged = store.updateComponentProp;
let current_view = 'props';
</script> </script>
<div class="root"> <div class="root">
@ -78,9 +42,9 @@ let current_view = 'props';
<div class="component-props-container"> <div class="component-props-container">
{#if current_view === 'props'} {#if current_view === 'props'}
<PropsView {componentInfo} {components} {onPropsChanged} /> <PropsView {componentInfo} {components} {onPropChanged} />
{:else if current_view === 'layout'} {:else if current_view === 'layout'}
<LayoutEditor /> <LayoutEditor {onStyleChanged} {componentInfo}/>
{:else} {:else}
<CodeEditor /> <CodeEditor />
{/if} {/if}

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

@ -1,31 +1,27 @@
<script> <script>
import { searchAllComponents } from "./pagesParsing/searchComponents"; import { searchAllComponents } from "./pagesParsing/searchComponents";
import { store } from "../builderStore"; import { store } from "../builderStore";
export let onComponentChosen = () => {}; export let onComponentChosen = () => {};
let components = []; let phrase = "";
let phrase = "";
store.subscribe(s => { components = $store.components;
components = s.components;
});
$: filteredComponents =
!phrase
? []
: searchAllComponents(components, phrase);
$: filteredComponents =
!phrase
? []
: searchAllComponents(components, phrase);
</script> </script>
<div class="root"> <div class="root">
<form class="uk-search uk-search-large"> <form class="uk-search uk-search-large">
<span uk-search-icon></span> <span uk-search-icon></span>
<input class="uk-search-input" <input class="uk-search-input"
type="search" type="search"
placeholder="Based on component..." placeholder="Based on component..."
bind:value={phrase}> bind:value={phrase}>
</form> </form>
@ -65,4 +61,4 @@ $: filteredComponents =
font-style: italic; font-style: italic;
} }
</style> </style>

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

@ -1,74 +1,66 @@
<script> <script>
import { import {
isRootComponent isRootComponent
} from "./pagesParsing/searchComponents" } from "./pagesParsing/searchComponents"
import { splitName } from "./pagesParsing/splitRootComponentName.js" import { splitName } from "./pagesParsing/splitRootComponentName.js"
import { store } from "../builderStore"; import { store } from "../builderStore";
import { import { find, sortBy } from "lodash/fp";
groupBy, keys, find, sortBy
} from "lodash/fp"; export let onComponentChosen;
import { pipe } from "../common/core"; export let onGeneratorChosen;
export let allowGenerators;
export let onComponentChosen;
export let onGeneratorChosen; let screens = [];
export let allowGenerators; let componentLibraries=[];
let screens=[]; const addRootComponent = (c, all, isGenerator) => {
let componentLibraries=[]; const { libName } = splitName(c.name);
let group = find(r => r.libName === libName)(all);
const addRootComponent = (c, all, isGenerator) => {
const { libName } = splitName(c.name); if(!group) {
let group = find(r => r.libName === libName)(all); group = {
libName,
if(!group) { components: [],
group = { generators: []
libName, };
components: [],
generators: [] all.push(group);
};
all.push(group);
}
if(isGenerator) {
group.generators.push(c)
} else {
group.components.push(c)
}
};
store.subscribe(s => {
const newComponentLibraries = [];
const newscreens = [];
for(let comp of sortBy(["name"])(s.components)) {
if(isRootComponent(comp)) {
addRootComponent(
comp,
newComponentLibraries,
false);
} else {
newscreens.push(comp);
} }
}
for(let generator of s.generators) { if(isGenerator) {
addRootComponent( group.generators.push(c)
generator, } else {
newComponentLibraries, group.components.push(c)
true); }
}
screens = sortBy(["name"])(newscreens); };
componentLibraries = newComponentLibraries;
});
$: {
const newComponentLibraries = [];
const newscreens = [];
for(let comp of sortBy(["name"])($store.components)) {
if(isRootComponent(comp)) {
addRootComponent(
comp,
newComponentLibraries,
false);
} else {
newscreens.push(comp);
}
}
for(let generator of $store.generators) {
addRootComponent(
generator,
newComponentLibraries,
true);
}
screens = sortBy(["name"])(newscreens);
componentLibraries = newComponentLibraries;
};
</script> </script>
{#each componentLibraries as lib} {#each componentLibraries as lib}

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

@ -1,90 +1,79 @@
<script> <script>
import ComponentsHierarchyChildren from './ComponentsHierarchyChildren.svelte'; import ComponentsHierarchyChildren from './ComponentsHierarchyChildren.svelte';
import { import {
last, last,
sortBy, sortBy,
filter, map,
map, trimCharsStart,
uniqWith, trimChars,
isEqual, join,
trimCharsStart, } from "lodash/fp";
trimChars,
join, import { pipe } from "../common/core";
includes import { store } from "../builderStore";
} from "lodash/fp"; import { ArrowDownIcon } from '../common/Icons/'
import { pipe } from "../common/core"; export let components = []
import getIcon from "../common/icon";
import { store } from "../builderStore"; const joinPath = join("/");
export let components = [] const normalizedName = name => pipe(name, [
trimCharsStart("./"),
const joinPath = join("/"); trimCharsStart("~/"),
trimCharsStart("../"),
const normalizedName = name => pipe(name, [ trimChars(" ")
trimCharsStart("./"), ]);
trimCharsStart("~/"),
trimCharsStart("../"),
trimChars(" ")
]);
const lastPartOfName = (c) =>
last(c.name ? c.name.split("/") : c._component.split("/"))
const expandFolder = folder => {
const expandedFolder = {...folder};
if(expandedFolder.isExpanded) {
expandedFolder.isExpanded = false;
expandedFolders = filter(f => f.name !== folder.name)(expandedFolders);
} else {
expandedFolder.isExpanded = true;
expandedFolders.push(folder.name);
}
const newFolders = [...subfolders];
newFolders.splice(
newFolders.indexOf(folder),
1,
expandedFolder);
subfolders = newFolders;
}
const isComponentSelected = (type, current,c) => const lastPartOfName = (c) =>
type==="screen" last(c.name ? c.name.split("/") : c._component.split("/"))
&& current
&& current.name === c.name
const isFolderSelected = (current, folder) => const isComponentSelected = (current, comp) =>
isInSubfolder(current, folder) current &&
current.component &&
comp.component &&
current.component.name === comp.component.name
const isFolderSelected = (current, folder) =>
isInSubfolder(current, folder)
$: _components =
pipe(components, [
map(c => ({component: c, title:lastPartOfName(c)})),
sortBy("title")
]);
$: _components = function select_component(screen, component) {
pipe(components, [ store.setCurrentScreen(screen);
map(c => ({component: c, title:lastPartOfName(c)})), store.selectComponent(component);
sortBy("title") }
]);
function select_component(screen, component) { const isScreenSelected = component =>
store.setCurrentScreen(screen); component.component &&
store.selectComponent(component); $store.currentFrontEndItem &&
} component.component.name === $store.currentFrontEndItem.name;
</script> </script>
<div class="root"> <div class="root">
{#each _components as component} {#each _components as component}
<div class="hierarchy-item component" <div class="hierarchy-item component"
class:selected={isComponentSelected($store.currentFrontEndType, $store.currentFrontEndItem, component.component)} class:selected={isComponentSelected($store.currentComponentInfo, component)}
on:click|stopPropagation={() => store.setCurrentScreen(component.component.name)}> on:click|stopPropagation={() => store.setCurrentScreen(component.component.name)}>
<span class="icon" style="transform: rotate({isScreenSelected(component) ? 0 : -90}deg);">
{#if component.component.props && component.component.props._children}
<ArrowDownIcon />
{/if}
</span>
<span class="title">{component.title}</span> <span class="title">{component.title}</span>
</div> </div>
{#if component.component.props && component.component.props._children}
{#if isScreenSelected(component) && component.component.props && component.component.props._children}
<ComponentsHierarchyChildren components={component.component.props._children} <ComponentsHierarchyChildren components={component.component.props._children}
currentComponent={$store.currentComponentInfo}
onSelect={child => select_component(component.component.name, child)} /> onSelect={child => select_component(component.component.name, child)} />
{/if} {/if}
{/each} {/each}
@ -102,9 +91,10 @@ function select_component(screen, component) {
.hierarchy-item { .hierarchy-item {
cursor: pointer; cursor: pointer;
padding: 11px 7px; padding: 11px 7px;
margin: 5px 0; margin: 5px 0;
border-radius: 5px; border-radius: 5px;
display: flex;
align-items: center;
} }
.hierarchy-item:hover { .hierarchy-item:hover {
@ -112,12 +102,6 @@ function select_component(screen, component) {
background: #fafafa; background: #fafafa;
} }
.currentfolder {
color: var(--secondary100);
}
.selected { .selected {
color: var(--button-text); color: var(--button-text);
background: var(--background-button)!important; background: var(--background-button)!important;
@ -127,5 +111,10 @@ function select_component(screen, component) {
margin-left: 10px; margin-left: 10px;
} }
.icon {
display: inline-block;
transition: 0.2s;
width: 24px;
height: 24px;
}
</style> </style>

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

@ -3,22 +3,55 @@
import { pipe } from "../common/core"; import { pipe } from "../common/core";
export let components = []; export let components = [];
export let currentComponent;
export let onSelect = () => {}; export let onSelect = () => {};
export let level = 0;
const capitalise = s => s.substring(0,1).toUpperCase() + s.substring(1); const capitalise = s => s.substring(0,1).toUpperCase() + s.substring(1);
const get_name = s => last(s.split('/')); const get_name = s => last(s.split('/'));
const get_capitalised_name = name => pipe(name, [get_name,capitalise]); const get_capitalised_name = name => pipe(name, [get_name,capitalise]);
</script> </script>
{#each components as component} <ul>
<ul> {#each components as component}
<li on:click|stopPropagation={() => onSelect(component)}> <li on:click|stopPropagation={() => onSelect(component)}>
{get_capitalised_name(component._component)} <span class="item"
class:selected={currentComponent === component}
style="padding-left: {level * 20 + 67}px">
{get_capitalised_name(component._component)}
</span>
{#if component._children} {#if component._children}
<svelte:self components={component._children}/> <svelte:self components={component._children}
{currentComponent}
{onSelect}
level={level + 1}/>
{/if} {/if}
</li> </li>
{/each}
</ul>
<style>
ul {
list-style: none;
padding-left: 0;
margin: 0;
}
.item {
display: block;
padding: 11px 67px;
border-radius: 3px;
}
.item:hover {
background: #fafafa;
cursor: pointer;
}
</ul> .selected {
{/each} color: var(--button-text);
background: var(--background-button)!important;
}
</style>

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

@ -1,52 +1,43 @@
<script> <script>
import { import { splitName } from "./pagesParsing/splitRootComponentName.js"
isRootComponent import { store } from "../builderStore";
} from "./pagesParsing/searchComponents" import { find, sortBy } from "lodash/fp";
import { splitName } from "./pagesParsing/splitRootComponentName.js" import { ImageIcon, InputIcon, LayoutIcon } from '../common/Icons/';
import { store } from "../builderStore";
import {
groupBy, keys, find, sortBy
} from "lodash/fp";
import { pipe } from "../common/core";
import { ImageIcon, InputIcon, LayoutIcon } from '../common/Icons/';
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) let componentLibraries = [];
let current_view = 'text';
}; const addRootComponent = (c, all) => {
const { libName } = splitName(c.name);
let group = find(r => r.libName === libName)(all);
const onComponentChosen = store.addChildComponent; if(!group) {
group = {
libName,
components: [],
generators: []
};
store.subscribe(s => { all.push(group);
}
const newComponentLibraries = []; group.components.push(c)
for(let comp of sortBy(["name"])(s.components)) { };
addRootComponent(
comp, const onComponentChosen = store.addChildComponent;
newComponentLibraries);
}
componentLibraries = newComponentLibraries; $: {
}); const newComponentLibraries = [];
let current_view = 'text'; for(let comp of sortBy(["name"])($store.components)) {
addRootComponent(
comp,
newComponentLibraries);
}
componentLibraries = newComponentLibraries;
}
</script> </script>
<div class="root"> <div class="root">
@ -115,13 +106,6 @@ let current_view = 'text';
min-height: 0px; min-height: 0px;
} }
.inner-header {
font-size: 0.9em;
font-weight: bold;
margin-top: 7px;
margin-bottom: 3px;
}
.component { .component {
padding: 0 15px; padding: 0 15px;
cursor: pointer; cursor: pointer;
@ -147,13 +131,6 @@ let current_view = 'text';
opacity: 0.6; opacity: 0.6;
} }
.component > .description {
font-size: 0.8em;
color: var(--secondary75);
display: inline-block;
margin-left: 10px;
}
ul { ul {
list-style: none; list-style: none;
display: flex; display: flex;

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

@ -1,16 +1,14 @@
<script> <script>
import ComponentPanel from "./ComponentPanel.svelte"; import ComponentPanel from "./ComponentPanel.svelte";
import ComponentsList from "./ComponentsList.svelte"; import ComponentsList from "./ComponentsList.svelte";
let selected="properties"; let selected="properties";
const isSelected = tab =>
selected === tab;
const selectTab = tab =>
selected = tab;
const isSelected = tab =>
selected === tab;
const selectTab = tab =>
selected = tab;
</script> </script>
<div class="root"> <div class="root">

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

@ -1,41 +1,25 @@
<script> <script>
import { store } from "../builderStore"; import { store } from "../builderStore";
import { makeLibraryUrl } from "../builderStore/loadComponentLibraries"; import { map, join } from "lodash/fp";
import { import { pipe } from "../common/core";
last, split, map, join import { buildPropsHierarchy } from "./pagesParsing/buildPropsHierarchy";
} from "lodash/fp";
import { pipe } from "../common/core";
import { splitName } from "./pagesParsing/splitRootComponentName"
import { afterUpdate } from 'svelte';
import { getRootComponent } from "./pagesParsing/getRootComponent";
import { buildPropsHierarchy } from "./pagesParsing/buildPropsHierarchy";
$: hasComponent = !!$store.currentFrontEndItem;
let hasComponent=false; $: stylesheetLinks = pipe($store.pages.stylesheets, [
let stylesheetLinks = "";
let appDefinition = {};
store.subscribe(s => {
hasComponent = !!s.currentFrontEndItem;
stylesheetLinks = pipe(s.pages.stylesheets, [
map(s => `<link rel="stylesheet" href="${s}"/>`), map(s => `<link rel="stylesheet" href="${s}"/>`),
join("\n") join("\n")
]); ]);
appDefinition = {
componentLibraries: s.loadLibraryUrls(), $: appDefinition = {
componentLibraries: $store.loadLibraryUrls(),
props: buildPropsHierarchy( props: buildPropsHierarchy(
s.components, $store.components,
s.screens, $store.screens,
s.currentFrontEndItem), $store.currentFrontEndItem),
hierarchy: s.hierarchy, hierarchy: $store.hierarchy,
appRootPath: "" appRootPath: ""
}; };
});
</script> </script>
@ -52,7 +36,6 @@ store.subscribe(s => {
window["##BUDIBASE_APPDEFINITION##"] = ${JSON.stringify(appDefinition)}; window["##BUDIBASE_APPDEFINITION##"] = ${JSON.stringify(appDefinition)};
import('/_builder/budibase-client.esm.mjs') import('/_builder/budibase-client.esm.mjs')
.then(module => { .then(module => {
console.log(module, window);
module.loadBudibase({ window, localStorage }); module.loadBudibase({ window, localStorage });
}) })
</script> </script>
@ -73,23 +56,21 @@ store.subscribe(s => {
<style> <style>
.component-container {
.component-container { grid-row-start: middle;
grid-row-start: middle; grid-column-start: middle;
grid-column-start: middle; position: relative;
position: relative; overflow: hidden;
overflow: hidden; padding-top: 56.25%;
padding-top: 56.25%; margin: auto;
margin: auto; }
}
.component-container iframe {
.component-container iframe { border: 0;
border: 0; height: 100%;
height: 100%; left: 0;
left: 0; position: absolute;
position: absolute; top: 0;
top: 0; width: 100%;
width: 100%; }
}
</style> </style>

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

@ -1,68 +1,31 @@
<script> <script>
import PropsView from "./PropsView.svelte";
import PropsView from "./PropsView.svelte"; import { store } from "../builderStore";
import { store } from "../builderStore"; import IconButton from "../common/IconButton.svelte";
import { isRootComponent } from "./pagesParsing/searchComponents"; import Textbox from "../common/Textbox.svelte";
import IconButton from "../common/IconButton.svelte"; import Button from "../common/Button.svelte";
import Textbox from "../common/Textbox.svelte"; import { LayoutIcon, PaintIcon, TerminalIcon } from '../common/Icons/';
import UIkit from "uikit";
import { pipe } from "../common/core"; import {
import { cloneDeep,
getScreenInfo join,
} from "./pagesParsing/createProps"; split,
import Button from "../common/Button.svelte"; last
import ButtonGroup from "../common/ButtonGroup.svelte"; } from "lodash/fp";
import { LayoutIcon, PaintIcon, TerminalIcon } from '../common/Icons/'; import { assign } from "lodash";
import { $: component = $store.currentFrontEndItem;
cloneDeep, $: componentInfo = $store.currentComponentInfo;
join, $: components = $store.components;
split,
map,
keys, const updateComponent = doChange =>
isUndefined, doChange(cloneDeep(component));
last
} from "lodash/fp"; const onPropsChanged = newProps => {
import { assign } from "lodash"; updateComponent(newComponent =>
assign(newComponent.props, newProps));
let component; }
let name = "";
let description = "";
let tagsString = "";
let nameInvalid = "";
let componentInfo;
let modalElement
let propsValidationErrors = [];
let originalName="";
let components;
let ignoreStore = false;
$: shortName = last(name.split("/"));
store.subscribe(s => {
if(ignoreStore) return;
component = s.currentFrontEndItem;
if(!component) return;
originalName = component.name;
name = component.name;
description = component.description;
tagsString = join(", ")(component.tags);
componentInfo = s.currentComponentInfo;
components = s.components;
});
const updateComponent = doChange => {
const newComponent = cloneDeep(component);
doChange(newComponent);
component = newComponent;
componentInfo = getScreenInfo(components, newComponent);
}
const onPropsChanged = newProps => {
updateComponent(newComponent =>
assign(newComponent.props, newProps));
}
</script> </script>
<div class="root"> <div class="root">
@ -74,74 +37,54 @@ const onPropsChanged = newProps => {
</ul> </ul>
<div class="component-props-container"> <div class="component-props-container">
<PropsView <PropsView
{componentInfo} {componentInfo}
{onPropsChanged} /> {onPropsChanged} />
</div> </div>
</div> </div>
<style> <style>
.root {
.root { height: 100%;
height: 100%; display: flex;
display: flex; flex-direction: column;
flex-direction: column;
}
}
.title > div:nth-child(1) {
.title { grid-column-start: name;
padding: 1rem; color: var(--secondary100);
display: grid; }
grid-template-columns: [name] 1fr [actions] auto;
color: var(--secondary100); .title > div:nth-child(2) {
font-size: .9rem; grid-column-start: actions;
font-weight: bold; }
}
.component-props-container {
.title > div:nth-child(1) { flex: 1 1 auto;
grid-column-start: name; overflow-y: auto;
color: var(--secondary100); }
}
ul {
.title > div:nth-child(2) { list-style: none;
grid-column-start: actions; display: flex;
} padding: 0;
}
.component-props-container {
flex: 1 1 auto; li {
overflow-y: auto; margin-right: 20px;
} background: none;
border-radius: 5px;
ul { width: 45px;
list-style: none; height: 45px;
display: flex; }
padding: 0;
} li button {
width: 100%;
li { height: 100%;
margin-right: 20px; background: none;
background: none; border: none;
border-radius: 5px; border-radius: 5px;
width: 45px; padding: 12px;
height: 45px; }
}
li button {
width: 100%;
height: 100%;
background: none;
border: none;
border-radius: 5px;
padding: 12px;
}
.selected {
background: lightblue;
}
</style> </style>

128
packages/builder/src/userInterface/EventListSelector.svelte

@ -1,45 +1,39 @@
<script> <script>
import IconButton from "../common/IconButton.svelte"; import IconButton from "../common/IconButton.svelte";
import EventSelector from "./EventSelector.svelte"; import EventSelector from "./EventSelector.svelte";
import { import {
filter filter
} from "lodash/fp"; } from "lodash/fp";
import {EVENT_TYPE_MEMBER_NAME} from "../common/eventHandlers"; import {EVENT_TYPE_MEMBER_NAME} from "../common/eventHandlers";
export let parentProps; export let parentProps;
export let propDef; export let propDef;
export let onValueChanged; export let onValueChanged;
export let onValidate = () => {};
$: events = parentProps[propDef.____name];
let events = [];
let elementErrors = {}; const addHandler = () => {
const newHandler = {parameters:{}};
$: { newHandler[EVENT_TYPE_MEMBER_NAME] = "";
events = parentProps[propDef.____name]; events = [...events, newHandler];
} onValueChanged(events);
}
const addHandler = () => {
const newHandler = {parameters:{}}; const onEventHandlerChanged = (oldEvent) => (newEvent) => {
newHandler[EVENT_TYPE_MEMBER_NAME] = ""; const indexOfOldEvent = events.indexOf(oldEvent);
events = [...events, newHandler]; const newEvents = [...events];
onValueChanged(events); newEvents.splice(
} events.indexOf(oldEvent),
1,
const onEventHandlerChanged = (oldEvent) => (newEvent) => { newEvent);
const indexOfOldEvent = events.indexOf(oldEvent); events = newEvents;
const newEvents = [...events]; onValueChanged(events);
newEvents.splice( }
events.indexOf(oldEvent),
1, const removeHandler = (index) => () => {
newEvent); events = filter(e => e !== events[index])(events);
events = newEvents; onValueChanged(events);
onValueChanged(events); }
}
const removeHandler = (index) => () => {
events = filter(e => e !== events[index])(events);
onValueChanged(events);
}
</script> </script>
@ -67,32 +61,28 @@ const removeHandler = (index) => () => {
<style> <style>
.addelement-container {
.addelement-container { cursor: pointer;
cursor: pointer; padding: 3px 0px;
padding: 3px 0px; text-align: center;
text-align: center; }
}
.addelement-container:hover {
background-color: var(--primary25);
margin-top: 5px;
.addelement-container:hover { }
background-color: var(--primary25);
margin-top: 5px;
} .control-container {
padding-left: 3px;
background: var(--secondary10);
.control-container { }
padding-left: 3px;
background: var(--secondary10); .separator {
} width: 60%;
margin: 10px auto;
.separator { border-style:solid;
width: 60%; border-width: 1px 0 0 0;
margin: 10px auto; border-color: var(--primary25);
border-style:solid; }
border-width: 1px 0 0 0;
border-color: var(--primary25);
}
</style> </style>

150
packages/builder/src/userInterface/EventSelector.svelte

@ -1,71 +1,67 @@
<script> <script>
import IconButton from "../common/IconButton.svelte"; import IconButton from "../common/IconButton.svelte";
import StateBindingControl from "./StateBindingControl.svelte"; import StateBindingControl from "./StateBindingControl.svelte";
import { import {
find, map, keys, reduce, keyBy find, map, keys, reduce, keyBy
} from "lodash/fp"; } from "lodash/fp";
import { pipe, userWithFullAccess } from "../common/core"; import { pipe, userWithFullAccess } from "../common/core";
import { EVENT_TYPE_MEMBER_NAME, allHandlers } from "../common/eventHandlers"; import { EVENT_TYPE_MEMBER_NAME, allHandlers } from "../common/eventHandlers";
import { store } from "../builderStore"; import { store } from "../builderStore";
export let event; export let event;
export let onChanged; export let onChanged;
export let onRemoved; export let onRemoved;
let events; let eventType;
let eventType; let parameters = [];
let parameters = [];
store.subscribe(s => { $: events = allHandlers(
events = allHandlers( {hierarchy: $store.hierarchy},
{hierarchy: s.hierarchy},
userWithFullAccess({ userWithFullAccess({
hierarchy: s.hierarchy, hierarchy: s.hierarchy,
actions: keyBy("name")(s.actions) actions: keyBy("name")($store.actions)
}) })
); );
});
$: {
if(event) {
eventType = event[EVENT_TYPE_MEMBER_NAME];
parameters = pipe(event.parameters, [
keys,
map(k => ({name:k, value:event.parameters[k]}))
]);
} else {
eventType = "";
parameters = [];
}
}
const eventChanged = (type, parameters) => { $: if(event) {
const paramsAsObject = reduce( eventType = event[EVENT_TYPE_MEMBER_NAME];
(obj, p) => { parameters = pipe(event.parameters, [
obj[p.name] = p.value; keys,
return obj; map(k => ({name:k, value:event.parameters[k]}))
]);
} else {
eventType = "";
parameters = [];
} }
, {}
)(parameters)
const ev = {}; const eventChanged = (type, parameters) => {
ev[EVENT_TYPE_MEMBER_NAME]=type; const paramsAsObject = reduce(
ev.parameters = paramsAsObject; (obj, p) => {
obj[p.name] = p.value;
return obj;
}
, {}
)(parameters)
const ev = {};
ev[EVENT_TYPE_MEMBER_NAME]=type;
ev.parameters = paramsAsObject;
onChanged(ev); onChanged(ev);
} }
const eventTypeChanged = (ev) => { const eventTypeChanged = (ev) => {
const eType = find(e => e.name === ev.target.value)(events); const eType = find(e => e.name === ev.target.value)(events);
const emptyParameters = map(p => ({name:p, value:""}))(eType.parameters); const emptyParameters = map(p => ({name:p, value:""}))(eType.parameters);
eventChanged(eType.name, emptyParameters); eventChanged(eType.name, emptyParameters);
} }
const onParameterChanged = index => val => { const onParameterChanged = index => val => {
const newparameters = [...parameters]; const newparameters = [...parameters];
newparameters[index].value = val; newparameters[index].value = val;
eventChanged(eventType, newparameters); eventChanged(eventType, newparameters);
} }
</script> </script>
@ -77,35 +73,33 @@ const onParameterChanged = index => val => {
{/each} {/each}
</select> </select>
<IconButton icon="trash" <IconButton icon="trash"
size="12" size="12"
on:click={onRemoved}/> on:click={onRemoved}/>
</div> </div>
{#if parameters} {#if parameters}
{#each parameters as p, index} {#each parameters as p, index}
<div> <div>
{p.name} {p.name}
</div> </div>
<StateBindingControl onChanged={onParameterChanged(index)} <StateBindingControl onChanged={onParameterChanged(index)}
value={p.value} /> value={p.value} />
{/each} {/each}
{/if} {/if}
<style> <style>
.type-selector-container {
display: flex;
}
.type-selector-container { .type-selector {
display: flex; border-color: var(--primary50);
} border-radius: 2px;
width: 50px;
.type-selector { flex: 1 0 auto;
border-color: var(--primary50); }
border-radius: 2px; </style>
width: 50px;
flex: 1 0 auto;
}
</style>

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

@ -1,13 +1,8 @@
<script> <script>
import InputGroup from '../common/Inputs/InputGroup.svelte'; import InputGroup from '../common/Inputs/InputGroup.svelte';
let grid_values = ['', '', '', '']; export let onStyleChanged = () => {};
let column_values = ['', '']; export let componentInfo;
let row_values = ['', ''];
let gap_values = [''];
let margin_values = ['', '', '', ''];
let padding_values = ['', '', '', ''];
let zindex_values = [''];
const tbrl = [ const tbrl = [
{ placeholder: 'T' }, { placeholder: 'T' },
@ -22,6 +17,27 @@
] ]
const single = [{ placeholder: '' }]; const single = [{ placeholder: '' }];
$: layout = componentInfo._layout;
$: positions = {
gridarea: ['Grid Area', tbrl, 'small'],
column: ['Column', se],
row: ['Row', se],
gap: ['Gap', single],
};
$: spacing = {
margin: ['Margin', tbrl, 'small'],
padding: ['Padding', tbrl, 'small']
};
$: zindex = {
zindex: ['Z-Index', single]
}
const newValue = n => Array(n).fill('');
</script> </script>
@ -30,48 +46,41 @@
<h4>Positioning</h4> <h4>Positioning</h4>
<div class="layout-pos"> <div class="layout-pos">
<div class="grid"> {#each Object.entries(positions) as [key, [name, meta, size]]}
<h5>Grid Area:</h5> <div class="grid">
<InputGroup meta={tbrl} bind:values={grid_values} size="small"/> <h5>Grid Area:</h5>
</div> <InputGroup onStyleChanged={_value => onStyleChanged(key, _value)}
values={layout[key] || newValue(meta.length)}
<div class="grid"> {meta}
<h5>Column:</h5> {size} />
<InputGroup meta={se} bind:values={column_values} /> </div>
</div> {/each}
<div class="grid">
<h5>Row:</h5>
<InputGroup meta={se} bind:values={row_values} />
</div>
<div class="grid">
<h5>Gap:</h5>
<InputGroup meta={single} bind:values={gap_values} />
</div>
</div> </div>
<h4>Spacing</h4> <h4>Spacing</h4>
<div class="layout-spacing"> <div class="layout-spacing">
<div class="grid"> {#each Object.entries(spacing) as [key, [name, meta, size]]}
<h5>Margin:</h5> <div class="grid">
<InputGroup meta={tbrl} bind:values={margin_values} size="small"/> <h5>Grid Area:</h5>
</div> <InputGroup onStyleChanged={_value => onStyleChanged(key, _value)}
values={layout[key] || newValue(meta.length)}
<div class="grid"> {meta}
<h5>Padding:</h5> {size} />
<InputGroup meta={tbrl} bind:values={padding_values} size="small"/> </div>
</div> {/each}
</div> </div>
<h4>Z-Index</h4> <h4>Z-Index</h4>
<div class="layout-layer"> <div class="layout-layer">
<div class="grid"> {#each Object.entries(zindex) as [key, [name, meta, size]]}
<h5>Z-Index:</h5> <div class="grid">
<InputGroup meta={single} bind:values={zindex_values}/> <h5>Grid Area:</h5>
</div> <InputGroup onStyleChanged={_value => onStyleChanged(key, _value)}
values={layout[key] || newValue(meta.length)}
{meta}
{size} />
</div>
{/each}
</div> </div>
<style> <style>
@ -112,5 +121,4 @@
.grid { .grid {
grid-template-columns: 70px 1fr; grid-template-columns: 70px 1fr;
} }
</style> </style>

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

@ -1,65 +1,64 @@
<script> <script>
import ComponentSelector from "./ComponentSelector.svelte";
import ComponentSelector from "./ComponentSelector.svelte"; import { store } from "../builderStore";
import { store } from "../builderStore"; import PropsView from "./PropsView.svelte";
import PropsView from "./PropsView.svelte"; import Textbox from "../common/Textbox.svelte";
import Textbox from "../common/Textbox.svelte"; import Button from "../common/Button.svelte";
import Button from "../common/Button.svelte"; import ButtonGroup from "../common/ButtonGroup.svelte";
import ButtonGroup from "../common/ButtonGroup.svelte"; import { pipe } from "../common/core";
import { pipe } from "../common/core"; import UIkit from "uikit";
import UIkit from "uikit"; import { isRootComponent } from "./pagesParsing/searchComponents";
import { isRootComponent } from "./pagesParsing/searchComponents"; import { splitName } from "./pagesParsing/splitRootComponentName.js"
import { splitName } from "./pagesParsing/splitRootComponentName.js"
import {
import { find, filter, some, map, includes
find, filter, some, map, includes } from "lodash/fp";
} from "lodash/fp"; import { assign } from "lodash";
import { assign } from "lodash";
export const show = () => {
export const show = () => { UIkit.modal(componentSelectorModal).show();
UIkit.modal(componentSelectorModal).show(); }
}
let componentSelectorModal;
let componentSelectorModal; let layoutComponents;
let layoutComponents; let layoutComponent;
let layoutComponent; let screens;
let screens; let name="";
let name=""; let saveAttempted=false;
let saveAttempted=false;
store.subscribe(s => {
store.subscribe(s => { layoutComponents = pipe(s.components, [
layoutComponents = pipe(s.components, [ filter(c => c.container),
filter(c => c.container), map(c => ({name:c.name, ...splitName(c.name)}))
map(c => ({name:c.name, ...splitName(c.name)})) ]);
]);
layoutComponent = layoutComponent
layoutComponent = layoutComponent ? find(c => c.name === layoutComponent.name)(layoutComponents)
? find(c => c.name === layoutComponent.name)(layoutComponents) : layoutComponents[0];
: layoutComponents[0];
screens = s.screens;
screens = s.screens; });
});
const save = () => {
const save = () => { saveAttempted = true;
saveAttempted = true;
const isValid = name.length > 0
const isValid = name.length > 0 && !screenNameExists(name)
&& !screenNameExists(name) && layoutComponent;
&& layoutComponent;
if(!isValid) return;
if(!isValid) return;
store.createScreen(name, layoutComponent.name);
store.createScreen(name, layoutComponent.name); UIkit.modal(componentSelectorModal).hide();
UIkit.modal(componentSelectorModal).hide(); }
}
const cancel = () => {
const cancel = () => { UIkit.modal(componentSelectorModal).hide();
UIkit.modal(componentSelectorModal).hide(); }
}
const screenNameExists = (name) =>
const screenNameExists = (name) => some(s => s.name.toLowerCase() === name.toLowerCase())(screens)
some(s => s.name.toLowerCase() === name.toLowerCase())(screens)
</script> </script>
@ -106,27 +105,7 @@ const screenNameExists = (name) =>
<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>

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

@ -1,44 +1,36 @@
<script> <script>
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 Button from "../common/Button.svelte"; import Button from "../common/Button.svelte";
import { store } from "../builderStore"; import { store } from "../builderStore";
import { isRootComponent } from "./pagesParsing/searchComponents"; import { isRootComponent } from "./pagesParsing/searchComponents";
import { pipe } from "../common/core"; import { pipe } from "../common/core";
import { import {
filter, find, concat filter, find, concat
} from "lodash/fp"; } from "lodash/fp";
let entryComponent; const notSeletedComponent = {name:"(none selected)"};
let title = "";
let components = []; $: page = $store.pages[$store.currentPageName];
let page={}; $: title = page.index.title;
const notSeletedComponent = {name:"(none selected)"}; $: components = pipe($store.components, [
filter(store => !isRootComponent($store)),
store.subscribe(s => {
page = s.pages[s.currentPageName];
if(!page) return;
title = page.index.title;
components = pipe(s.components, [
filter(s => !isRootComponent(s)),
concat([notSeletedComponent]) concat([notSeletedComponent])
]); ]);
entryComponent = find(c => c.name === page.appBody)(components); $: entryComponent = find(c => c.name === page.appBody)(components) || notSeletedComponent;
if(!entryComponent) entryComponent = notSeletedComponent;
});
const save = () => {
const save = () => { if(!title || !entryComponent || entryComponent === notSeletedComponent) return;
if(!title || !entryComponent || entryComponent === notSeletedComponent) return; const page = {
const page = { index: {
index: { title
title },
}, appBody: entryComponent.name,
appBody: entryComponent.name, }
store.savePage(page);
} }
store.savePage(page);
}
</script> </script>
<div class="root"> <div class="root">
@ -61,11 +53,11 @@ const save = () => {
</div> </div>
<style> <style>
.root { .root {
padding: 15px; padding: 15px;
} }
.help-text { .help-text {
color: var(--slate); color: var(--slate);
font-size: 10pt; font-size: 10pt;
} }
</style> </style>

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

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

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

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

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

@ -1,44 +1,32 @@
<script> <script>
import { some, includes, filter } from "lodash/fp";
import Textbox from "../common/Textbox.svelte";
import Dropdown from "../common/Dropdown.svelte";
import PropControl from "./PropControl.svelte";
import IconButton from "../common/IconButton.svelte";
import { export let componentInfo;
keys, map, some, includes, export let onPropChanged = () => {};
cloneDeep, isEqual, sortBy, export let components;
filter, difference
} from "lodash/fp";
import { pipe } from "../common/core";
import { getInstanceProps } from "./pagesParsing/createProps";
import Checkbox from "../common/Checkbox.svelte";
import Textbox from "../common/Textbox.svelte";
import Dropdown from "../common/Dropdown.svelte";
import PropControl from "./PropControl.svelte";
import IconButton from "../common/IconButton.svelte";
export let componentInfo;
export let instanceProps = null;
export let onPropsChanged = () => {};
export let components;
let errors = [];
let props = {};
let propsDefinitions = [];
let isInstance = false;
const props_to_ignore = ['_component','_children', '_layout'];
$: propDefs = componentInfo && Object.entries(componentInfo).filter(([name])=> !props_to_ignore.includes(name));
function find_type(prop_name) {
if(!componentInfo._component) return;
return components.find(({name}) => name === componentInfo._component).props[prop_name];
}
let setProp = (name, value) => {
onPropsChanged(name, value);
}
const fieldHasError = (propName) =>
some(e => e.propName === propName)(errors);
let errors = [];
let props = {};
const props_to_ignore = ['_component','_children', '_layout'];
$: propDefs = componentInfo && Object.entries(componentInfo).filter(([name])=> !props_to_ignore.includes(name));
function find_type(prop_name) {
if(!componentInfo._component) return;
return components.find(({name}) => name === componentInfo._component).props[prop_name];
}
let setProp = (name, value) => {
onPropChanged(name, value);
}
const fieldHasError = (propName) =>
some(e => e.propName === propName)(errors);
</script> </script>
<div class="root"> <div class="root">
@ -65,20 +53,18 @@ const fieldHasError = (propName) =>
<style> <style>
.root {
.root { font-size:10pt;
font-size:10pt; width: 100%;
width: 100%; }
}
.form-root {
.form-root { display: flex;
display: flex; flex-wrap: wrap;
flex-wrap: wrap; }
}
.prop-container {
.prop-container { flex: 1 1 auto;
flex: 1 1 auto; min-width: 250px;
min-width: 250px; }
}
</style> </style>

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

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

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

@ -1,145 +1,130 @@
<script> <script>
import { import IconButton from "../common/IconButton.svelte";
isString import {
} from "lodash/fp"; isBinding, getBinding, setBinding
import IconButton from "../common/IconButton.svelte"; } from "../common/binding";
import {
isBinding, getBinding, setBinding export let value="";
} from "../common/binding"; export let onChanged= () => {};
export let type="";
export let value=""; export let options=[];
export let onChanged= () => {};
export let type=""; let isBound=false;
export let options=[]; let bindingPath="";
let bindingFallbackValue="";
let isBound=false; let bindingSource="store";
let bindingPath=""; let isExpanded = false;
let bindingFallbackValue=""; let forceIsBound = false;
let bindingSource="store"; let canOnlyBind = false;
let isExpanded = false;
let forceIsBound = false; $: {
let canOnlyBind = false; canOnlyBind = type === "state";
if(!forceIsBound && canOnlyBind)
$: { forceIsBound = true;
canOnlyBind = type === "state";
if(!forceIsBound && canOnlyBind) isBound= forceIsBound || isBinding(value);
forceIsBound = true;
if(isBound) {
isBound= forceIsBound || isBinding(value); const binding = getBinding(value);
bindingPath= binding.path;
if(isBound) { bindingFallbackValue= binding.fallback;
const binding = getBinding(value); bindingSource = binding.source || "store";
bindingPath= binding.path; } else {
bindingFallbackValue= binding.fallback; bindingPath="";
bindingSource = binding.source || "store"; bindingFallbackValue="";
} else { bindingSource="store";
bindingPath=""; }
bindingFallbackValue="";
bindingSource="store";
} }
}
const clearBinding = () => { const clearBinding = () => {
forceIsBound = false; forceIsBound = false;
onChanged(""); onChanged("");
}
const bind = (path, fallback, source) => {
if(!path) {
clearBinding("");
return;
} }
const binding = setBinding({path, fallback, source});
onChanged(binding);
}
const setBindingPath = ev => { const bind = (path, fallback, source) => {
forceIsBound = canOnlyBind; if(!path) {
bind(ev.target.value, bindingFallbackValue, bindingSource) clearBinding("");
} return;
}
const binding = setBinding({path, fallback, source});
onChanged(binding);
}
const setBindingFallback = ev => { const setBindingPath = ev => {
bind(bindingPath, ev.target.value, bindingSource); forceIsBound = canOnlyBind;
} bind(ev.target.value, bindingFallbackValue, bindingSource)
}
const setBindingSource = ev => { const setBindingFallback = ev => {
bind(bindingPath, bindingFallbackValue, ev.target.value); bind(bindingPath, ev.target.value, bindingSource);
} }
// const makeBinding = () => { const setBindingSource = ev => {
// forceIsBound=true; bind(bindingPath, bindingFallbackValue, ev.target.value);
// isExpanded=true; }
// }
</script> </script>
{#if isBound} {#if isBound}
<div>
<div class="bound-header">
<div>{isExpanded ? "" : bindingPath}</div>
<IconButton icon={isExpanded ? "chevron-up" : "chevron-down"}
size="12"
on:click={() => isExpanded=!isExpanded}/>
{#if !canOnlyBind}
<IconButton icon="trash"
size="12"
on:click={clearBinding}/>
{/if}
</div>
{#if isExpanded}
<div> <div>
<div class="binding-prop-label">Binding Path</div> <div class="bound-header">
<input class="uk-input uk-form-small" <div>{isExpanded ? "" : bindingPath}</div>
value={bindingPath} <IconButton icon={isExpanded ? "chevron-up" : "chevron-down"}
on:change={setBindingPath} > size="12"
<div class="binding-prop-label">Fallback Value</div> on:click={() => isExpanded=!isExpanded}/>
<input class="uk-input uk-form-small" {#if !canOnlyBind}
value={bindingFallbackValue} <IconButton icon="trash"
on:change={setBindingFallback} > size="12"
<div class="binding-prop-label">Binding Source</div> on:click={clearBinding}/>
<select class="uk-select uk-form-small" {/if}
value={bindingSource} </div>
on:change={setBindingSource}> {#if isExpanded}
<div>
<option>store</option> <div class="binding-prop-label">Binding Path</div>
<option>context</option> <input class="uk-input uk-form-small"
value={bindingPath}
</select> on:change={setBindingPath} >
</div> <div class="binding-prop-label">Fallback Value</div>
{/if} <input class="uk-input uk-form-small"
value={bindingFallbackValue}
on:change={setBindingFallback} >
<div class="binding-prop-label">Binding Source</div>
<select class="uk-select uk-form-small"
value={bindingSource}
on:change={setBindingSource}>
<option>store</option>
<option>context</option>
</select>
</div>
{/if}
</div> </div>
{:else} {:else}
<div class="unbound-container"> <div class="unbound-container">
{#if type === "bool"} {#if type === "bool"}
<div>
<IconButton icon={value == true ? "check-square" : "square"}
size="19"
on:click={() => onChanged(!value)} />
</div>
{:else if type === "options"}
<select class="uk-select uk-form-small"
value={value}
on:change={ev => onChanged(ev.target.value)}>
{#each options as option}
<option value={option}>{option}</option>
{/each}
</select>
{:else}
<input on:change={ev => onChanged(ev.target.value)}
bind:value={value}
style="flex: 1 0 auto;" />
{/if}
<div>
<IconButton icon={value == true ? "check-square" : "square"}
size="19"
on:click={() => onChanged(!value)} />
</div> </div>
{:else if type === "options"}
<select class="uk-select uk-form-small"
value={value}
on:change={ev => onChanged(ev.target.value)}>
{#each options as option}
<option value={option}>{option}</option>
{/each}
</select>
{:else}
<input on:change={ev => onChanged(ev.target.value)}
bind:value={value}
style="flex: 1 0 auto;" />
{/if}
</div>
{/if} {/if}

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

@ -1,28 +1,25 @@
<script> <script>
import ComponentsHierarchy from "./ComponentsHierarchy.svelte";
import ComponentsHierarchy from "./ComponentsHierarchy.svelte"; import PagesList from "./PagesList.svelte"
import PagesList from "./PagesList.svelte" import { store } from "../builderStore";
import { store } from "../builderStore"; import IconButton from "../common/IconButton.svelte";
import getIcon from "../common/icon"; import Modal from "../common/Modal.svelte";
import { isComponent } from "./pagesParsing/searchComponents"; import NewComponent from "./NewComponent.svelte";
import IconButton from "../common/IconButton.svelte"; import CurrentItemPreview from "./CurrentItemPreview.svelte";
import Modal from "../common/Modal.svelte"; import SettingsView from "./SettingsView.svelte";
import NewComponent from "./NewComponent.svelte"; import PageView from "./PageView.svelte";
import CurrentItemPreview from "./CurrentItemPreview.svelte"; import ComponentsPaneSwitcher from "./ComponentsPaneSwitcher.svelte";
import SettingsView from "./SettingsView.svelte";
import PageView from "./PageView.svelte"; let newComponentPicker;
import ComponentsPaneSwitcher from "./ComponentsPaneSwitcher.svelte";
const newComponent = () => {
let newComponentPicker; newComponentPicker.show();
const newComponent = () => { }
newComponentPicker.show();
} let settingsView;
const settings = () => {
let settingsView; settingsView.show();
const settings = () => { }
settingsView.show();
}
</script> </script>
<div class="root"> <div class="root">
@ -44,11 +41,6 @@ const settings = () => {
<span class="components-nav-header">Screens</span> <span class="components-nav-header">Screens</span>
<div> <div>
<!-- <IconButton icon="settings"
size="14px"
on:click={settings}/> -->
<!-- <IconButton icon="plus"
on:click={newComponent}/> -->
<button on:click={newComponent}>+</button> <button on:click={newComponent}>+</button>
</div> </div>
</div> </div>
@ -61,16 +53,16 @@ const settings = () => {
<div class="preview-pane"> <div class="preview-pane">
{#if $store.currentFrontEndType === "screen"} {#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 === "screen"} {#if $store.currentFrontEndType === "screen"}
<div class="components-pane"> <div class="components-pane">
<ComponentsPaneSwitcher /> <ComponentsPaneSwitcher />
</div> </div>
{/if} {/if}
</div> </div>
@ -81,109 +73,108 @@ const settings = () => {
<style> <style>
button { button {
cursor: pointer; cursor: pointer;
outline: none; outline: none;
border: none; border: none;
border-radius: 5px; border-radius: 5px;
background: var(--background-button); background: var(--background-button);
width: 1.8rem; width: 1.8rem;
height: 1.8rem; height: 1.8rem;
padding-bottom: 10px; padding-bottom: 10px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-size: 1.2rem; font-size: 1.2rem;
font-weight: 700; font-weight: 700;
color: var(--button-text); color: var(--button-text);
} }
.root { .root {
display: grid; display: grid;
grid-template-columns: 290px 1fr 300px; grid-template-columns: 290px 1fr 300px;
height: 100%; height: 100%;
width: 100%; width: 100%;
background: #fafafa; background: #fafafa;
} }
.ui-nav { .ui-nav {
grid-column: 1; grid-column: 1;
background-color: var(--secondary5); background-color: var(--secondary5);
height: 100%; height: 100%;
padding: 0 1.5rem 0rem 1.5rem padding: 0 1.5rem 0rem 1.5rem
} }
.preview-pane { .preview-pane {
grid-column: 2; grid-column: 2;
margin: 80px 60px; margin: 80px 60px;
background: #fff; background: #fff;
border-radius: 5px; border-radius: 5px;
box-shadow: 0 0px 6px rgba(0,0,0,0.05) box-shadow: 0 0px 6px rgba(0,0,0,0.05)
} }
.components-pane { .components-pane {
grid-column: 3; grid-column: 3;
background-color: var(--secondary5); background-color: var(--secondary5);
min-height: 0px; min-height: 0px;
overflow-y: hidden; overflow-y: hidden;
} }
.components-nav-header { .components-nav-header {
font-size: 0.75rem; font-size: 0.75rem;
color: #999; color: #999;
text-transform: uppercase; text-transform: uppercase;
} }
.nav-group-header { .nav-group-header {
font-size: .9rem; font-size: .9rem;
padding-left: 1rem; padding-left: 1rem;
} }
.nav-items-container { .nav-items-container {
padding: 1rem 0rem 0rem 0rem; padding: 1rem 0rem 0rem 0rem;
} }
.nav-group-header { .nav-group-header {
display: flex; display: flex;
padding: 2rem 0 0 0; padding: 2rem 0 0 0;
font-size: .9rem; font-size: .9rem;
font-weight: bold; font-weight: bold;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.nav-group-header>div:nth-child(1) { .nav-group-header>div:nth-child(1) {
padding: 0rem .5rem 0rem 0rem; padding: 0rem .5rem 0rem 0rem;
vertical-align: bottom; vertical-align: bottom;
grid-column-start: icon; grid-column-start: icon;
margin-right: 5px; margin-right: 5px;
} }
.nav-group-header>span:nth-child(2) { .nav-group-header>span:nth-child(2) {
margin-left:5px; margin-left:5px;
vertical-align: bottom; vertical-align: bottom;
grid-column-start: title; grid-column-start: title;
margin-top:auto; margin-top:auto;
} }
.nav-group-header>div:nth-child(3) { .nav-group-header>div:nth-child(3) {
vertical-align: bottom; vertical-align: bottom;
grid-column-start: button; grid-column-start: button;
cursor: pointer; cursor: pointer;
color: var(--primary75); color: var(--primary75);
} }
.nav-group-header>div:nth-child(3):hover { .nav-group-header>div:nth-child(3):hover {
color: var(--primary75); color: var(--primary75);
} }
.navigator-title { .navigator-title {
text-transform: uppercase; text-transform: uppercase;
font-weight: 400; font-weight: 400;
color: #999; color: #999;
} }
</style> </style>

Loading…
Cancel
Save