Implement updating of individual component props

This commit is contained in:
pngwn 2020-01-24 11:32:13 +00:00
parent 05a32f25f0
commit d78f8013b5
14 changed files with 385 additions and 254 deletions

View File

@ -10,14 +10,14 @@ import {
pipe, getNode, validate, pipe, getNode, validate,
constructHierarchy, templateApi constructHierarchy, templateApi
} from "../common/core"; } from "../common/core";
import {writable} from "svelte/store"; import { writable } from "svelte/store";
import { defaultPagesObject } from "../userInterface/pagesParsing/defaultPagesObject" import { defaultPagesObject } from "../userInterface/pagesParsing/defaultPagesObject"
import { buildPropsHierarchy } from "../userInterface/pagesParsing/buildPropsHierarchy" import { buildPropsHierarchy } from "../userInterface/pagesParsing/buildPropsHierarchy"
import api from "./api"; import api from "./api";
import { isRootComponent, getExactComponent } from "../userInterface/pagesParsing/searchComponents"; import { isRootComponent, getExactComponent } from "../userInterface/pagesParsing/searchComponents";
import { rename } from "../userInterface/pagesParsing/renameScreen"; import { rename } from "../userInterface/pagesParsing/renameScreen";
import { import {
getNewComponentInfo, getScreenInfo getNewComponentInfo, getScreenInfo, getComponentInfo
} from "../userInterface/pagesParsing/createProps"; } from "../userInterface/pagesParsing/createProps";
import { import {
loadLibs, loadLibUrls, loadGeneratorLibs loadLibs, loadLibUrls, loadGeneratorLibs
@ -28,30 +28,30 @@ let appname = "";
export const getStore = () => { export const getStore = () => {
const initial = { const initial = {
apps:[], apps: [],
appname:"", appname: "",
hierarchy: {}, hierarchy: {},
actions: [], actions: [],
triggers: [], triggers: [],
pages:defaultPagesObject(), pages: defaultPagesObject(),
mainUi:{}, mainUi: {},
unauthenticatedUi:{}, unauthenticatedUi: {},
components:[], components: [],
currentFrontEndItem:null, currentFrontEndItem: null,
currentComponentInfo:null, currentComponentInfo: null,
currentFrontEndType:"none", currentFrontEndType: "none",
currentPageName: "", currentPageName: "",
currentComponentProps:null, currentComponentProps: null,
currentNodeIsNew: false, currentNodeIsNew: false,
errors: [], errors: [],
activeNav: "database", activeNav: "database",
isBackend:true, isBackend: true,
hasAppPackage: false, hasAppPackage: false,
accessLevels: {version:0, levels:[]}, accessLevels: { version: 0, levels: [] },
currentNode: null, currentNode: null,
libraries:null, libraries: null,
showSettings:false, showSettings: false,
useAnalytics:true, useAnalytics: true,
}; };
const store = writable(initial); const store = writable(initial);
@ -82,7 +82,7 @@ export const getStore = () => {
store.setCurrentScreen = setCurrentScreen(store); store.setCurrentScreen = setCurrentScreen(store);
store.setCurrentPage = setCurrentPage(store); store.setCurrentPage = setCurrentPage(store);
store.createScreen = createScreen(store); store.createScreen = createScreen(store);
store.removeComponentLibrary =removeComponentLibrary(store); store.removeComponentLibrary = removeComponentLibrary(store);
store.addStylesheet = addStylesheet(store); store.addStylesheet = addStylesheet(store);
store.removeStylesheet = removeStylesheet(store); store.removeStylesheet = removeStylesheet(store);
store.savePage = savePage(store); store.savePage = savePage(store);
@ -91,6 +91,9 @@ export const getStore = () => {
store.showSettings = showSettings(store); store.showSettings = showSettings(store);
store.useAnalytics = useAnalytics(store); store.useAnalytics = useAnalytics(store);
store.createGeneratedComponents = createGeneratedComponents(store); store.createGeneratedComponents = createGeneratedComponents(store);
store.addChildComponent = addChildComponent(store);
store.selectComponent = selectComponent(store);
store.saveComponent = saveComponent(store);
return store; return store;
} }
@ -99,10 +102,10 @@ export default getStore;
const initialise = (store, initial) => async () => { const initialise = (store, initial) => async () => {
appname = window.location.hash appname = window.location.hash
? last(window.location.hash.substr(1).split("/")) ? last(window.location.hash.substr(1).split("/"))
: ""; : "";
if(!appname) { if (!appname) {
initial.apps = await api.get(`/_builder/api/apps`).then(r => r.json()); initial.apps = await api.get(`/_builder/api/apps`).then(r => r.json());
initial.hasAppPackage = false; initial.hasAppPackage = false;
store.set(initial); store.set(initial);
@ -110,7 +113,7 @@ const initialise = (store, initial) => async () => {
} }
const pkg = await api.get(`/_builder/api/${appname}/appPackage`) const pkg = await api.get(`/_builder/api/${appname}/appPackage`)
.then(r => r.json()); .then(r => r.json());
initial.libraries = await loadLibs(appname, pkg); initial.libraries = await loadLibs(appname, pkg);
initial.generatorLibraries = await loadGeneratorLibs(appname, pkg); initial.generatorLibraries = await loadGeneratorLibs(appname, pkg);
@ -126,10 +129,10 @@ const initialise = (store, initial) => async () => {
initial.actions = values(pkg.appDefinition.actions); initial.actions = values(pkg.appDefinition.actions);
initial.triggers = pkg.appDefinition.triggers; initial.triggers = pkg.appDefinition.triggers;
if(!!initial.hierarchy && !isEmpty(initial.hierarchy)) { if (!!initial.hierarchy && !isEmpty(initial.hierarchy)) {
initial.hierarchy = constructHierarchy(initial.hierarchy); initial.hierarchy = constructHierarchy(initial.hierarchy);
const shadowHierarchy = createShadowHierarchy(initial.hierarchy); const shadowHierarchy = createShadowHierarchy(initial.hierarchy);
if(initial.currentNode !== null) if (initial.currentNode !== null)
initial.currentNode = getNode( initial.currentNode = getNode(
shadowHierarchy, initial.currentNode.nodeId shadowHierarchy, initial.currentNode.nodeId
); );
@ -179,12 +182,12 @@ const newRecord = (store, useRoot) => () => {
s.currentNodeIsNew = true; s.currentNodeIsNew = true;
const shadowHierarchy = createShadowHierarchy(s.hierarchy); const shadowHierarchy = createShadowHierarchy(s.hierarchy);
parent = useRoot ? shadowHierarchy parent = useRoot ? shadowHierarchy
: getNode( : getNode(
shadowHierarchy, shadowHierarchy,
s.currentNode.nodeId); s.currentNode.nodeId);
s.errors = []; s.errors = [];
s.currentNode = templateApi(shadowHierarchy) s.currentNode = templateApi(shadowHierarchy)
.getNewRecordTemplate(parent, "", true); .getNewRecordTemplate(parent, "", true);
return s; return s;
}); });
} }
@ -209,12 +212,12 @@ const newIndex = (store, useRoot) => () => {
s.errors = []; s.errors = [];
const shadowHierarchy = createShadowHierarchy(s.hierarchy); const shadowHierarchy = createShadowHierarchy(s.hierarchy);
parent = useRoot ? shadowHierarchy parent = useRoot ? shadowHierarchy
: getNode( : getNode(
shadowHierarchy, shadowHierarchy,
s.currentNode.nodeId); s.currentNode.nodeId);
s.currentNode = templateApi(shadowHierarchy) s.currentNode = templateApi(shadowHierarchy)
.getNewIndexTemplate(parent); .getNewIndexTemplate(parent);
return s; return s;
}); });
} }
@ -224,7 +227,7 @@ const saveCurrentNode = (store) => () => {
const errors = validate.node(s.currentNode); const errors = validate.node(s.currentNode);
s.errors = errors; s.errors = errors;
if(errors.length > 0) { if (errors.length > 0) {
return s; return s;
} }
@ -235,7 +238,7 @@ const saveCurrentNode = (store) => () => {
s.hierarchy, s.currentNode.nodeId); s.hierarchy, s.currentNode.nodeId);
let index = parentNode.children.length; let index = parentNode.children.length;
if(!!existingNode) { if (!!existingNode) {
// remove existing // remove existing
index = existingNode.parent().children.indexOf(existingNode); index = existingNode.parent().children.indexOf(existingNode);
existingNode.parent().children = pipe(existingNode.parent().children, [ existingNode.parent().children = pipe(existingNode.parent().children, [
@ -251,7 +254,7 @@ const saveCurrentNode = (store) => () => {
); );
const newIndexOfchild = child => { const newIndexOfchild = child => {
if(child === cloned) return index; if (child === cloned) return index;
const currentIndex = parentNode.children.indexOf(child); const currentIndex = parentNode.children.indexOf(child);
return currentIndex >= index ? currentIndex + 1 : currentIndex; return currentIndex >= index ? currentIndex + 1 : currentIndex;
} }
@ -260,9 +263,9 @@ const saveCurrentNode = (store) => () => {
sortBy(newIndexOfchild) sortBy(newIndexOfchild)
]); ]);
if(!existingNode && s.currentNode.type === "record") { if (!existingNode && s.currentNode.type === "record") {
const defaultIndex = templateApi(s.hierarchy) const defaultIndex = templateApi(s.hierarchy)
.getNewIndexTemplate(cloned.parent()); .getNewIndexTemplate(cloned.parent());
defaultIndex.name = `all_${cloned.collectionName}`; defaultIndex.name = `all_${cloned.collectionName}`;
defaultIndex.allowedRecordNodeIds = [cloned.nodeId]; defaultIndex.allowedRecordNodeIds = [cloned.nodeId];
} }
@ -279,8 +282,8 @@ const importAppDefinition = store => appDefinition => {
store.update(s => { store.update(s => {
s.hierarchy = appDefinition.hierarchy; s.hierarchy = appDefinition.hierarchy;
s.currentNode = appDefinition.hierarchy.children.length > 0 s.currentNode = appDefinition.hierarchy.children.length > 0
? appDefinition.hierarchy.children[0] ? appDefinition.hierarchy.children[0]
: null; : null;
s.actions = appDefinition.actions; s.actions = appDefinition.actions;
s.triggers = appDefinition.triggers; s.triggers = appDefinition.triggers;
s.currentNodeIsNew = false; s.currentNodeIsNew = false;
@ -292,15 +295,15 @@ const deleteCurrentNode = store => () => {
store.update(s => { store.update(s => {
const nodeToDelete = getNode(s.hierarchy, s.currentNode.nodeId); const nodeToDelete = getNode(s.hierarchy, s.currentNode.nodeId);
s.currentNode = hierarchyFunctions.isRoot(nodeToDelete.parent()) s.currentNode = hierarchyFunctions.isRoot(nodeToDelete.parent())
? find(n => n != s.currentNode) ? find(n => n != s.currentNode)
(s.hierarchy.children) (s.hierarchy.children)
: nodeToDelete.parent(); : nodeToDelete.parent();
if(hierarchyFunctions.isRecord(nodeToDelete)) { if (hierarchyFunctions.isRecord(nodeToDelete)) {
nodeToDelete.parent().children = filter(c => c.nodeId !== nodeToDelete.nodeId) nodeToDelete.parent().children = filter(c => c.nodeId !== nodeToDelete.nodeId)
(nodeToDelete.parent().children); (nodeToDelete.parent().children);
} else { } else {
nodeToDelete.parent().indexes = filter(c => c.nodeId !== nodeToDelete.nodeId) nodeToDelete.parent().indexes = filter(c => c.nodeId !== nodeToDelete.nodeId)
(nodeToDelete.parent().indexes); (nodeToDelete.parent().indexes);
} }
s.errors = []; s.errors = [];
savePackage(store, s); savePackage(store, s);
@ -311,7 +314,7 @@ const deleteCurrentNode = store => () => {
const saveField = databaseStore => (field) => { const saveField = databaseStore => (field) => {
databaseStore.update(db => { databaseStore.update(db => {
db.currentNode.fields = filter(f => f.name !== field.name) db.currentNode.fields = filter(f => f.name !== field.name)
(db.currentNode.fields); (db.currentNode.fields);
templateApi(db.hierarchy).addField(db.currentNode, field); templateApi(db.hierarchy).addField(db.currentNode, field);
return db; return db;
@ -322,21 +325,21 @@ const saveField = databaseStore => (field) => {
const deleteField = databaseStore => field => { const deleteField = databaseStore => field => {
databaseStore.update(db => { databaseStore.update(db => {
db.currentNode.fields = filter(f => f.name !== field.name) db.currentNode.fields = filter(f => f.name !== field.name)
(db.currentNode.fields); (db.currentNode.fields);
return db; return db;
}); });
} }
const saveAction = store => (newAction, isNew, oldAction=null) => { const saveAction = store => (newAction, isNew, oldAction = null) => {
store.update(s => { store.update(s => {
const existingAction = isNew const existingAction = isNew
? null ? null
: find(a => a.name === oldAction.name)(s.actions); : find(a => a.name === oldAction.name)(s.actions);
if(existingAction) { if (existingAction) {
s.actions = pipe(s.actions, [ s.actions = pipe(s.actions, [
map(a => a === existingAction ? newAction : a) map(a => a === existingAction ? newAction : a)
]); ]);
@ -348,7 +351,7 @@ const saveAction = store => (newAction, isNew, oldAction=null) => {
}); });
} }
const deleteAction = store => action => { const deleteAction = store => action => {
store.update(s => { store.update(s => {
s.actions = filter(a => a.name !== action.name)(s.actions); s.actions = filter(a => a.name !== action.name)(s.actions);
savePackage(store, s); savePackage(store, s);
@ -356,14 +359,14 @@ const deleteAction = store => action => {
}); });
} }
const saveTrigger = store => (newTrigger, isNew, oldTrigger=null) => { const saveTrigger = store => (newTrigger, isNew, oldTrigger = null) => {
store.update(s => { store.update(s => {
const existingTrigger = isNew const existingTrigger = isNew
? null ? null
: find(a => a.name === oldTrigger.name)(s.triggers); : find(a => a.name === oldTrigger.name)(s.triggers);
if(existingTrigger) { if (existingTrigger) {
s.triggers = pipe(s.triggers, [ s.triggers = pipe(s.triggers, [
map(a => a === existingTrigger ? newTrigger : a) map(a => a === existingTrigger ? newTrigger : a)
]); ]);
@ -375,7 +378,7 @@ const saveTrigger = store => (newTrigger, isNew, oldTrigger=null) => {
}); });
} }
const deleteTrigger = store => trigger => { const deleteTrigger = store => trigger => {
store.update(s => { store.update(s => {
s.triggers = filter(t => t.name !== trigger.name)(s.triggers); s.triggers = filter(t => t.name !== trigger.name)(s.triggers);
return s; return s;
@ -385,16 +388,16 @@ const deleteTrigger = store => trigger => {
const incrementAccessLevelsVersion = (s) => const incrementAccessLevelsVersion = (s) =>
s.accessLevels.version = (s.accessLevels.version || 0) + 1; s.accessLevels.version = (s.accessLevels.version || 0) + 1;
const saveLevel = store => (newLevel, isNew, oldLevel=null) => { const saveLevel = store => (newLevel, isNew, oldLevel = null) => {
store.update(s => { store.update(s => {
const levels = s.accessLevels.levels; const levels = s.accessLevels.levels;
const existingLevel = isNew const existingLevel = isNew
? null ? null
: find(a => a.name === oldLevel.name)(levels); : find(a => a.name === oldLevel.name)(levels);
if(existingLevel) { if (existingLevel) {
s.accessLevels.levels = pipe(levels, [ s.accessLevels.levels = pipe(levels, [
map(a => a === existingLevel ? newLevel : a) map(a => a === existingLevel ? newLevel : a)
]); ]);
@ -470,7 +473,7 @@ const createGeneratedComponents = store => components => {
s.screens = [...s.screens, ...components]; s.screens = [...s.screens, ...components];
const doCreate = async () => { const doCreate = async () => {
for(let c of components) { for (let c of components) {
await api.post(`/_builder/api/${s.appname}/screen`, c); await api.post(`/_builder/api/${s.appname}/screen`, c);
} }
@ -496,7 +499,7 @@ const deleteScreen = store => name => {
s.components = components; s.components = components;
s.screens = screens; s.screens = screens;
if(s.currentFrontEndItem.name === name) { if (s.currentFrontEndItem.name === name) {
s.currentFrontEndItem = null; s.currentFrontEndItem = null;
s.currentFrontEndType = ""; s.currentFrontEndType = "";
} }
@ -514,20 +517,20 @@ const renameScreen = store => (oldname, newname) => {
screens, pages, error, changedScreens screens, pages, error, changedScreens
} = rename(s.pages, s.screens, oldname, newname); } = rename(s.pages, s.screens, oldname, newname);
if(error) { if (error) {
// should really do something with this // should really do something with this
return s; return s;
} }
s.screens = screens; s.screens = screens;
s.pages = pages; s.pages = pages;
if(s.currentFrontEndItem.name === oldname) if (s.currentFrontEndItem.name === oldname)
s.currentFrontEndItem.name = newname; s.currentFrontEndItem.name = newname;
const saveAllChanged = async () => { const saveAllChanged = async () => {
for(let screenName of changedScreens) { for (let screenName of changedScreens) {
const changedScreen const changedScreen
= getExactComponent(screens, screenName); = getExactComponent(screens, screenName);
await api.post(`/_builder/api/${s.appname}/screen`, changedScreen); await api.post(`/_builder/api/${s.appname}/screen`, changedScreen);
} }
} }
@ -535,10 +538,10 @@ const renameScreen = store => (oldname, newname) => {
api.patch(`/_builder/api/${s.appname}/screen`, { api.patch(`/_builder/api/${s.appname}/screen`, {
oldname, newname oldname, newname
}) })
.then(() => saveAllChanged()) .then(() => saveAllChanged())
.then(() => { .then(() => {
savePackage(store, s); savePackage(store, s);
}); });
return s; return s;
}) })
@ -546,7 +549,7 @@ const renameScreen = store => (oldname, newname) => {
const savePage = store => async page => { const savePage = store => async page => {
store.update(s => { store.update(s => {
if(s.currentFrontEndType !== "page" || !s.currentPageName) { if (s.currentFrontEndType !== "page" || !s.currentPageName) {
return s; return s;
} }
@ -559,25 +562,25 @@ const savePage = store => async page => {
const addComponentLibrary = store => async lib => { const addComponentLibrary = store => async lib => {
const response = const response =
await api.get(`/_builder/api/${appname}/componentlibrary?lib=${encodeURI(lib)}`,undefined, false); await api.get(`/_builder/api/${appname}/componentlibrary?lib=${encodeURI(lib)}`, undefined, false);
const success = response.status === 200; const success = response.status === 200;
const error = response.status === 404 const error = response.status === 404
? `Could not find library ${lib}` ? `Could not find library ${lib}`
: success : success
? "" ? ""
: response.statusText; : response.statusText;
const components = success const components = success
? await response.json() ? await response.json()
: []; : [];
store.update(s => { store.update(s => {
if(success) { if (success) {
const componentsArray = []; const componentsArray = [];
for(let c in components) { for (let c in components) {
componentsArray.push(components[c]); componentsArray.push(components[c]);
} }
@ -601,7 +604,7 @@ const removeComponentLibrary = store => lib => {
s.pages.componentLibraries = filter(l => l !== lib)( s.pages.componentLibraries = filter(l => l !== lib)(
s.pages.componentLibraries); s.pages.componentLibraries);
savePackage(store, s); savePackage(store, s);
@ -632,7 +635,7 @@ const refreshComponents = store => async () => {
const components = pipe(componentsAndGenerators.components, [ const components = pipe(componentsAndGenerators.components, [
keys, keys,
map(k => ({...componentsAndGenerators[k], name:k})) map(k => ({ ...componentsAndGenerators[k], name: k }))
]); ]);
store.update(s => { store.update(s => {
@ -648,25 +651,25 @@ const refreshComponents = store => async () => {
const savePackage = (store, s) => { const savePackage = (store, s) => {
const appDefinition = { const appDefinition = {
hierarchy:s.hierarchy, hierarchy: s.hierarchy,
triggers:s.triggers, triggers: s.triggers,
actions: keyBy("name")(s.actions), actions: keyBy("name")(s.actions),
props: { props: {
main: buildPropsHierarchy( main: buildPropsHierarchy(
s.components, s.components,
s.screens, s.screens,
s.pages.main.appBody), s.pages.main.appBody),
unauthenticated: buildPropsHierarchy( unauthenticated: buildPropsHierarchy(
s.components, s.components,
s.screens, s.screens,
s.pages.unauthenticated.appBody) s.pages.unauthenticated.appBody)
} }
}; };
const data = { const data = {
appDefinition, appDefinition,
accessLevels:s.accessLevels, accessLevels: s.accessLevels,
pages:s.pages, pages: s.pages,
} }
return api.post(`/_builder/api/${s.appname}/appPackage`, data); return api.post(`/_builder/api/${s.appname}/appPackage`, data);
@ -689,3 +692,44 @@ const setCurrentPage = store => pageName => {
return s; return s;
}) })
} }
const addChildComponent = store => component => {
store.update(s => {
const newComponent = getNewComponentInfo(
s.components, component);
const children = s.currentFrontEndItem.props._children;
const component_definition = {
...newComponent.fullProps,
_component: component,
name: component,
description: '',
location: (s.currentFrontEndItem.location ? s.currentFrontEndItem.location : [])
.concat(children && children.length || 0)
}
s.currentFrontEndItem.props._children =
children ?
children.concat(component_definition) :
[component_definition];
return s;
})
}
const selectComponent = store => component => {
store.update(s => {
s.currentComponentInfo = getComponentInfo(s.components, component);
return s;
})
}
const saveComponent = store => component => {
store.update(s => {
s.currentComponentInfo = getComponentInfo(s.components, component);
console.log(s.currentFrontEndItem, s.screens)
return _saveScreen(store, s, s.currentFrontEndItem);
})
}

View File

@ -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="M4.828 21l-.02.02-.021-.02H2.992A.993.993 0 0 1 2 20.007V3.993A1 1 0 0 1 2.992 3h18.016c.548 0 .992.445.992.993v16.014a1 1 0 0 1-.992.993H4.828zM20 15V5H4v14L14 9l6 6zm0 2.828l-6-6L6.828 19H20v-1.172zM8 11a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/>
</svg>

After

Width:  |  Height:  |  Size: 400 B

View File

@ -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="M5 5v14h14V5H5zM4 3h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm5.869 12l-.82 2H6.833L11 7h2l4.167 10H14.95l-.82-2H9.87zm.82-2h2.622L12 9.8 10.689 13z"/>
</svg>

After

Width:  |  Height:  |  Size: 339 B

View File

@ -1,3 +1,5 @@
export { default as LayoutIcon } from './Layout.svelte'; export { default as LayoutIcon } from './Layout.svelte';
export { default as PaintIcon } from './Paint.svelte'; 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 ImageIcon } from './Image.svelte';

View File

@ -0,0 +1,24 @@
<script>
import { last } from "lodash/fp";
import { pipe } from "../common/core";
export let components = [];
export let onSelect = () => {};
const capitalise = s => s.substring(0,1).toUpperCase() + s.substring(1);
const get_name = s => last(s.split('/'));
const get_capitalised_name = name => pipe(name, [get_name,capitalise]);
</script>
{#each components as component}
<ul>
<li on:click|stopPropagation={() => onSelect(component)}>
{get_capitalised_name(component.name)}
{#if component._children}
<svelte:self components={component._children}/>
{/if}
</li>
</ul>
{/each}

View File

@ -43,7 +43,7 @@ $: shortName = last(name.split("/"));
store.subscribe(s => { store.subscribe(s => {
if(ignoreStore) return; if(ignoreStore) return;
component = s.currentFrontEndItem; component = s.currentComponentInfo.component;
if(!component) return; if(!component) return;
originalName = component.name; originalName = component.name;
name = component.name; name = component.name;
@ -68,7 +68,7 @@ const save = () => {
map(s => s.trim()) map(s => s.trim())
]); ]);
store.saveScreen(component); store.saveComponent(component);
ignoreStore = false; ignoreStore = false;
// now do the rename // now do the rename
@ -92,14 +92,15 @@ const onPropsValidate = result => {
const updateComponent = doChange => { const updateComponent = doChange => {
const newComponent = cloneDeep(component); const newComponent = cloneDeep(component);
doChange(newComponent);
component = newComponent; component = doChange(newComponent);
console.log(component, $store.screens[0].props._children[1])
componentInfo = getScreenInfo(components, newComponent); componentInfo = getScreenInfo(components, newComponent);
} }
const onPropsChanged = newProps => { const onPropsChanged = newProps => {
updateComponent(newComponent => updateComponent(newComponent =>
assign(newComponent.props, newProps)); assign(newComponent, newProps))
save(); save();
} }

View File

@ -1,4 +1,5 @@
<script> <script>
import ComponentHierarchyChildren from './ComponentHierarchyChildren.svelte';
import { import {
last, last,
@ -22,6 +23,7 @@ export let thisLevel = "";
let pathPartsThisLevel; let pathPartsThisLevel;
let componentsThisLevel; let componentsThisLevel;
let _components;
let subfolders; let subfolders;
let expandedFolders = []; let expandedFolders = [];
@ -52,7 +54,7 @@ const isOnNextLevel = (c) =>
normalizedName(c.name).split("/").length === pathPartsThisLevel + 1 normalizedName(c.name).split("/").length === pathPartsThisLevel + 1
const lastPartOfName = (c) => const lastPartOfName = (c) =>
last(c.name.split("/")) last(c.name ? c.name.split("/") : c._component.split("/"))
const subFolder = (c) => { const subFolder = (c) => {
const cname = normalizedName(c.name); const cname = normalizedName(c.name);
@ -102,27 +104,27 @@ $: {
? 1 ? 1
: normalizedName(thisLevel).split("/").length + 1; : normalizedName(thisLevel).split("/").length + 1;
componentsThisLevel = _components =
pipe(components, [ pipe(components, [
filter(isOnThisLevel), // filter(isOnThisLevel),
map(c => ({component:c, title:lastPartOfName(c)})), map(c => ({component: c, title:lastPartOfName(c)})),
sortBy("title") sortBy("title")
]); ]);
subfolders = // subfolders =
pipe(components, [ // pipe(components, [
filter(notOnThisLevel), // filter(notOnThisLevel),
sortBy("name"), // sortBy("name"),
map(subFolder), // map(subFolder),
uniqWith((f1,f2) => f1.path === f2.path) // uniqWith((f1,f2) => f1.path === f2.path)
]); // ]);
} }
</script> </script>
<div class="root"> <div class="root">
{#each subfolders as folder} <!-- {#each subfolders as folder}
<div class="hierarchy-item folder" <div class="hierarchy-item folder"
on:click|stopPropagation={() => expandFolder(folder)}> on:click|stopPropagation={() => expandFolder(folder)}>
<span>{@html getIcon(folder.isExpanded ? "chevron-down" : "chevron-right", "16")}</span> <span>{@html getIcon(folder.isExpanded ? "chevron-down" : "chevron-right", "16")}</span>
@ -132,14 +134,18 @@ $: {
thisLevel={folder.path} /> thisLevel={folder.path} />
{/if} {/if}
</div> </div>
{/each} {/each} -->
{#each componentsThisLevel as component} {#each _components as component}
<div class="hierarchy-item component" class:selected={isComponentSelected($store.currentFrontEndType, $store.currentFrontEndItem, component.component)} <div class="hierarchy-item component"
class:selected={isComponentSelected($store.currentFrontEndType, $store.currentFrontEndItem, component.component)}
on:click|stopPropagation={() => store.setCurrentScreen(component.component.name)}> on:click|stopPropagation={() => store.setCurrentScreen(component.component.name)}>
<!-- <span>{@html getIcon("circle", "7")}</span> -->
<span class="title">{component.title}</span> <span class="title">{component.title}</span>
</div> </div>
{#if component.component.props && component.component.props._children}
<ComponentHierarchyChildren components={component.component.props._children} onSelect={store.selectComponent}/>
{/if}
{/each} {/each}
</div> </div>

View File

@ -8,6 +8,7 @@ import {
groupBy, keys, find, sortBy groupBy, keys, find, sortBy
} from "lodash/fp"; } from "lodash/fp";
import { pipe } from "../common/core"; import { pipe } from "../common/core";
import { ImageIcon, InputIcon, LayoutIcon } from '../common/Icons/';
let componentLibraries=[]; let componentLibraries=[];
@ -29,9 +30,7 @@ const addRootComponent = (c, all) => {
}; };
const onComponentChosen = (component) => { const onComponentChosen = store.addChildComponent;
};
store.subscribe(s => { store.subscribe(s => {
@ -46,6 +45,8 @@ store.subscribe(s => {
componentLibraries = newComponentLibraries; componentLibraries = newComponentLibraries;
}); });
let current_view = 'text';
</script> </script>
<div class="root"> <div class="root">
@ -55,22 +56,34 @@ store.subscribe(s => {
</div> </div>
<div class="library-container"> <div class="library-container">
<ul>
<li>
<button class:selected={current_view === 'text'} on:click={() => current_view = 'text'}>
<InputIcon />
</button>
</li>
<li>
<button class:selected={current_view === 'layout'} on:click={() => current_view = 'layout'}>
<LayoutIcon />
</button>
</li>
<li>
<button class:selected={current_view === 'media'} on:click={() => current_view = 'media'}>
<ImageIcon />
</button>
</li>
</ul>
{#each lib.components.filter(_ => true) as component}
<div class="inner-header">
Components
</div>
{#each lib.components as component}
<div class="component" <div class="component"
on:click={() => onComponentChosen(component)}> on:click={() => onComponentChosen(component.name)}>
<div class="name"> <div class="name">
{splitName(component.name).componentName} {splitName(component.name).componentName}
</div> </div>
<div class="description"> <!-- <div class="description">
{component.description} {component.description}
</div> </div> -->
</div> </div>
{/each} {/each}
@ -113,8 +126,16 @@ store.subscribe(s => {
} }
.component { .component {
padding: 2px 0px; padding: 0 15px;
cursor: pointer; cursor: pointer;
border: 1px solid #ccc;
border-radius: 2px;
margin: 10px 0;
height: 40px;
box-sizing: border-box;
color: #163057;
display: flex;
align-items: center;
} }
.component:hover { .component:hover {
@ -122,8 +143,11 @@ store.subscribe(s => {
} }
.component > .name { .component > .name {
color: var(--secondary100); color: #163057;
display: inline-block; display: inline-block;
font-size: 12px;
font-weight: bold;
opacity: 0.6;
} }
.component > .description { .component > .description {
@ -133,6 +157,34 @@ store.subscribe(s => {
margin-left: 10px; margin-left: 10px;
} }
ul {
list-style: none;
display: flex;
padding: 0;
}
li {
margin-right: 20px;
background: none;
border-radius: 5px;
width: 48px;
height: 48px;
}
li button {
width: 100%;
height: 100%;
background: none;
border: none;
border-radius: 5px;
padding: 12px;
outline: none;
cursor: pointer;
}
.selected {
color: var(--button-text);
background: var(--background-button)!important;
}
</style> </style>

View File

@ -29,7 +29,6 @@ let name="";
let saveAttempted=false; let saveAttempted=false;
store.subscribe(s => { store.subscribe(s => {
console.log(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)}))

View File

@ -25,7 +25,6 @@ let props = {};
let propsDefinitions = []; let propsDefinitions = [];
let isInstance = false; let isInstance = false;
$: { $: {
if(componentInfo) if(componentInfo)
{ {
@ -46,7 +45,7 @@ $: {
let setProp = (name, value) => { let setProp = (name, value) => {
const newProps = cloneDeep(props); const newProps = cloneDeep(props);
let finalProps = isInstance ? newProps : cloneDeep(componentInfo.component.props); let finalProps = isInstance ? newProps : cloneDeep(componentInfo.component.props ? componentInfo.component.props : componentInfo.component);
if(!isInstance) { if(!isInstance) {
const nowSet = []; const nowSet = [];
@ -98,9 +97,6 @@ const fieldHasError = (propName) =>
</form> </form>
</div> </div>

View File

@ -1,6 +1,6 @@
import { import {
isString, isUndefined, find, keys, uniq, isString, isUndefined, find, keys, uniq,
some, filter, reduce, cloneDeep, includes,last some, filter, reduce, cloneDeep, includes, last
} from "lodash/fp"; } from "lodash/fp";
import { types, expandComponentDefinition } from "./types"; import { types, expandComponentDefinition } from "./types";
import { assign } from "lodash"; import { assign } from "lodash";
@ -11,7 +11,7 @@ import { ensureShardNameIsInShardMap } from "../../../../core/src/indexing/shard
export const getInstanceProps = (componentInfo, props) => { export const getInstanceProps = (componentInfo, props) => {
const finalProps = cloneDeep(componentInfo.fullProps); const finalProps = cloneDeep(componentInfo.fullProps);
for(let p in props) { for (let p in props) {
finalProps[p] = props[p]; finalProps[p] = props[p];
} }
@ -21,8 +21,8 @@ export const getInstanceProps = (componentInfo, props) => {
export const getNewComponentInfo = (components, rootComponent, name) => { export const getNewComponentInfo = (components, rootComponent, name) => {
const component = { const component = {
name: name || "", name: name || "",
description:"", description: "",
props:{ props: {
_component: rootComponent _component: rootComponent
} }
}; };
@ -40,16 +40,16 @@ export const getScreenInfo = (components, screen) => {
export const getComponentInfo = (components, comp) => { export const getComponentInfo = (components, comp) => {
const targetComponent = isString(comp) const targetComponent = isString(comp)
? find(c => c.name === comp)(components) ? find(c => c.name === comp)(components)
: comp; : comp;
let component; let component;
let subComponent; let subComponent;
if(isRootComponent(targetComponent)) { if (isRootComponent(targetComponent)) {
component = targetComponent; component = targetComponent;
} else { } else {
subComponent = targetComponent; subComponent = targetComponent;
component = find(c => c.name === subComponent.props._component)( component = find(c => c.name === (subComponent.props ? subComponent.props._component : subComponent._component))(
components); components);
} }
const subComponentProps = subComponent ? subComponent.props : {}; const subComponentProps = subComponent ? subComponent.props : {};
@ -65,7 +65,7 @@ export const getComponentInfo = (components, comp) => {
fullProps._component = targetComponent.name; fullProps._component = targetComponent.name;
return ({ return ({
propsDefinition:expandComponentDefinition(component), propsDefinition: expandComponentDefinition(component),
rootDefaultProps: rootProps.props, rootDefaultProps: rootProps.props,
unsetProps, unsetProps,
fullProps: fullProps, fullProps: fullProps,
@ -78,7 +78,7 @@ export const getComponentInfo = (components, comp) => {
export const createProps = (componentDefinition, derivedFromProps) => { export const createProps = (componentDefinition, derivedFromProps) => {
const error = (propName, error) => const error = (propName, error) =>
errors.push({propName, error}); errors.push({ propName, error });
const props = { const props = {
_component: componentDefinition.name _component: componentDefinition.name
@ -86,24 +86,24 @@ export const createProps = (componentDefinition, derivedFromProps) => {
const errors = []; const errors = [];
if(!componentDefinition.name) if (!componentDefinition.name)
error("_component", "Component name not supplied"); error("_component", "Component name not supplied");
const propsDef = componentDefinition.props; const propsDef = componentDefinition.props;
for(let propDef in propsDef) { for (let propDef in propsDef) {
const parsedPropDef = parsePropDef(propsDef[propDef]); const parsedPropDef = parsePropDef(propsDef[propDef]);
if(parsedPropDef.error) if (parsedPropDef.error)
error(propDef, parsedPropDef.error); error(propDef, parsedPropDef.error);
else else
props[propDef] = parsedPropDef; props[propDef] = parsedPropDef;
} }
if(derivedFromProps) { if (derivedFromProps) {
assign(props, derivedFromProps); assign(props, derivedFromProps);
} }
if(componentDefinition.children !== false if (componentDefinition.children !== false
&& isUndefined(props._children)) { && isUndefined(props._children)) {
props._children = []; props._children = [];
} }
@ -114,26 +114,26 @@ export const createProps = (componentDefinition, derivedFromProps) => {
const parsePropDef = propDef => { const parsePropDef = propDef => {
const error = message => ({error:message, propDef}); const error = message => ({ error: message, propDef });
if(isString(propDef)) { if (isString(propDef)) {
if(!types[propDef]) if (!types[propDef])
return error(`Do not recognise type ${propDef}`); return error(`Do not recognise type ${propDef}`);
return types[propDef].default(); return types[propDef].default();
} }
if(!propDef.type) if (!propDef.type)
return error("Property Definition must declare a type"); return error("Property Definition must declare a type");
const type = types[propDef.type]; const type = types[propDef.type];
if(!type) if (!type)
return error(`Do not recognise type ${propDef.type}`); return error(`Do not recognise type ${propDef.type}`);
if(isUndefined(propDef.default)) if (isUndefined(propDef.default))
return type.default(propDef); return type.default(propDef);
if(!type.isOfType(propDef.default)) if (!type.isOfType(propDef.default))
return error(`${propDef.default} is not of type ${type}`); return error(`${propDef.default} is not of type ${type}`);
return propDef.default; return propDef.default;

View File

@ -19,11 +19,12 @@ const makeError = (errors, propName, stack) => (message) =>
errors.push({ errors.push({
stack, stack,
propName, propName,
error:message}); error: message
});
export const recursivelyValidate = (rootProps, getComponent, stack=[]) => { export const recursivelyValidate = (rootProps, getComponent, stack = []) => {
if(!rootProps._component) { if (!rootProps._component) {
const errs = []; const errs = [];
makeError(errs, "_component", stack)("Component is not set"); makeError(errs, "_component", stack)("Component is not set");
return errs; return errs;
@ -42,32 +43,32 @@ export const recursivelyValidate = (rootProps, getComponent, stack=[]) => {
const validateChildren = (_props, _stack) => const validateChildren = (_props, _stack) =>
!_props._children !_props._children
? [] ? []
: pipe(_props._children, [ : pipe(_props._children, [
map(child => recursivelyValidate( map(child => recursivelyValidate(
child, child,
getComponent, getComponent,
[..._stack, _props._children.indexOf(child)])) [..._stack, _props._children.indexOf(child)]))
]); ]);
const childErrors = validateChildren( const childErrors = validateChildren(
rootProps, stack); rootProps, stack);
return flattenDeep([errors, ...childErrors]); return flattenDeep([errors, ...childErrors]);
} }
const expandPropDef = propDef => const expandPropDef = propDef =>
isString(propDef) isString(propDef)
? types[propDef].defaultDefinition() ? types[propDef].defaultDefinition()
: propDef; : propDef;
export const validateProps = (componentDefinition, props, stack=[], isFinal=true) => { export const validateProps = (componentDefinition, props, stack = [], isFinal = true) => {
const errors = []; const errors = [];
if(isFinal && !props._component) { if (isFinal && !props._component) {
makeError(errors, "_component", stack)("Component is not set"); makeError(errors, "_component", stack)("Component is not set");
return errors; return errors;
// this would break everything else anyway // this would break everything else anyway
@ -75,11 +76,11 @@ export const validateProps = (componentDefinition, props, stack=[], isFinal=true
const propsDefinition = componentDefinition.props; const propsDefinition = componentDefinition.props;
for(let propDefName in props) { for (let propDefName in props) {
if(propDefName === "_component") continue; const ignore = ['_component', "_children", '_layout', 'name', 'description', 'location'];
if(propDefName === "_children") continue;
if(propDefName === "_layout") continue; if (ignore.includes(propDefName)) continue;
const propDef = expandPropDef(propsDefinition[propDefName]); const propDef = expandPropDef(propsDefinition[propDefName]);
@ -90,29 +91,29 @@ export const validateProps = (componentDefinition, props, stack=[], isFinal=true
const propValue = props[propDefName]; const propValue = props[propDefName];
// component declarations dont need to define al props. // component declarations dont need to define al props.
if(!isFinal && isUndefined(propValue)) continue; if (!isFinal && isUndefined(propValue)) continue;
if(isFinal && propDef.required && propValue) { if (isFinal && propDef.required && propValue) {
error(`Property ${propDefName} is required`); error(`Property ${propDefName} is required`);
continue; continue;
} }
if(isBinding(propValue)) { if (isBinding(propValue)) {
if(propDef.type === "event") { if (propDef.type === "event") {
error(`Cannot apply binding to type ${propDef.type}`); error(`Cannot apply binding to type ${propDef.type}`);
continue; continue;
} }
} }
else if(!type.isOfType(propValue)) { else if (!type.isOfType(propValue)) {
error(`Property ${propDefName} is not of type ${propDef.type}. Actual value ${propValue}`) error(`Property ${propDefName} is not of type ${propDef.type}. Actual value ${propValue}`)
continue; continue;
} }
if(propDef.type === "options" if (propDef.type === "options"
&& propValue && propValue
&& !isBinding(propValue) && !isBinding(propValue)
&& !includes(propValue)(propDef.options)) { && !includes(propValue)(propDef.options)) {
error(`Property ${propDefName} is not one of allowed options. Acutal value is ${propValue}`); error(`Property ${propDefName} is not one of allowed options. Acutal value is ${propValue}`);
} }
@ -129,15 +130,14 @@ export const validateComponentDefinition = (componentDefinition) => {
pipe(propDefinitions, [ pipe(propDefinitions, [
keys, keys,
map(k => ({ map(k => ({
propDef:propDefinitions[k], propDef: propDefinitions[k],
propName:k propName: k
})), })),
filter(d => d.propDef.type === "options" filter(d => d.propDef.type === "options"
&& (!d.propDef.options || d.propDef.options.length === 0)), && (!d.propDef.options || d.propDef.options.length === 0)),
each(d => makeError(errors, d.propName)(`${d.propName} does not have any options`)) each(d => makeError(errors, d.propName)(`${d.propName} does not have any options`))
]); ]);
return errors; return errors;
} }

View File

@ -8,7 +8,7 @@ export let _bb;
let rootDiv; let rootDiv;
$:{ $:{
if(_bb && rootDiv && _children && _children.length) if(_bb && rootDiv && _children && _children.length)
_bb.hydrateChildren(_children, theButton); _bb.hydrateChildren(_children, rootDiv);
} }
@ -16,4 +16,3 @@ $:{
<div class="{className}" bind:this={rootDiv}> <div class="{className}" bind:this={rootDiv}>
</div> </div>