designer for nested components

This commit is contained in:
michael shanks 2019-08-19 08:51:01 +01:00
parent 0d7d9f471e
commit 53c3a54230
9 changed files with 216 additions and 35 deletions

View File

@ -1,17 +1,19 @@
const apiCall = (method) => (url, body, returnResponse=false) =>
const apiCall = (method, returnResponse) => (url, body) =>
fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
},
body: body && JSON.stringify(body),
}).then(r => returnResponse ? r : r.json());
}).then(r =>
returnResponse ? r.json() : r
);
const post = apiCall("POST");
const get = apiCall("GET");
const patch = apiCall("PATCH");
const del = apiCall("DELETE");
const post = apiCall("POST", true);
const get = apiCall("GET", true);
const patch = apiCall("PATCH", true);
const del = apiCall("DELETE", false);
export default {
post, get, patch, delete:del

View File

@ -384,6 +384,10 @@ const saveDerivedComponent = store => (derivedComponent) => {
]);
s.allComponents = components;
s.currentFrontEndItem = derivedComponent;
s.currentComponentInfo = getNewComponentInfo(
s.allComponents, componentName);
s.currentComponentIsNew = false;
api.post(`/_builder/api/${s.appname}/derivedcomponent`, derivedComponent);

View File

@ -0,0 +1,94 @@
<script>
import PropsView from "./PropsView.svelte";
import IconButton from "../common/IconButton.svelte";
import { getComponentInfo } from "./pagesParsing/createProps";
import { store } from "../builderStore";
import { cloneDeep } from "lodash/fp";
import { fade, slide } from 'svelte/transition';
export let propertyName = "";
export let onGoBack = () => {};
export let instanceProps = {};
export let onPropsChanged = () => {};
let editingSubComponentName;
let editingSubComponentProps;
let allComponents;
store.subscribe(s => {
allComponents = s.allComponents;
})
$: componentInfo = getComponentInfo(
allComponents, instanceProps._component);
const onSubComponentGoBack = () => {
editingSubComponentName = null;
editingSubComponentProps = null;
}
const onEditComponentProp = (propName) => {
editingSubComponentName = propName;
editingSubComponentProps = instanceProps[propName];
};
const onSubComponentPropsChanged = (subProps) => {
const newProps = cloneDeep(instanceProps);
newProps[editingSubComponentName] = 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>{propertyName}</span>
</div>
{#if editingSubComponentName}
<div in:slide={{delay: 250, duration: 300}}
out:fade>
<svelte:self onPropsChanged={onSubComponentPropsChanged}
onGoBack={onSubComponentGoBack}
instanceProps={editingSubComponentProps}
propertyName={editingSubComponentName} />
</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

@ -16,6 +16,8 @@ const emptyProps = () => ({_component:""})
export let props = emptyProps();
export let onValueChanged = () => {};
export let onComponentChosen = () => {};
export let onEdit = () => {};
export let label = ""
export let disabled = false;
@ -43,10 +45,11 @@ const clearComponent = () => {
showDialog();
}
const onComponentChosen = (component) => {
const componentChosen = (component) => {
const componentInfo = getComponentInfo(allComponents, component.name);
props = componentInfo.fullProps;
onValueChanged(props);
onComponentChosen();
hideDialog();
}
@ -74,7 +77,8 @@ const confirmClearComponent = () => {
</div>
<div>
{#if !disabled && componentSelected}
<IconButton icon="edit" />
<IconButton icon="edit"
on:click={onEdit}/>
<IconButton icon="trash"
on:click={clearComponent} />
@ -91,7 +95,7 @@ const confirmClearComponent = () => {
{#if modalAction === CHOOSE_COMPONENT}
<div class="uk-modal-body">
<ComponentSearch {onComponentChosen} />
<ComponentSearch onComponentChosen={componentChosen} />
</div>
{:else if modalAction === CLEAR_COMPONENT}
<div class="uk-modal-body">

View File

@ -12,6 +12,7 @@ import {
} from "./pagesParsing/createProps";
import Button from "../common/Button.svelte";
import ButtonGroup from "../common/ButtonGroup.svelte";
import ComponentInstanceEditor from "./ComponentInstanceEditor.svelte";
import {
cloneDeep,
@ -33,6 +34,10 @@ let componentDetailsExpanded = false;
let componentInfo;
let modalElement
let propsValidationErrors = [];
let editingComponentInstance;
let editingComponentInstancePropName="";
let allComponents;
$: shortName = last(name.split("/"));
store.subscribe(s => {
@ -43,6 +48,7 @@ store.subscribe(s => {
tagsString = join(", ")(component.tags);
componentInfo = s.currentComponentInfo;
componentDetailsExpanded = s.currentComponentIsNew;
allComponents = s.allComponents;
});
const save = () => {
@ -72,8 +78,17 @@ const onPropsValidate = result => {
propsValidationErrors = result;
}
const onPropsChanged = props => {
assign(component.props, props);
const updateComponent = doChange => {
const newComponent = cloneDeep(component);
doChange(newComponent);
component = newComponent;
componentInfo = getComponentInfo(allComponents, newComponent);
}
const onPropsChanged = newProps => {
updateComponent(newComponent =>
assign(newComponent.props, newProps));
}
const validate = () => {
@ -96,6 +111,21 @@ const showDialog = () => {
UIkit.modal(modalElement).show();
}
const onEditComponentProp = (propName) => {
editingComponentInstance = component.props[propName];
editingComponentInstancePropName = propName;
}
const componentInstanceCancelEdit = () => {
editingComponentInstance = null;
editingComponentInstancePropName = "";
}
const componentInstancePropsChanged = (instanceProps) => {
updateComponent(newComponent =>
newComponent.props[editingComponentInstancePropName] = instanceProps);
}
</script>
<div class="root">
@ -114,15 +144,21 @@ const showDialog = () => {
</div>
</div>
<div class="body">
{#if editingComponentInstance}
<ComponentInstanceEditor onGoBack={componentInstanceCancelEdit}
propertyName={editingComponentInstancePropName}
instanceProps={editingComponentInstance}
onPropsChanged={componentInstancePropsChanged}/>
{:else}
<div>
<div class="section-header" on:click={() => componentDetailsExpanded = !componentDetailsExpanded}>
<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>
<div class="padding">
<Textbox label="Name"
infoText="use forward slash to store in subfolders"
bind:text={name}
@ -137,15 +173,19 @@ const showDialog = () => {
</div>
{/if}
<p class="section-header"><span>Properties</span></p>
<div class="section-header padding">
<span>Properties</span>
</div>
<PropsView onValidate={onPropsValidate}
{componentInfo}
{onPropsChanged}/>
{onPropsChanged}
{onEditComponentProp}/>
</div>
{/if}
</div>
@ -188,8 +228,8 @@ const showDialog = () => {
border-width: 0px 0px 0px 1px;
}
.body {
padding: 10px;
.padding {
padding: 0px 5px 0px 10px;
}
.title {
@ -218,6 +258,7 @@ const showDialog = () => {
.section-header {
vertical-align: middle;
margin-top: 20px;
}
</style>

View File

@ -11,11 +11,19 @@ export let fieldHasError =() => {};
export let propDef = {};
export let props = {};
export let disabled;
export let index;
export let onEditComponent = () => {};
$: isOdd = (index % 2 !== 0);
const setComponentProp = (props) => {
setProp(propDef.____name, props);
}
</script>
<div class="root">
<div class="root" >
{#if propDef.type === "bool"}
@ -33,7 +41,9 @@ export let disabled;
<ComponentPropSelector label={propDef.____name}
props={props[propDef.____name]}
{disabled}
onValueChanged={props => setProp(propDef.____name, props)}/>
onEdit={onEditComponent}
onComponentChosen={onEditComponent}
onValueChanged={setComponentProp}/>
{:else}
<Textbox label={propDef.____name}
text={props[propDef.____name]}
@ -48,7 +58,10 @@ export let disabled;
<style>
.root {
margin-top: 7px;
padding: 3px 5px 7px 10px;
border-style: dotted;
border-width: 0 0 1px 0;
border-color: var(--primary25);
}
</style>

View File

@ -8,7 +8,8 @@ import {
cloneDeep,
isEqual,
sortBy,
filter
filter,
difference
} from "lodash/fp";
import { pipe } from "../common/core";
import {
@ -29,12 +30,14 @@ export let onValidate = () => {};
export let componentInfo;
export let instanceProps = null;
export let onPropsChanged = () => {};
export let onEditComponentProp = () => {};
let errors = [];
let props = {};
let propsDefinitions = [];
let inheritedPropsDefinitions = [];
let inheritedExpanded = false;
let isInstance = false;
const isPropInherited = name =>
includes(name)(componentInfo.inheritedProps);
@ -42,7 +45,8 @@ const isPropInherited = name =>
$: {
if(componentInfo)
{
props = instanceProps
isInstance = !!instanceProps;
props = isInstance
? getInstanceProps(componentInfo, instanceProps)
: cloneDeep(componentInfo.fullProps);
@ -65,16 +69,22 @@ $: {
let setProp = (name, value) => {
const newProps = cloneDeep(props);
newProps[name] = value;
const finalProps = {};
let finalProps = isInstance ? newProps : cloneDeep(componentInfo.component.props);
if(!isInstance) {
const nowSet = [];
for(let p of componentInfo.unsetProps) {
if(!isEqual(newProps[p])(componentInfo.rootDefaultProps[p])) {
finalProps[p] = newProps[p];
nowSet.push(p);
}
}
componentInfo.unsetProps = difference(nowSet)(componentInfo.unsetProps);
}
newProps[name] = value;
finalProps[name] = value;
props = newProps;
if(validate(finalProps))
onPropsChanged(finalProps);
@ -90,25 +100,30 @@ const validate = (finalProps) => {
const fieldHasError = (propName) =>
some(e => e.propName === propName)(errors);
const onEditComponent = (propName) => () => {
onEditComponentProp(propName);
}
</script>
<div class="root">
<form class="uk-form-stacked">
{#each propsDefinitions as propDef}
{#each propsDefinitions as propDef, index}
<PropControl {errors}
{setProp}
{fieldHasError}
{propDef}
{props}
{index}
onEditComponent={onEditComponent(propDef.____name)}
disabled={false} />
{/each}
{#if inheritedPropsDefinitions.length > 0}
<div class="inherited-title">
<div class="inherited-title padding">
<div>Inherited</div>
<div>
<IconButton icon={inheritedExpanded ? "chevron-down" : "chevron-right"}
@ -118,13 +133,14 @@ const fieldHasError = (propName) =>
{/if}
{#if inheritedExpanded}
{#each inheritedPropsDefinitions as propDef}
{#each inheritedPropsDefinitions as propDef, index}
<PropControl {errors}
{setProp}
{fieldHasError}
{propDef}
{props}
{index}
disabled={true} />
{/each}
@ -143,6 +159,10 @@ const fieldHasError = (propName) =>
font-size:10pt;
}
.padding {
padding: 0 10px;
}
.inherited-title {
margin-top: 40px;
display: grid;

View File

@ -71,7 +71,7 @@ const newComponent = () => {
.root {
display: grid;
grid-template-columns: [uiNav] 250px [preview] auto [properties] 250px;
grid-template-columns: [uiNav] 250px [preview] auto [properties] 300px;
height: 100%;
width: 100%;
}

View File

@ -66,8 +66,11 @@ export const getNewComponentInfo = (allComponents, inherits) => {
}
export const getComponentInfo = (allComponents, cname, stack=[], subComponentProps=null) => {
const component = find(c => c.name === cname)(allComponents);
export const getComponentInfo = (allComponents, comp, stack=[], subComponentProps=null) => {
const component = isString(comp)
? find(c => c.name === comp)(allComponents)
: comp;
const cname = isString(comp) ? comp : comp.name;
if(isRootComponent(component)) {
subComponentProps = subComponentProps||{};
const p = createProps(cname, component.props, subComponentProps);