designer for nested components
This commit is contained in:
parent
0d7d9f471e
commit
53c3a54230
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
|
@ -11,6 +11,14 @@ 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>
|
||||
|
||||
|
@ -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>
|
|
@ -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;
|
||||
|
|
|
@ -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%;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue