Merge branch 'master' of github.com:Budibase/budibase
This commit is contained in:
commit
511834492d
43 changed files with 1164 additions and 1019 deletions
|
@ -9,6 +9,7 @@
|
|||
"bootstrap": "lerna bootstrap",
|
||||
"build": "lerna run build",
|
||||
"initialise": "lerna run initialise",
|
||||
"clean": "lerna clean"
|
||||
"clean": "lerna clean",
|
||||
"dev": "lerna run --parallel --stream dev:builder"
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
8
packages/builder/.vscode/settings.json
vendored
Normal file
8
packages/builder/.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"javascript.format.enable": false,
|
||||
"svelte.plugin.svelte.format.enable": false,
|
||||
"html.format.enable": false,
|
||||
"json.format.enable": false,
|
||||
"editor.trimAutoWhitespace": false,
|
||||
"sass.format.deleteWhitespace": false
|
||||
}
|
|
@ -11,9 +11,10 @@ import { fade } from "svelte/transition";
|
|||
<div class="root">
|
||||
|
||||
<div class="top-nav">
|
||||
<IconButton icon="home"
|
||||
<button class="home-logo"><img src="/assets/budibase-logo-only.png"/></button>
|
||||
<!-- <IconButton icon="home"
|
||||
color="var(--slate)"
|
||||
hoverColor="var(--secondary75)"/>
|
||||
hoverColor="var(--secondary75)"/> -->
|
||||
<span class:active={$store.isBackend}
|
||||
class="topnavitem"
|
||||
on:click={store.showBackend}>
|
||||
|
@ -47,14 +48,18 @@ import { fade } from "svelte/transition";
|
|||
width:100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
}
|
||||
|
||||
.top-nav {
|
||||
flex: 0 0 auto;
|
||||
height: 25px;
|
||||
height: 48px;
|
||||
background: white;
|
||||
padding: 5px;
|
||||
padding: 0px 15px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.content {
|
||||
|
@ -66,14 +71,20 @@ import { fade } from "svelte/transition";
|
|||
.content > div {
|
||||
height:100%;
|
||||
width:100%;
|
||||
|
||||
}
|
||||
|
||||
.topnavitem {
|
||||
cursor: pointer;
|
||||
color: var(--secondary50);
|
||||
padding: 0px 15px;
|
||||
margin: 0px 15px;
|
||||
padding-top: 4px;
|
||||
font-weight: 600;
|
||||
font-size: .9rem;
|
||||
font-size: 1rem;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.topnavitem:hover {
|
||||
|
@ -84,8 +95,31 @@ import { fade } from "svelte/transition";
|
|||
|
||||
.active {
|
||||
color: var(--primary100);
|
||||
font-weight: 900;
|
||||
font-weight: 600;
|
||||
border-bottom: 2px solid var(--primary100);
|
||||
border-top: 2px solid transparent;
|
||||
}
|
||||
|
||||
.home-logo {
|
||||
border-style: none;
|
||||
background-color: rgba(0,0,0,0);
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
height: 40px;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.home-logo:hover {
|
||||
color: var(--hovercolor);
|
||||
}
|
||||
|
||||
.home-logo:active {
|
||||
outline:none;
|
||||
}
|
||||
|
||||
|
||||
.home-logo img {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -10,14 +10,14 @@ import {
|
|||
pipe, getNode, validate,
|
||||
constructHierarchy, templateApi
|
||||
} from "../common/core";
|
||||
import {writable} from "svelte/store";
|
||||
import { writable } from "svelte/store";
|
||||
import { defaultPagesObject } from "../userInterface/pagesParsing/defaultPagesObject"
|
||||
import { buildPropsHierarchy } from "../userInterface/pagesParsing/buildPropsHierarchy"
|
||||
import api from "./api";
|
||||
import { isRootComponent, getExactComponent } from "../userInterface/pagesParsing/searchComponents";
|
||||
import { rename } from "../userInterface/pagesParsing/renameScreen";
|
||||
import {
|
||||
getNewComponentInfo, getScreenInfo
|
||||
getNewComponentInfo, getScreenInfo, getComponentInfo
|
||||
} from "../userInterface/pagesParsing/createProps";
|
||||
import {
|
||||
loadLibs, loadLibUrls, loadGeneratorLibs
|
||||
|
@ -28,30 +28,30 @@ let appname = "";
|
|||
export const getStore = () => {
|
||||
|
||||
const initial = {
|
||||
apps:[],
|
||||
appname:"",
|
||||
apps: [],
|
||||
appname: "",
|
||||
hierarchy: {},
|
||||
actions: [],
|
||||
triggers: [],
|
||||
pages:defaultPagesObject(),
|
||||
mainUi:{},
|
||||
unauthenticatedUi:{},
|
||||
components:[],
|
||||
currentFrontEndItem:null,
|
||||
currentComponentInfo:null,
|
||||
currentFrontEndType:"none",
|
||||
pages: defaultPagesObject(),
|
||||
mainUi: {},
|
||||
unauthenticatedUi: {},
|
||||
components: [],
|
||||
currentFrontEndItem: null,
|
||||
currentComponentInfo: null,
|
||||
currentFrontEndType: "none",
|
||||
currentPageName: "",
|
||||
currentComponentProps:null,
|
||||
currentComponentProps: null,
|
||||
currentNodeIsNew: false,
|
||||
errors: [],
|
||||
activeNav: "database",
|
||||
isBackend:true,
|
||||
isBackend: true,
|
||||
hasAppPackage: false,
|
||||
accessLevels: {version:0, levels:[]},
|
||||
accessLevels: { version: 0, levels: [] },
|
||||
currentNode: null,
|
||||
libraries:null,
|
||||
showSettings:false,
|
||||
useAnalytics:true,
|
||||
libraries: null,
|
||||
showSettings: false,
|
||||
useAnalytics: true,
|
||||
};
|
||||
|
||||
const store = writable(initial);
|
||||
|
@ -82,7 +82,7 @@ export const getStore = () => {
|
|||
store.setCurrentScreen = setCurrentScreen(store);
|
||||
store.setCurrentPage = setCurrentPage(store);
|
||||
store.createScreen = createScreen(store);
|
||||
store.removeComponentLibrary =removeComponentLibrary(store);
|
||||
store.removeComponentLibrary = removeComponentLibrary(store);
|
||||
store.addStylesheet = addStylesheet(store);
|
||||
store.removeStylesheet = removeStylesheet(store);
|
||||
store.savePage = savePage(store);
|
||||
|
@ -91,6 +91,10 @@ export const getStore = () => {
|
|||
store.showSettings = showSettings(store);
|
||||
store.useAnalytics = useAnalytics(store);
|
||||
store.createGeneratedComponents = createGeneratedComponents(store);
|
||||
store.addChildComponent = addChildComponent(store);
|
||||
store.selectComponent = selectComponent(store);
|
||||
store.updateComponentProp = updateComponentProp(store);
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
|
@ -102,7 +106,7 @@ const initialise = (store, initial) => async () => {
|
|||
? last(window.location.hash.substr(1).split("/"))
|
||||
: "";
|
||||
|
||||
if(!appname) {
|
||||
if (!appname) {
|
||||
initial.apps = await api.get(`/_builder/api/apps`).then(r => r.json());
|
||||
initial.hasAppPackage = false;
|
||||
store.set(initial);
|
||||
|
@ -126,14 +130,15 @@ const initialise = (store, initial) => async () => {
|
|||
initial.actions = values(pkg.appDefinition.actions);
|
||||
initial.triggers = pkg.appDefinition.triggers;
|
||||
|
||||
if(!!initial.hierarchy && !isEmpty(initial.hierarchy)) {
|
||||
if (!!initial.hierarchy && !isEmpty(initial.hierarchy)) {
|
||||
initial.hierarchy = constructHierarchy(initial.hierarchy);
|
||||
const shadowHierarchy = createShadowHierarchy(initial.hierarchy);
|
||||
if(initial.currentNode !== null)
|
||||
if (initial.currentNode !== null)
|
||||
initial.currentNode = getNode(
|
||||
shadowHierarchy, initial.currentNode.nodeId
|
||||
);
|
||||
}
|
||||
|
||||
store.set(initial);
|
||||
return initial;
|
||||
}
|
||||
|
@ -224,7 +229,7 @@ const saveCurrentNode = (store) => () => {
|
|||
|
||||
const errors = validate.node(s.currentNode);
|
||||
s.errors = errors;
|
||||
if(errors.length > 0) {
|
||||
if (errors.length > 0) {
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -235,7 +240,7 @@ const saveCurrentNode = (store) => () => {
|
|||
s.hierarchy, s.currentNode.nodeId);
|
||||
|
||||
let index = parentNode.children.length;
|
||||
if(!!existingNode) {
|
||||
if (!!existingNode) {
|
||||
// remove existing
|
||||
index = existingNode.parent().children.indexOf(existingNode);
|
||||
existingNode.parent().children = pipe(existingNode.parent().children, [
|
||||
|
@ -251,7 +256,7 @@ const saveCurrentNode = (store) => () => {
|
|||
);
|
||||
|
||||
const newIndexOfchild = child => {
|
||||
if(child === cloned) return index;
|
||||
if (child === cloned) return index;
|
||||
const currentIndex = parentNode.children.indexOf(child);
|
||||
return currentIndex >= index ? currentIndex + 1 : currentIndex;
|
||||
}
|
||||
|
@ -260,7 +265,7 @@ const saveCurrentNode = (store) => () => {
|
|||
sortBy(newIndexOfchild)
|
||||
]);
|
||||
|
||||
if(!existingNode && s.currentNode.type === "record") {
|
||||
if (!existingNode && s.currentNode.type === "record") {
|
||||
const defaultIndex = templateApi(s.hierarchy)
|
||||
.getNewIndexTemplate(cloned.parent());
|
||||
defaultIndex.name = `all_${cloned.collectionName}`;
|
||||
|
@ -295,7 +300,7 @@ const deleteCurrentNode = store => () => {
|
|||
? find(n => n != s.currentNode)
|
||||
(s.hierarchy.children)
|
||||
: nodeToDelete.parent();
|
||||
if(hierarchyFunctions.isRecord(nodeToDelete)) {
|
||||
if (hierarchyFunctions.isRecord(nodeToDelete)) {
|
||||
nodeToDelete.parent().children = filter(c => c.nodeId !== nodeToDelete.nodeId)
|
||||
(nodeToDelete.parent().children);
|
||||
} else {
|
||||
|
@ -329,14 +334,14 @@ const deleteField = databaseStore => field => {
|
|||
}
|
||||
|
||||
|
||||
const saveAction = store => (newAction, isNew, oldAction=null) => {
|
||||
const saveAction = store => (newAction, isNew, oldAction = null) => {
|
||||
store.update(s => {
|
||||
|
||||
const existingAction = isNew
|
||||
? null
|
||||
: find(a => a.name === oldAction.name)(s.actions);
|
||||
|
||||
if(existingAction) {
|
||||
if (existingAction) {
|
||||
s.actions = pipe(s.actions, [
|
||||
map(a => a === existingAction ? newAction : a)
|
||||
]);
|
||||
|
@ -356,14 +361,14 @@ const deleteAction = store => action => {
|
|||
});
|
||||
}
|
||||
|
||||
const saveTrigger = store => (newTrigger, isNew, oldTrigger=null) => {
|
||||
const saveTrigger = store => (newTrigger, isNew, oldTrigger = null) => {
|
||||
store.update(s => {
|
||||
|
||||
const existingTrigger = isNew
|
||||
? null
|
||||
: find(a => a.name === oldTrigger.name)(s.triggers);
|
||||
|
||||
if(existingTrigger) {
|
||||
if (existingTrigger) {
|
||||
s.triggers = pipe(s.triggers, [
|
||||
map(a => a === existingTrigger ? newTrigger : a)
|
||||
]);
|
||||
|
@ -385,7 +390,7 @@ const deleteTrigger = store => trigger => {
|
|||
const incrementAccessLevelsVersion = (s) =>
|
||||
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 => {
|
||||
|
||||
const levels = s.accessLevels.levels;
|
||||
|
@ -394,7 +399,7 @@ const saveLevel = store => (newLevel, isNew, oldLevel=null) => {
|
|||
? null
|
||||
: find(a => a.name === oldLevel.name)(levels);
|
||||
|
||||
if(existingLevel) {
|
||||
if (existingLevel) {
|
||||
s.accessLevels.levels = pipe(levels, [
|
||||
map(a => a === existingLevel ? newLevel : a)
|
||||
]);
|
||||
|
@ -470,7 +475,7 @@ const createGeneratedComponents = store => components => {
|
|||
s.screens = [...s.screens, ...components];
|
||||
|
||||
const doCreate = async () => {
|
||||
for(let c of components) {
|
||||
for (let c of components) {
|
||||
await api.post(`/_builder/api/${s.appname}/screen`, c);
|
||||
}
|
||||
|
||||
|
@ -496,7 +501,7 @@ const deleteScreen = store => name => {
|
|||
|
||||
s.components = components;
|
||||
s.screens = screens;
|
||||
if(s.currentFrontEndItem.name === name) {
|
||||
if (s.currentFrontEndItem.name === name) {
|
||||
s.currentFrontEndItem = null;
|
||||
s.currentFrontEndType = "";
|
||||
}
|
||||
|
@ -514,18 +519,18 @@ const renameScreen = store => (oldname, newname) => {
|
|||
screens, pages, error, changedScreens
|
||||
} = rename(s.pages, s.screens, oldname, newname);
|
||||
|
||||
if(error) {
|
||||
if (error) {
|
||||
// should really do something with this
|
||||
return s;
|
||||
}
|
||||
|
||||
s.screens = screens;
|
||||
s.pages = pages;
|
||||
if(s.currentFrontEndItem.name === oldname)
|
||||
if (s.currentFrontEndItem.name === oldname)
|
||||
s.currentFrontEndItem.name = newname;
|
||||
|
||||
const saveAllChanged = async () => {
|
||||
for(let screenName of changedScreens) {
|
||||
for (let screenName of changedScreens) {
|
||||
const changedScreen
|
||||
= getExactComponent(screens, screenName);
|
||||
await api.post(`/_builder/api/${s.appname}/screen`, changedScreen);
|
||||
|
@ -546,7 +551,7 @@ const renameScreen = store => (oldname, newname) => {
|
|||
|
||||
const savePage = store => async page => {
|
||||
store.update(s => {
|
||||
if(s.currentFrontEndType !== "page" || !s.currentPageName) {
|
||||
if (s.currentFrontEndType !== "page" || !s.currentPageName) {
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -559,7 +564,7 @@ const savePage = store => async page => {
|
|||
const addComponentLibrary = store => async lib => {
|
||||
|
||||
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;
|
||||
|
||||
|
@ -574,10 +579,10 @@ const addComponentLibrary = store => async lib => {
|
|||
: [];
|
||||
|
||||
store.update(s => {
|
||||
if(success) {
|
||||
if (success) {
|
||||
|
||||
const componentsArray = [];
|
||||
for(let c in components) {
|
||||
for (let c in components) {
|
||||
componentsArray.push(components[c]);
|
||||
}
|
||||
|
||||
|
@ -632,7 +637,7 @@ const refreshComponents = store => async () => {
|
|||
|
||||
const components = pipe(componentsAndGenerators.components, [
|
||||
keys,
|
||||
map(k => ({...componentsAndGenerators[k], name:k}))
|
||||
map(k => ({ ...componentsAndGenerators[k], name: k }))
|
||||
]);
|
||||
|
||||
store.update(s => {
|
||||
|
@ -648,8 +653,8 @@ const refreshComponents = store => async () => {
|
|||
const savePackage = (store, s) => {
|
||||
|
||||
const appDefinition = {
|
||||
hierarchy:s.hierarchy,
|
||||
triggers:s.triggers,
|
||||
hierarchy: s.hierarchy,
|
||||
triggers: s.triggers,
|
||||
actions: keyBy("name")(s.actions),
|
||||
props: {
|
||||
main: buildPropsHierarchy(
|
||||
|
@ -665,8 +670,8 @@ const savePackage = (store, s) => {
|
|||
|
||||
const data = {
|
||||
appDefinition,
|
||||
accessLevels:s.accessLevels,
|
||||
pages:s.pages,
|
||||
accessLevels: s.accessLevels,
|
||||
pages: s.pages,
|
||||
}
|
||||
|
||||
return api.post(`/_builder/api/${s.appname}/appPackage`, data);
|
||||
|
@ -689,3 +694,44 @@ const setCurrentPage = store => pageName => {
|
|||
return s;
|
||||
})
|
||||
}
|
||||
|
||||
const addChildComponent = store => component => {
|
||||
|
||||
store.update(s => {
|
||||
const newComponent = getNewComponentInfo(
|
||||
s.components, component);
|
||||
|
||||
const children = s.currentFrontEndItem.props._children;
|
||||
|
||||
const component_definition = Object.assign(
|
||||
cloneDeep(newComponent.fullProps), {
|
||||
_component: component,
|
||||
})
|
||||
|
||||
s.currentFrontEndItem.props._children =
|
||||
children ?
|
||||
children.concat(component_definition) :
|
||||
[component_definition];
|
||||
|
||||
return s;
|
||||
})
|
||||
}
|
||||
|
||||
const selectComponent = store => component => {
|
||||
store.update(s => {
|
||||
s.currentComponentInfo = component;
|
||||
return s;
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
const updateComponentProp = store => (name, value) => {
|
||||
store.update(s => {
|
||||
const current_component = s.currentComponentInfo;
|
||||
s.currentComponentInfo[name] = value;
|
||||
_saveScreen(store, s, s.currentFrontEndItem);
|
||||
s.currentComponentInfo = current_component;
|
||||
return s;
|
||||
})
|
||||
|
||||
}
|
||||
|
|
4
packages/builder/src/common/Icons/Image.svelte
Normal file
4
packages/builder/src/common/Icons/Image.svelte
Normal 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 |
4
packages/builder/src/common/Icons/Input.svelte
Normal file
4
packages/builder/src/common/Icons/Input.svelte
Normal 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 |
3
packages/builder/src/common/Icons/Layout.svelte
Normal file
3
packages/builder/src/common/Icons/Layout.svelte
Normal file
|
@ -0,0 +1,3 @@
|
|||
<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="M21 20a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16zM11 5H5v14h6V5zm8 8h-6v6h6v-6zm0-8h-6v6h6V5z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 280 B |
3
packages/builder/src/common/Icons/Paint.svelte
Normal file
3
packages/builder/src/common/Icons/Paint.svelte
Normal file
|
@ -0,0 +1,3 @@
|
|||
<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="M19.228 18.732l1.768-1.768 1.767 1.768a2.5 2.5 0 1 1-3.535 0zM8.878 1.08l11.314 11.313a1 1 0 0 1 0 1.415l-8.485 8.485a1 1 0 0 1-1.414 0l-8.485-8.485a1 1 0 0 1 0-1.415l7.778-7.778-2.122-2.121L8.88 1.08zM11 6.03L3.929 13.1 11 20.173l7.071-7.071L11 6.029z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 415 B |
4
packages/builder/src/common/Icons/Terminal.svelte
Normal file
4
packages/builder/src/common/Icons/Terminal.svelte
Normal 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="M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm1 2v14h16V5H4zm8 10h6v2h-6v-2zm-3.333-3L5.838 9.172l1.415-1.415L11.495 12l-4.242 4.243-1.415-1.415L8.667 12z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 346 B |
5
packages/builder/src/common/Icons/index.js
Normal file
5
packages/builder/src/common/Icons/index.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
export { default as LayoutIcon } from './Layout.svelte';
|
||||
export { default as PaintIcon } from './Paint.svelte';
|
||||
export { default as TerminalIcon } from './Terminal.svelte';
|
||||
export { default as InputIcon } from './Input.svelte';
|
||||
export { default as ImageIcon } from './Image.svelte';
|
54
packages/builder/src/common/Inputs/InputGroup.svelte
Normal file
54
packages/builder/src/common/Inputs/InputGroup.svelte
Normal file
|
@ -0,0 +1,54 @@
|
|||
<script>
|
||||
export let meta = [];
|
||||
export let size = '';
|
||||
export let values = [];
|
||||
</script>
|
||||
|
||||
<div class="inputs {size}">
|
||||
{#each meta as { placeholder }, i}
|
||||
<input type="number" placeholder="{placeholder}" bind:value={values[i]}/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.inputs {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
}
|
||||
|
||||
input {
|
||||
width: 83px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #163057;
|
||||
opacity: 0.7;
|
||||
padding: 5px 10px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #DBDBDB;
|
||||
border-radius: 2px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type=number]::-webkit-inner-spin-button,
|
||||
input[type=number]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.small > input {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.small > input::placeholder {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
|
@ -1,5 +1,5 @@
|
|||
:root {
|
||||
--primary100: #454CA0FF;
|
||||
--primary100: #173157FF;
|
||||
--primary75: #454CA0BF;
|
||||
--primary50: #454CA080;
|
||||
--primary25: #454CA040;
|
||||
|
@ -7,12 +7,12 @@
|
|||
--primary5: #454ca00c;
|
||||
--primarydark: #3F448A;
|
||||
|
||||
--secondary100: #162B4DFF;
|
||||
--secondary100:#828fa5;
|
||||
--secondary75: #162B4DBF;
|
||||
--secondary50: #162B4D80;
|
||||
--secondary25: #162B4D40;
|
||||
--secondary10: #162B4D1A;
|
||||
--secondary5: rgba(22, 43, 77, 0.068);
|
||||
--secondary5:#fff;
|
||||
--secondarydark: #3F448A;
|
||||
|
||||
--tertiary: #F2F5F7;
|
||||
|
@ -52,6 +52,9 @@
|
|||
--heavybodytext: var(--fontbold) "regular" var(--secondary100) 16pt;
|
||||
--quotation: var(--fontnormal) "italics" var(--darkslate) 16pt;
|
||||
--smallheavybodytext: var(--fontbold) "regular" var(--secondary100) 14pt;
|
||||
|
||||
--background-button: #e6eeff;
|
||||
--button-text: #0055ff;
|
||||
}
|
||||
|
||||
html, body {
|
||||
|
|
119
packages/builder/src/userInterface/CodeEditor.svelte
Normal file
119
packages/builder/src/userInterface/CodeEditor.svelte
Normal file
|
@ -0,0 +1,119 @@
|
|||
<script>
|
||||
let snippets = [];
|
||||
let current_snippet = 0;
|
||||
let snippet_text = ''
|
||||
let id = 0;
|
||||
|
||||
function save_snippet() {
|
||||
if (!snippet_text) return;
|
||||
|
||||
const index = snippets.findIndex(({ id }) => current_snippet === id);
|
||||
|
||||
if (index > -1) {
|
||||
snippets[index].snippet = snippet_text;
|
||||
} else {
|
||||
snippets = snippets.concat({ snippet: snippet_text , id: id });
|
||||
}
|
||||
snippet_text = '';
|
||||
current_snippet = ++id;
|
||||
}
|
||||
|
||||
function edit_snippet(id) {
|
||||
const { snippet, id: _id } = snippets.find(({ id:_id }) => _id === id);
|
||||
current_snippet = id
|
||||
snippet_text = snippet;
|
||||
}
|
||||
</script>
|
||||
|
||||
<h3>Code</h3>
|
||||
|
||||
<p>Use the code box below to add snippets of javascript to enhance your webapp</p>
|
||||
|
||||
<div class="editor">
|
||||
<textarea class="code" bind:value={snippet_text} />
|
||||
<button on:click={save_snippet}>Save</button>
|
||||
</div>
|
||||
|
||||
<div class="snippets">
|
||||
<h3>Snippets added</h3>
|
||||
{#each snippets as { id, snippet } }
|
||||
<div class="snippet">
|
||||
<pre class="code">{snippet}</pre>
|
||||
<button on:click={() => edit_snippet(id)}>Edit</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
h3 {
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #8997ab;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.editor {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.code {
|
||||
width: 100%;
|
||||
outline: none;
|
||||
border: none;
|
||||
background: #173157;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
white-space: pre;
|
||||
color: #eee;
|
||||
padding: 10px;
|
||||
font-family: monospace;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.editor textarea {
|
||||
resize: none;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
button {
|
||||
position: absolute;
|
||||
box-shadow: 0 0 black;
|
||||
color: #eee;
|
||||
right: 5px;
|
||||
bottom: 10px;
|
||||
background: none;
|
||||
border: none;
|
||||
text-transform: uppercase;
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.snippets {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.snippet {
|
||||
position: relative;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.snippet pre {
|
||||
background: #f9f9f9;
|
||||
color: #333;
|
||||
max-height: 150px;
|
||||
}
|
||||
|
||||
.snippet button {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
</style>
|
150
packages/builder/src/userInterface/ComponentPanel.svelte
Normal file
150
packages/builder/src/userInterface/ComponentPanel.svelte
Normal file
|
@ -0,0 +1,150 @@
|
|||
<script>
|
||||
|
||||
import PropsView from "./PropsView.svelte";
|
||||
import { store } from "../builderStore";
|
||||
import { isRootComponent } from "./pagesParsing/searchComponents";
|
||||
import IconButton from "../common/IconButton.svelte";
|
||||
import Textbox from "../common/Textbox.svelte";
|
||||
import { pipe } from "../common/core";
|
||||
import {
|
||||
getScreenInfo
|
||||
} from "./pagesParsing/createProps";
|
||||
import { LayoutIcon, PaintIcon, TerminalIcon } from '../common/Icons/';
|
||||
import CodeEditor from './CodeEditor.svelte';
|
||||
import LayoutEditor from './LayoutEditor.svelte';
|
||||
|
||||
import {
|
||||
cloneDeep,
|
||||
join,
|
||||
split,
|
||||
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>
|
||||
|
||||
<div class="root">
|
||||
<ul>
|
||||
<li>
|
||||
<button class:selected={current_view === 'props'} on:click={() => current_view = 'props'}>
|
||||
<PaintIcon />
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class:selected={current_view === 'layout'} on:click={() => current_view = 'layout'}>
|
||||
<LayoutIcon />
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class:selected={current_view === 'code'} on:click={() => current_view = 'code'}>
|
||||
<TerminalIcon />
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{#if !componentInfo.component}
|
||||
<div class="component-props-container">
|
||||
|
||||
{#if current_view === 'props'}
|
||||
<PropsView {componentInfo} {components} {onPropsChanged} />
|
||||
{:else if current_view === 'layout'}
|
||||
<LayoutEditor />
|
||||
{:else}
|
||||
<CodeEditor />
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
{:else}
|
||||
<h1> This is a screen, this will be dealt with later</h1>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
|
||||
.root {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
}
|
||||
|
||||
.title > div:nth-child(1) {
|
||||
grid-column-start: name;
|
||||
color: var(--secondary100);
|
||||
}
|
||||
|
||||
.title > div:nth-child(2) {
|
||||
grid-column-start: actions;
|
||||
}
|
||||
|
||||
.component-props-container {
|
||||
margin-top: 10px;
|
||||
flex: 1 1 auto;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
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>
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import ComponentsHierarchyChildren from './ComponentsHierarchyChildren.svelte';
|
||||
|
||||
import {
|
||||
last,
|
||||
|
@ -18,13 +19,6 @@ import getIcon from "../common/icon";
|
|||
import { store } from "../builderStore";
|
||||
|
||||
export let components = []
|
||||
export let thisLevel = "";
|
||||
|
||||
let pathPartsThisLevel;
|
||||
let componentsThisLevel;
|
||||
let subfolders;
|
||||
|
||||
let expandedFolders = [];
|
||||
|
||||
const joinPath = join("/");
|
||||
|
||||
|
@ -35,39 +29,8 @@ const normalizedName = name => pipe(name, [
|
|||
trimChars(" ")
|
||||
]);
|
||||
|
||||
|
||||
const isOnThisLevel = (c) =>
|
||||
normalizedName(c.name).split("/").length === pathPartsThisLevel
|
||||
&&
|
||||
(!thisLevel || normalizedName(c.name).startsWith(normalizedName(thisLevel)));
|
||||
|
||||
const notOnThisLevel = (c) => !isOnThisLevel(c);
|
||||
|
||||
const isInSubfolder = (subfolder, c) =>
|
||||
normalizedName(c.name).startsWith(
|
||||
trimCharsStart("/")(
|
||||
joinPath([thisLevel, subfolder])));
|
||||
|
||||
const isOnNextLevel = (c) =>
|
||||
normalizedName(c.name).split("/").length === pathPartsThisLevel + 1
|
||||
|
||||
const lastPartOfName = (c) =>
|
||||
last(c.name.split("/"))
|
||||
|
||||
const subFolder = (c) => {
|
||||
const cname = normalizedName(c.name);
|
||||
const folderName = cname.substring(thisLevel.length, cname.length).split("/")[0];
|
||||
|
||||
return ({
|
||||
name: folderName,
|
||||
isExpanded: includes(folderName)(expandedFolders),
|
||||
path: thisLevel + "/" + folderName
|
||||
});
|
||||
}
|
||||
|
||||
const subComponents = (subfolder) => pipe(components, [
|
||||
filter(c => isInSubfolder(subfolder, c))
|
||||
]);
|
||||
last(c.name ? c.name.split("/") : c._component.split("/"))
|
||||
|
||||
const expandFolder = folder => {
|
||||
const expandedFolder = {...folder};
|
||||
|
@ -97,49 +60,33 @@ const isFolderSelected = (current, folder) =>
|
|||
|
||||
|
||||
|
||||
$: {
|
||||
pathPartsThisLevel = !thisLevel
|
||||
? 1
|
||||
: normalizedName(thisLevel).split("/").length + 1;
|
||||
|
||||
componentsThisLevel =
|
||||
$: _components =
|
||||
pipe(components, [
|
||||
filter(isOnThisLevel),
|
||||
map(c => ({component:c, title:lastPartOfName(c)})),
|
||||
map(c => ({component: c, title:lastPartOfName(c)})),
|
||||
sortBy("title")
|
||||
]);
|
||||
|
||||
subfolders =
|
||||
pipe(components, [
|
||||
filter(notOnThisLevel),
|
||||
sortBy("name"),
|
||||
map(subFolder),
|
||||
uniqWith((f1,f2) => f1.path === f2.path)
|
||||
]);
|
||||
function select_component(screen, component) {
|
||||
store.setCurrentScreen(screen);
|
||||
store.selectComponent(component);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="root" style={`padding-left: calc(10px * ${pathPartsThisLevel})`}>
|
||||
<div class="root">
|
||||
|
||||
{#each subfolders as folder}
|
||||
<div class="hierarchy-item folder"
|
||||
on:click|stopPropagation={() => expandFolder(folder)}>
|
||||
<span>{@html getIcon(folder.isExpanded ? "chevron-down" : "chevron-right", "16")}</span>
|
||||
<span class="title" class:currentfolder={$store.currentFrontEndItem && isInSubfolder(folder.name, $store.currentFrontEndItem)}>{folder.name}</span>
|
||||
{#if folder.isExpanded}
|
||||
<svelte:self components={subComponents(folder.name)}
|
||||
thisLevel={folder.path} />
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
{#each componentsThisLevel as component}
|
||||
<div class="hierarchy-item component" class:selected={isComponentSelected($store.currentFrontEndType, $store.currentFrontEndItem, component.component)}
|
||||
{#each _components as component}
|
||||
<div class="hierarchy-item component"
|
||||
class:selected={isComponentSelected($store.currentFrontEndType, $store.currentFrontEndItem, component.component)}
|
||||
on:click|stopPropagation={() => store.setCurrentScreen(component.component.name)}>
|
||||
<span>{@html getIcon("circle", "7")}</span>
|
||||
|
||||
<span class="title">{component.title}</span>
|
||||
</div>
|
||||
{#if component.component.props && component.component.props._children}
|
||||
<ComponentsHierarchyChildren components={component.component.props._children}
|
||||
onSelect={child => select_component(component.component.name, child)} />
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
</div>
|
||||
|
@ -147,31 +94,33 @@ $: {
|
|||
<style>
|
||||
|
||||
.root {
|
||||
color: var(--secondary50);
|
||||
font-size: .9rem;
|
||||
font-weight: bold;
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
color: #828fa5;
|
||||
}
|
||||
|
||||
.hierarchy-item {
|
||||
cursor: pointer;
|
||||
padding: 5px 0px;
|
||||
padding: 11px 7px;
|
||||
|
||||
margin: 5px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.hierarchy-item:hover {
|
||||
color: var(--secondary);
|
||||
/* color: var(--secondary); */
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.component {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
|
||||
.currentfolder {
|
||||
color: var(--secondary100);
|
||||
}
|
||||
|
||||
.selected {
|
||||
color: var(--primary100);
|
||||
font-weight: bold;
|
||||
color: var(--button-text);
|
||||
background: var(--background-button)!important;
|
||||
}
|
||||
|
||||
.title {
|
||||
|
|
|
@ -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._component)}
|
||||
|
||||
{#if component._children}
|
||||
<svelte:self components={component._children}/>
|
||||
{/if}
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
{/each}
|
|
@ -8,6 +8,7 @@ import {
|
|||
groupBy, keys, find, sortBy
|
||||
} from "lodash/fp";
|
||||
import { pipe } from "../common/core";
|
||||
import { ImageIcon, InputIcon, LayoutIcon } from '../common/Icons/';
|
||||
|
||||
let componentLibraries=[];
|
||||
|
||||
|
@ -29,9 +30,7 @@ const addRootComponent = (c, all) => {
|
|||
|
||||
};
|
||||
|
||||
const onComponentChosen = (component) => {
|
||||
|
||||
};
|
||||
const onComponentChosen = store.addChildComponent;
|
||||
|
||||
store.subscribe(s => {
|
||||
|
||||
|
@ -46,9 +45,7 @@ store.subscribe(s => {
|
|||
componentLibraries = newComponentLibraries;
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
let current_view = 'text';
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -59,22 +56,31 @@ store.subscribe(s => {
|
|||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
<div class="inner-header">
|
||||
Components
|
||||
</div>
|
||||
|
||||
{#each lib.components as component}
|
||||
{#each lib.components.filter(_ => true) as component}
|
||||
|
||||
<div class="component"
|
||||
on:click={() => onComponentChosen(component)}>
|
||||
on:click={() => onComponentChosen(component.name)}>
|
||||
<div class="name">
|
||||
{splitName(component.name).componentName}
|
||||
</div>
|
||||
<div class="description">
|
||||
{component.description}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/each}
|
||||
|
@ -117,8 +123,16 @@ store.subscribe(s => {
|
|||
}
|
||||
|
||||
.component {
|
||||
padding: 2px 0px;
|
||||
padding: 0 15px;
|
||||
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 {
|
||||
|
@ -126,8 +140,11 @@ store.subscribe(s => {
|
|||
}
|
||||
|
||||
.component > .name {
|
||||
color: var(--secondary100);
|
||||
color: #163057;
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.component > .description {
|
||||
|
@ -137,6 +154,34 @@ store.subscribe(s => {
|
|||
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>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import EditComponentProps from "./EditComponentProps.svelte";
|
||||
import ComponentPanel from "./ComponentPanel.svelte";
|
||||
import ComponentsList from "./ComponentsList.svelte";
|
||||
|
||||
let selected="properties";
|
||||
|
@ -33,7 +33,7 @@ const selectTab = tab =>
|
|||
|
||||
<div class="panel">
|
||||
{#if selected==="properties"}
|
||||
<EditComponentProps />
|
||||
<ComponentPanel />
|
||||
{/if}
|
||||
|
||||
{#if selected==="components"}
|
||||
|
@ -50,24 +50,29 @@ const selectTab = tab =>
|
|||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 2rem 1.5rem 2rem 1.5rem;
|
||||
}
|
||||
|
||||
.switcher {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.switcher > button {
|
||||
display: inline-block;
|
||||
background-color: rgba(0,0,0,0);
|
||||
border-style: solid;
|
||||
border-color: var(--slate);
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
text-transform: uppercase;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.switcher > .selected {
|
||||
background-color: red;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.panel {
|
||||
|
|
|
@ -52,7 +52,8 @@ store.subscribe(s => {
|
|||
window["##BUDIBASE_APPDEFINITION##"] = ${JSON.stringify(appDefinition)};
|
||||
import('/_builder/budibase-client.esm.mjs')
|
||||
.then(module => {
|
||||
module.loadBudibase();
|
||||
console.log(module, window);
|
||||
module.loadBudibase({ window, localStorage });
|
||||
})
|
||||
</script>
|
||||
<style>
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
} from "./pagesParsing/createProps";
|
||||
import Button from "../common/Button.svelte";
|
||||
import ButtonGroup from "../common/ButtonGroup.svelte";
|
||||
import { LayoutIcon, PaintIcon, TerminalIcon } from '../common/Icons/';
|
||||
|
||||
import {
|
||||
cloneDeep,
|
||||
|
@ -50,43 +51,6 @@ store.subscribe(s => {
|
|||
components = s.components;
|
||||
});
|
||||
|
||||
const save = () => {
|
||||
|
||||
ignoreStore = true;
|
||||
if(!validate()) {
|
||||
ignoreStore = false;
|
||||
return;
|
||||
}
|
||||
|
||||
component.name = originalName || name;
|
||||
component.description = description;
|
||||
component.tags = pipe(tagsString, [
|
||||
split(","),
|
||||
map(s => s.trim())
|
||||
]);
|
||||
|
||||
store.saveScreen(component);
|
||||
|
||||
ignoreStore = false;
|
||||
// now do the rename
|
||||
if(name !== originalName) {
|
||||
store.renameScreen(originalName, name);
|
||||
}
|
||||
}
|
||||
|
||||
const deleteComponent = () => {
|
||||
showDialog();
|
||||
}
|
||||
|
||||
const confirmDeleteComponent = () => {
|
||||
store.deleteScreen(component.name);
|
||||
hideDialog();
|
||||
}
|
||||
|
||||
const onPropsValidate = result => {
|
||||
propsValidationErrors = result;
|
||||
}
|
||||
|
||||
const updateComponent = doChange => {
|
||||
const newComponent = cloneDeep(component);
|
||||
doChange(newComponent);
|
||||
|
@ -97,51 +61,22 @@ const updateComponent = doChange => {
|
|||
const onPropsChanged = newProps => {
|
||||
updateComponent(newComponent =>
|
||||
assign(newComponent.props, newProps));
|
||||
|
||||
}
|
||||
|
||||
const validate = () => {
|
||||
const fieldInvalid = (field, err) =>
|
||||
errors[field] = err;
|
||||
const fieldValid = field =>
|
||||
errors[field] && delete errors[field];
|
||||
|
||||
if(!name) nameInvalid = "component name i not supplied";
|
||||
else nameInvalid = "";
|
||||
|
||||
return (!nameInvalid && propsValidationErrors.length === 0);
|
||||
}
|
||||
|
||||
const hideDialog = () => {
|
||||
UIkit.modal(modalElement).hide();
|
||||
}
|
||||
|
||||
const showDialog = () => {
|
||||
UIkit.modal(modalElement).show();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
|
||||
<div class="title">
|
||||
<div>{shortName}</div>
|
||||
<div>
|
||||
<IconButton icon="save"
|
||||
on:click={save}
|
||||
color="var(--secondary100)"
|
||||
hoverColor="var(--primary100)"/>
|
||||
<IconButton icon="trash"
|
||||
on:click={deleteComponent}
|
||||
color="var(--secondary100)"
|
||||
hoverColor="var(--primary100)"/>
|
||||
</div>
|
||||
</div>
|
||||
<ul>
|
||||
<li><button><PaintIcon /></button></li>
|
||||
<li><button><LayoutIcon /></button></li>
|
||||
<li><button><TerminalIcon /></button></li>
|
||||
</ul>
|
||||
|
||||
<div class="component-props-container">
|
||||
|
||||
|
||||
<PropsView onValidate={onPropsValidate}
|
||||
<PropsView
|
||||
{componentInfo}
|
||||
{onPropsChanged} />
|
||||
|
||||
|
@ -150,45 +85,13 @@ const showDialog = () => {
|
|||
|
||||
</div>
|
||||
|
||||
|
||||
<div bind:this={modalElement} uk-modal>
|
||||
<div class="uk-modal-dialog">
|
||||
|
||||
<div class="uk-modal-header">
|
||||
Delete {name} ?
|
||||
</div>
|
||||
|
||||
<div class="uk-modal-body">
|
||||
Are you sure you want to delete this component ?
|
||||
</div>
|
||||
|
||||
<div class="uk-modal-footer">
|
||||
<ButtonGroup>
|
||||
<Button grouped
|
||||
on:click={confirmDeleteComponent}>
|
||||
OK
|
||||
</Button>
|
||||
<Button grouped
|
||||
on:click={hideDialog}
|
||||
color="secondary" >
|
||||
Cancel
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
.root {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-style: solid;
|
||||
border-width: 1px 0 0 0;
|
||||
border-color: var(--slate);
|
||||
|
||||
}
|
||||
|
||||
.title {
|
||||
|
@ -213,4 +116,32 @@ const showDialog = () => {
|
|||
flex: 1 1 auto;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-right: 20px;
|
||||
background: none;
|
||||
border-radius: 5px;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
li button {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background: lightblue;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -5,6 +5,7 @@ import {
|
|||
filter
|
||||
} from "lodash/fp";
|
||||
import {EVENT_TYPE_MEMBER_NAME} from "../common/eventHandlers";
|
||||
|
||||
export let parentProps;
|
||||
export let propDef;
|
||||
export let onValueChanged;
|
||||
|
|
116
packages/builder/src/userInterface/LayoutEditor.svelte
Normal file
116
packages/builder/src/userInterface/LayoutEditor.svelte
Normal file
|
@ -0,0 +1,116 @@
|
|||
<script>
|
||||
import InputGroup from '../common/Inputs/InputGroup.svelte';
|
||||
|
||||
let grid_values = ['', '', '', ''];
|
||||
let column_values = ['', ''];
|
||||
let row_values = ['', ''];
|
||||
let gap_values = [''];
|
||||
let margin_values = ['', '', '', ''];
|
||||
let padding_values = ['', '', '', ''];
|
||||
let zindex_values = [''];
|
||||
|
||||
const tbrl = [
|
||||
{ placeholder: 'T' },
|
||||
{ placeholder: 'R' },
|
||||
{ placeholder: 'B' },
|
||||
{ placeholder: 'L' }
|
||||
];
|
||||
|
||||
const se = [
|
||||
{ placeholder: 'START' },
|
||||
{ placeholder: 'END' },
|
||||
]
|
||||
|
||||
const single = [{ placeholder: '' }];
|
||||
</script>
|
||||
|
||||
|
||||
<h3>Layout</h3>
|
||||
|
||||
<h4>Positioning</h4>
|
||||
|
||||
<div class="layout-pos">
|
||||
<div class="grid">
|
||||
<h5>Grid Area:</h5>
|
||||
<InputGroup meta={tbrl} bind:values={grid_values} size="small"/>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<h5>Column:</h5>
|
||||
<InputGroup meta={se} bind:values={column_values} />
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<h4>Spacing</h4>
|
||||
|
||||
<div class="layout-spacing">
|
||||
<div class="grid">
|
||||
<h5>Margin:</h5>
|
||||
<InputGroup meta={tbrl} bind:values={margin_values} size="small"/>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<h5>Padding:</h5>
|
||||
<InputGroup meta={tbrl} bind:values={padding_values} size="small"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4>Z-Index</h4>
|
||||
|
||||
<div class="layout-layer">
|
||||
<div class="grid">
|
||||
<h5>Z-Index:</h5>
|
||||
<InputGroup meta={single} bind:values={zindex_values}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
h3 {
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #8997ab;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
text-transform: uppercase;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: #163057;
|
||||
opacity: 0.3;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #163057;
|
||||
opacity: 0.6;
|
||||
padding-top: 12px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div > div {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr;
|
||||
grid-gap: 10px;
|
||||
height: 40px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: 70px 1fr;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -29,7 +29,6 @@ let name="";
|
|||
let saveAttempted=false;
|
||||
|
||||
store.subscribe(s => {
|
||||
|
||||
layoutComponents = pipe(s.components, [
|
||||
filter(c => c.container),
|
||||
map(c => ({name:c.name, ...splitName(c.name)}))
|
||||
|
|
|
@ -7,54 +7,70 @@ const getPage = (s, name) => {
|
|||
return ({name, props});
|
||||
}
|
||||
|
||||
const pages = [{
|
||||
title: 'Main',
|
||||
id: 'main'
|
||||
}, {
|
||||
title: 'Login',
|
||||
id: 'unauthenticated'
|
||||
}]
|
||||
|
||||
store.setCurrentPage('main')
|
||||
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
<div class="hierarchy-item component" class:selected={$store.currentFrontEndType === "page" && $store.currentPageName === "main"}
|
||||
on:click|stopPropagation={() => store.setCurrentPage("main")}>
|
||||
<span>{@html getIcon("circle", "7")}</span>
|
||||
<span class="title">Main</span>
|
||||
</div>
|
||||
<select id="page" name="select" on:change={({target}) => store.setCurrentPage(target.value)}>
|
||||
|
||||
<div class="hierarchy-item component" class:selected={$store.currentFrontEndType === "page" && $store.currentPageName === "unauthenticated"}
|
||||
on:click|stopPropagation={() => store.setCurrentPage("unauthenticated")}>
|
||||
<span>{@html getIcon("circle", "7")}</span>
|
||||
<span class="title">Login</span>
|
||||
</div>
|
||||
{#each pages as {title, id}}
|
||||
<option value="{id}">Page: {title}</option>
|
||||
{/each}
|
||||
|
||||
</select>
|
||||
<span class="arrow">{@html getIcon("chevron-down","24")}</span>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
.root {
|
||||
padding-bottom: 10px;
|
||||
padding-left: 10px;
|
||||
font-size: .9rem;
|
||||
color: var(--secondary50);
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hierarchy-item {
|
||||
cursor: pointer;
|
||||
padding: 5px 0px;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
.hierarchy-item:hover {
|
||||
color: var(--secondary100);
|
||||
}
|
||||
|
||||
.component {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.selected {
|
||||
.arrow {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
pointer-events: none;
|
||||
color: var(--primary100);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.title {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -6,13 +6,12 @@ import Dropdown from "../common/Dropdown.svelte";
|
|||
import EventListSelector from "./EventListSelector.svelte";
|
||||
import StateBindingControl from "./StateBindingControl.svelte";
|
||||
|
||||
export let errors = [];
|
||||
export let setProp = () => {};
|
||||
export let fieldHasError =() => {};
|
||||
export let propDef = {};
|
||||
export let props = {};
|
||||
export let disabled;
|
||||
export let index;
|
||||
export let prop_name;
|
||||
export let prop_value;
|
||||
export let prop_type = {};
|
||||
|
||||
$: isOdd = (index % 2 !== 0);
|
||||
|
||||
|
@ -25,20 +24,20 @@ const setComponentProp = (props) => {
|
|||
|
||||
<div class="root" >
|
||||
|
||||
{#if propDef.type === "event"}
|
||||
{#if prop_type === "event"}
|
||||
|
||||
<div class="prop-label">{propDef.____name}</div>
|
||||
<!-- <h5>{prop_name}</h5>
|
||||
<EventListSelector parentProps={props}
|
||||
{propDef}
|
||||
onValueChanged={setComponentProp} />
|
||||
onValueChanged={setComponentProp} /> -->
|
||||
|
||||
{:else }
|
||||
|
||||
<div class="prop-label">{propDef.____name}</div>
|
||||
<StateBindingControl value={props[propDef.____name]}
|
||||
type={propDef.type}
|
||||
options={propDef.options}
|
||||
onChanged={v => setProp(propDef.____name, v)}/>
|
||||
<h5>{prop_name}</h5>
|
||||
<StateBindingControl value={prop_value}
|
||||
type={prop_type}
|
||||
options={prop_type.options}
|
||||
onChanged={v => setProp(prop_name, v)}/>
|
||||
|
||||
{/if}
|
||||
|
||||
|
@ -47,13 +46,21 @@ const setComponentProp = (props) => {
|
|||
<style>
|
||||
|
||||
.root {
|
||||
padding: 1rem 1rem 0rem 1rem;
|
||||
height: 40px;
|
||||
margin-bottom: 15px;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-columns: 70px 1fr;
|
||||
grid-gap: 10px;
|
||||
}
|
||||
|
||||
.prop-label {
|
||||
font-size: 0.8rem;
|
||||
color: var(--secondary100);
|
||||
font-weight: bold;
|
||||
}
|
||||
h5 {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #163057;
|
||||
opacity: 0.6;
|
||||
padding-top: 12px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -10,67 +10,30 @@ import { getInstanceProps } from "./pagesParsing/createProps";
|
|||
import Checkbox from "../common/Checkbox.svelte";
|
||||
import Textbox from "../common/Textbox.svelte";
|
||||
import Dropdown from "../common/Dropdown.svelte";
|
||||
import { validateProps } from "./pagesParsing/validateProps";
|
||||
import PropControl from "./PropControl.svelte";
|
||||
import IconButton from "../common/IconButton.svelte";
|
||||
|
||||
export let shouldValidate = true;
|
||||
export let onValidate = () => {};
|
||||
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'];
|
||||
|
||||
$: {
|
||||
if(componentInfo)
|
||||
{
|
||||
isInstance = !!instanceProps;
|
||||
props = isInstance
|
||||
? getInstanceProps(componentInfo, instanceProps)
|
||||
: cloneDeep(componentInfo.fullProps);
|
||||
$: propDefs = componentInfo && Object.entries(componentInfo).filter(([name])=> !props_to_ignore.includes(name));
|
||||
|
||||
propsDefinitions = pipe(componentInfo.propsDefinition, [
|
||||
keys,
|
||||
map(k => ({...componentInfo.propsDefinition[k], ____name:k})),
|
||||
sortBy("____name")
|
||||
]);
|
||||
}
|
||||
function find_type(prop_name) {
|
||||
if(!componentInfo._component) return;
|
||||
return components.find(({name}) => name === componentInfo._component).props[prop_name];
|
||||
}
|
||||
|
||||
|
||||
let setProp = (name, value) => {
|
||||
const newProps = cloneDeep(props);
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
const validate = (finalProps) => {
|
||||
errors = validateProps(componentInfo.rootComponent, finalProps, [], false);
|
||||
onValidate(errors);
|
||||
return errors.length === 0;
|
||||
onPropsChanged(name, value);
|
||||
}
|
||||
|
||||
const fieldHasError = (propName) =>
|
||||
|
@ -81,14 +44,14 @@ const fieldHasError = (propName) =>
|
|||
<div class="root">
|
||||
|
||||
<form class="uk-form-stacked form-root">
|
||||
{#each propsDefinitions as propDef, index}
|
||||
{#each propDefs as [prop_name, prop_value], index}
|
||||
|
||||
<div class="prop-container">
|
||||
|
||||
<PropControl {setProp}
|
||||
{fieldHasError}
|
||||
{propDef}
|
||||
{props}
|
||||
{prop_name}
|
||||
{prop_value}
|
||||
prop_type={find_type(prop_name)}
|
||||
{index}
|
||||
disabled={false} />
|
||||
|
||||
|
@ -98,9 +61,6 @@ const fieldHasError = (propName) =>
|
|||
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
@ -66,10 +66,10 @@ const setBindingSource = ev => {
|
|||
bind(bindingPath, bindingFallbackValue, ev.target.value);
|
||||
}
|
||||
|
||||
const makeBinding = () => {
|
||||
forceIsBound=true;
|
||||
isExpanded=true;
|
||||
}
|
||||
// const makeBinding = () => {
|
||||
// forceIsBound=true;
|
||||
// isExpanded=true;
|
||||
// }
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -117,7 +117,7 @@ const makeBinding = () => {
|
|||
<div>
|
||||
<IconButton icon={value == true ? "check-square" : "square"}
|
||||
size="19"
|
||||
on:click={() => onChanged(!value)}/>
|
||||
on:click={() => onChanged(!value)} />
|
||||
</div>
|
||||
|
||||
{:else if type === "options"}
|
||||
|
@ -132,49 +132,46 @@ const makeBinding = () => {
|
|||
|
||||
{:else}
|
||||
|
||||
<input class="uk-input uk-form-small"
|
||||
on:change={ev => onChanged(ev.target.value)}
|
||||
<input on:change={ev => onChanged(ev.target.value)}
|
||||
bind:value={value}
|
||||
style="flex: 1 0 auto;" >
|
||||
style="flex: 1 0 auto;" />
|
||||
|
||||
|
||||
{/if}
|
||||
<IconButton icon="link"
|
||||
size="12"
|
||||
on:click={makeBinding} />
|
||||
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
<style>
|
||||
|
||||
.unbound-container {
|
||||
.unbound-container {
|
||||
display:flex;
|
||||
margin: .5rem 0rem .5rem 0rem;
|
||||
}
|
||||
}
|
||||
|
||||
.unbound-container > *:nth-child(1) {
|
||||
width:auto;
|
||||
flex: 1 0 auto;
|
||||
font-size: 0.8rem;
|
||||
color: var(--secondary100);
|
||||
border-radius: .2rem;
|
||||
}
|
||||
|
||||
.bound-header {
|
||||
.bound-header {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.bound-header > div:nth-child(1) {
|
||||
.bound-header > div:nth-child(1) {
|
||||
flex: 1 0 auto;
|
||||
width: 30px;
|
||||
color: var(--secondary50);
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.binding-prop-label {
|
||||
.binding-prop-label {
|
||||
color: var(--secondary50);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #163057;
|
||||
opacity: 0.7;
|
||||
padding: 5px 10px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #DBDBDB;
|
||||
border-radius: 2px;
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
|
@ -29,16 +29,27 @@ const settings = () => {
|
|||
|
||||
<div class="ui-nav">
|
||||
|
||||
<div class="pages-list-container">
|
||||
<div class="nav-group-header">
|
||||
|
||||
<span class="navigator-title">Navigator</span>
|
||||
</div>
|
||||
<div class="nav-items-container">
|
||||
<PagesList />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="components-list-container">
|
||||
<div class="nav-group-header">
|
||||
<div>{@html getIcon("sidebar","18")}</div>
|
||||
|
||||
<span class="components-nav-header">Screens</span>
|
||||
<div>
|
||||
<IconButton icon="settings"
|
||||
<!-- <IconButton icon="settings"
|
||||
size="14px"
|
||||
on:click={settings}/>
|
||||
<IconButton icon="plus"
|
||||
on:click={newComponent}/>
|
||||
on:click={settings}/> -->
|
||||
<!-- <IconButton icon="plus"
|
||||
on:click={newComponent}/> -->
|
||||
<button on:click={newComponent}>+</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-items-container">
|
||||
|
@ -46,16 +57,6 @@ const settings = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pages-list-container">
|
||||
<div class="nav-group-header">
|
||||
<div>{@html getIcon("grid","18")}</div>
|
||||
<span>Pages</span>
|
||||
</div>
|
||||
<div class="nav-items-container">
|
||||
<PagesList />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="preview-pane">
|
||||
|
@ -80,22 +81,47 @@ const settings = () => {
|
|||
|
||||
|
||||
<style>
|
||||
button {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background: var(--background-button);
|
||||
|
||||
width: 1.8rem;
|
||||
height: 1.8rem;
|
||||
padding-bottom: 10px;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
color: var(--button-text);
|
||||
}
|
||||
|
||||
.root {
|
||||
display: grid;
|
||||
grid-template-columns: 250px 1fr 300px;
|
||||
grid-template-columns: 290px 1fr 300px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.ui-nav {
|
||||
grid-column: 1;
|
||||
background-color: var(--secondary5);
|
||||
height: 100%;
|
||||
padding: 0 1.5rem 0rem 1.5rem
|
||||
}
|
||||
|
||||
.preview-pane {
|
||||
grid-column: 2;
|
||||
margin: 80px 60px;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0px 6px rgba(0,0,0,0.05)
|
||||
}
|
||||
|
||||
.components-pane {
|
||||
|
@ -105,12 +131,10 @@ const settings = () => {
|
|||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.pages-list-container {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.components-nav-header {
|
||||
font-size: .9rem;
|
||||
font-size: 0.75rem;
|
||||
color: #999;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.nav-group-header {
|
||||
|
@ -119,15 +143,16 @@ const settings = () => {
|
|||
}
|
||||
|
||||
.nav-items-container {
|
||||
padding: 1rem 1rem 0rem 1rem;
|
||||
padding: 1rem 0rem 0rem 0rem;
|
||||
}
|
||||
|
||||
.nav-group-header {
|
||||
display:grid;
|
||||
grid-template-columns: [icon] auto [title] 1fr [button] auto;
|
||||
padding: 2rem 1rem 0rem 1rem;
|
||||
display: flex;
|
||||
padding: 2rem 0 0 0;
|
||||
font-size: .9rem;
|
||||
font-weight: bold;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-group-header>div:nth-child(1) {
|
||||
|
@ -155,4 +180,10 @@ const settings = () => {
|
|||
color: var(--primary75);
|
||||
}
|
||||
|
||||
.navigator-title {
|
||||
text-transform: uppercase;
|
||||
font-weight: 400;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
isString, isUndefined, find, keys, uniq,
|
||||
some, filter, reduce, cloneDeep, includes,last
|
||||
some, filter, reduce, cloneDeep, includes, last
|
||||
} from "lodash/fp";
|
||||
import { types, expandComponentDefinition } from "./types";
|
||||
import { assign } from "lodash";
|
||||
|
@ -11,7 +11,7 @@ import { ensureShardNameIsInShardMap } from "../../../../core/src/indexing/shard
|
|||
export const getInstanceProps = (componentInfo, props) => {
|
||||
const finalProps = cloneDeep(componentInfo.fullProps);
|
||||
|
||||
for(let p in props) {
|
||||
for (let p in props) {
|
||||
finalProps[p] = props[p];
|
||||
}
|
||||
|
||||
|
@ -21,8 +21,8 @@ export const getInstanceProps = (componentInfo, props) => {
|
|||
export const getNewComponentInfo = (components, rootComponent, name) => {
|
||||
const component = {
|
||||
name: name || "",
|
||||
description:"",
|
||||
props:{
|
||||
description: "",
|
||||
props: {
|
||||
_component: rootComponent
|
||||
}
|
||||
};
|
||||
|
@ -44,11 +44,11 @@ export const getComponentInfo = (components, comp) => {
|
|||
: comp;
|
||||
let component;
|
||||
let subComponent;
|
||||
if(isRootComponent(targetComponent)) {
|
||||
if (isRootComponent(targetComponent)) {
|
||||
component = targetComponent;
|
||||
} else {
|
||||
subComponent = targetComponent;
|
||||
component = find(c => c.name === subComponent.props._component)(
|
||||
component = find(c => c.name === (subComponent.props ? subComponent.props._component : subComponent._component))(
|
||||
components);
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,7 @@ export const getComponentInfo = (components, comp) => {
|
|||
fullProps._component = targetComponent.name;
|
||||
|
||||
return ({
|
||||
propsDefinition:expandComponentDefinition(component),
|
||||
propsDefinition: expandComponentDefinition(component),
|
||||
rootDefaultProps: rootProps.props,
|
||||
unsetProps,
|
||||
fullProps: fullProps,
|
||||
|
@ -78,7 +78,7 @@ export const getComponentInfo = (components, comp) => {
|
|||
export const createProps = (componentDefinition, derivedFromProps) => {
|
||||
|
||||
const error = (propName, error) =>
|
||||
errors.push({propName, error});
|
||||
errors.push({ propName, error });
|
||||
|
||||
const props = {
|
||||
_component: componentDefinition.name
|
||||
|
@ -86,23 +86,23 @@ export const createProps = (componentDefinition, derivedFromProps) => {
|
|||
|
||||
const errors = [];
|
||||
|
||||
if(!componentDefinition.name)
|
||||
if (!componentDefinition.name)
|
||||
error("_component", "Component name not supplied");
|
||||
|
||||
const propsDef = componentDefinition.props;
|
||||
for(let propDef in propsDef) {
|
||||
for (let propDef in propsDef) {
|
||||
const parsedPropDef = parsePropDef(propsDef[propDef]);
|
||||
if(parsedPropDef.error)
|
||||
if (parsedPropDef.error)
|
||||
error(propDef, parsedPropDef.error);
|
||||
else
|
||||
props[propDef] = parsedPropDef;
|
||||
}
|
||||
|
||||
if(derivedFromProps) {
|
||||
if (derivedFromProps) {
|
||||
assign(props, derivedFromProps);
|
||||
}
|
||||
|
||||
if(componentDefinition.children !== false
|
||||
if (componentDefinition.children !== false
|
||||
&& isUndefined(props._children)) {
|
||||
props._children = [];
|
||||
}
|
||||
|
@ -114,26 +114,26 @@ export const createProps = (componentDefinition, derivedFromProps) => {
|
|||
|
||||
|
||||
const parsePropDef = propDef => {
|
||||
const error = message => ({error:message, propDef});
|
||||
const error = message => ({ error: message, propDef });
|
||||
|
||||
if(isString(propDef)) {
|
||||
if(!types[propDef])
|
||||
if (isString(propDef)) {
|
||||
if (!types[propDef])
|
||||
return error(`Do not recognise type ${propDef}`);
|
||||
|
||||
return types[propDef].default();
|
||||
}
|
||||
|
||||
if(!propDef.type)
|
||||
if (!propDef.type)
|
||||
return error("Property Definition must declare a type");
|
||||
|
||||
const type = types[propDef.type];
|
||||
if(!type)
|
||||
if (!type)
|
||||
return error(`Do not recognise type ${propDef.type}`);
|
||||
|
||||
if(isUndefined(propDef.default))
|
||||
if (isUndefined(propDef.default))
|
||||
return type.default(propDef);
|
||||
|
||||
if(!type.isOfType(propDef.default))
|
||||
if (!type.isOfType(propDef.default))
|
||||
return error(`${propDef.default} is not of type ${type}`);
|
||||
|
||||
return propDef.default;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { recursivelyValidate } from "./validateProps";
|
||||
import {
|
||||
isString,
|
||||
keys,
|
||||
|
@ -15,11 +14,11 @@ export const validatePage = (page, getComponent) => {
|
|||
const error = message => errors.push(message);
|
||||
|
||||
const noIndex = !page.index;
|
||||
if(noIndex) {
|
||||
if (noIndex) {
|
||||
error("Page does not define an index member");
|
||||
}
|
||||
|
||||
if(!page.appBody
|
||||
if (!page.appBody
|
||||
|| !isString(page.appBody)
|
||||
|| !page.appBody.endsWith(".json")) {
|
||||
error("App body must be set toa valid JSON file");
|
||||
|
@ -44,15 +43,15 @@ export const validatePages = (pages, getComponent) => {
|
|||
let errors = [];
|
||||
const error = message => errors.push(message);
|
||||
|
||||
if(!pages.main) {
|
||||
if (!pages.main) {
|
||||
error("must have a 'main' page");
|
||||
}
|
||||
|
||||
if(!pages.unauthenticated) {
|
||||
if (!pages.unauthenticated) {
|
||||
error("must have a 'unauthenticated' (login) page");
|
||||
}
|
||||
|
||||
if(!pages.componentLibraries
|
||||
if (!pages.componentLibraries
|
||||
|| !isArray(pages.componentLibraries)
|
||||
|| pages.componentLibraries.length === 0) {
|
||||
|
||||
|
|
|
@ -1,143 +0,0 @@
|
|||
import { types } from "./types";
|
||||
import {
|
||||
createProps, arrayElementComponentName
|
||||
} from "./createProps";
|
||||
import { isString } from "util";
|
||||
import {
|
||||
includes, filter, map, keys,
|
||||
flatten, flattenDeep, each,
|
||||
indexOf, isUndefined
|
||||
} from "lodash/fp";
|
||||
import { common } from "../../../../core/src";
|
||||
import {
|
||||
isBinding
|
||||
} from "../../common/binding";
|
||||
|
||||
const pipe = common.$;
|
||||
|
||||
const makeError = (errors, propName, stack) => (message) =>
|
||||
errors.push({
|
||||
stack,
|
||||
propName,
|
||||
error:message});
|
||||
|
||||
export const recursivelyValidate = (rootProps, getComponent, stack=[]) => {
|
||||
|
||||
if(!rootProps._component) {
|
||||
const errs = [];
|
||||
makeError(errs, "_component", stack)("Component is not set");
|
||||
return errs;
|
||||
// this would break everything else anyway
|
||||
}
|
||||
|
||||
const componentDef = getComponent(
|
||||
rootProps._component);
|
||||
|
||||
|
||||
const errors = validateProps(
|
||||
componentDef,
|
||||
rootProps,
|
||||
stack,
|
||||
true);
|
||||
|
||||
const validateChildren = (_props, _stack) =>
|
||||
!_props._children
|
||||
? []
|
||||
: pipe(_props._children, [
|
||||
map(child => recursivelyValidate(
|
||||
child,
|
||||
getComponent,
|
||||
[..._stack, _props._children.indexOf(child)]))
|
||||
]);
|
||||
|
||||
const childErrors = validateChildren(
|
||||
rootProps, stack);
|
||||
|
||||
return flattenDeep([errors, ...childErrors]);
|
||||
}
|
||||
|
||||
const expandPropDef = propDef =>
|
||||
isString(propDef)
|
||||
? types[propDef].defaultDefinition()
|
||||
: propDef;
|
||||
|
||||
|
||||
|
||||
export const validateProps = (componentDefinition, props, stack=[], isFinal=true) => {
|
||||
|
||||
const errors = [];
|
||||
|
||||
if(isFinal && !props._component) {
|
||||
makeError(errors, "_component", stack)("Component is not set");
|
||||
return errors;
|
||||
// this would break everything else anyway
|
||||
}
|
||||
|
||||
const propsDefinition = componentDefinition.props;
|
||||
|
||||
for(let propDefName in props) {
|
||||
|
||||
if(propDefName === "_component") continue;
|
||||
if(propDefName === "_children") continue;
|
||||
if(propDefName === "_layout") continue;
|
||||
|
||||
const propDef = expandPropDef(propsDefinition[propDefName]);
|
||||
|
||||
const type = types[propDef.type];
|
||||
|
||||
const error = makeError(errors, propDefName, stack);
|
||||
|
||||
const propValue = props[propDefName];
|
||||
|
||||
// component declarations dont need to define al props.
|
||||
if(!isFinal && isUndefined(propValue)) continue;
|
||||
|
||||
if(isFinal && propDef.required && propValue) {
|
||||
error(`Property ${propDefName} is required`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(isBinding(propValue)) {
|
||||
if(propDef.type === "event") {
|
||||
error(`Cannot apply binding to type ${propDef.type}`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if(!type.isOfType(propValue)) {
|
||||
error(`Property ${propDefName} is not of type ${propDef.type}. Actual value ${propValue}`)
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if(propDef.type === "options"
|
||||
&& propValue
|
||||
&& !isBinding(propValue)
|
||||
&& !includes(propValue)(propDef.options)) {
|
||||
error(`Property ${propDefName} is not one of allowed options. Acutal value is ${propValue}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
export const validateComponentDefinition = (componentDefinition) => {
|
||||
const { errors } = createProps(componentDefinition);
|
||||
|
||||
const propDefinitions = expandPropDef(componentDefinition.props);
|
||||
|
||||
pipe(propDefinitions, [
|
||||
keys,
|
||||
map(k => ({
|
||||
propDef:propDefinitions[k],
|
||||
propName:k
|
||||
})),
|
||||
filter(d => d.propDef.type === "options"
|
||||
&& (!d.propDef.options || d.propDef.options.length === 0)),
|
||||
each(d => makeError(errors, d.propName)(`${d.propName} does not have any options`))
|
||||
]);
|
||||
|
||||
return errors;
|
||||
|
||||
}
|
||||
|
|
@ -1,232 +0,0 @@
|
|||
import {
|
||||
validateComponentDefinition,
|
||||
validateProps,
|
||||
recursivelyValidate
|
||||
} from "../src/userInterface/pagesParsing/validateProps";
|
||||
import { createProps } from "../src/userInterface/pagesParsing/createProps";
|
||||
import {
|
||||
setBinding
|
||||
} from "../src/common/binding";
|
||||
|
||||
// not that allot of this functionality is covered
|
||||
// in createDefaultProps - as validate props uses that.
|
||||
|
||||
describe("validateComponentDefinition", () => {
|
||||
|
||||
|
||||
it("should return error when no options for options field", () => {
|
||||
|
||||
const compDef = {
|
||||
name:"some_component",
|
||||
props: {
|
||||
size: {
|
||||
type: "options",
|
||||
options: []
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const errors = validateComponentDefinition(compDef);
|
||||
|
||||
expect(errors.length).toEqual(1);
|
||||
expect(errors[0].propName).toBe("size");
|
||||
|
||||
});
|
||||
|
||||
it("should not return error when options field has options", () => {
|
||||
|
||||
const compDef = {
|
||||
name: "some_component",
|
||||
props: {
|
||||
size: {
|
||||
type: "options",
|
||||
options: ["small", "medium", "large"]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const errors = validateComponentDefinition(compDef);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
const validComponentDef = {
|
||||
name: "some_component",
|
||||
props: {
|
||||
size: {
|
||||
type: "options",
|
||||
options: ["small", "medium", "large"],
|
||||
default:"medium"
|
||||
},
|
||||
rowCount : "number"
|
||||
}
|
||||
};
|
||||
|
||||
const childComponentDef = {
|
||||
name: "child_component",
|
||||
props: {
|
||||
width: "number",
|
||||
units: {
|
||||
type: "string",
|
||||
default: "px"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const validProps = () => {
|
||||
|
||||
const { props } = createProps(validComponentDef);
|
||||
props._children.push(
|
||||
createProps(childComponentDef));
|
||||
return props;
|
||||
}
|
||||
|
||||
describe("validateProps", () => {
|
||||
|
||||
it("should have no errors with a big list of valid props", () => {
|
||||
|
||||
const errors = validateProps(validComponentDef, validProps(), [], true);
|
||||
expect(errors).toEqual([]);
|
||||
|
||||
});
|
||||
|
||||
it("should return error with invalid value", () => {
|
||||
|
||||
const props = validProps();
|
||||
props.rowCount = "1";
|
||||
const errors = validateProps(validComponentDef, props, [], true);
|
||||
expect(errors.length).toEqual(1);
|
||||
expect(errors[0].propName).toBe("rowCount");
|
||||
|
||||
});
|
||||
|
||||
it("should return error with invalid option", () => {
|
||||
|
||||
const props = validProps();
|
||||
props.size = "really_small";
|
||||
const errors = validateProps(validComponentDef, props, [], true);
|
||||
expect(errors.length).toEqual(1);
|
||||
expect(errors[0].propName).toBe("size");
|
||||
|
||||
});
|
||||
|
||||
it("should not return error when has binding", () => {
|
||||
const props = validProps();
|
||||
props._children[0].width = setBinding({path:"some_path"});
|
||||
props.size = setBinding({path:"other path", fallback:"small"});
|
||||
const errors = validateProps(validComponentDef, props, [], true);
|
||||
expect(errors.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("recursivelyValidateProps", () => {
|
||||
|
||||
const rootComponent = {
|
||||
name: "rootComponent",
|
||||
children: true,
|
||||
props: {
|
||||
width: "number"
|
||||
}
|
||||
};
|
||||
|
||||
const todoListComponent = {
|
||||
name: "todoListComponent",
|
||||
props:{
|
||||
showTitle: "bool"
|
||||
}
|
||||
};
|
||||
|
||||
const headerComponent = {
|
||||
name: "headerComponent",
|
||||
props: {
|
||||
text: "string"
|
||||
}
|
||||
};
|
||||
|
||||
const iconComponent = {
|
||||
name: "iconComponent",
|
||||
props: {
|
||||
iconName: "string"
|
||||
}
|
||||
};
|
||||
|
||||
const navItemComponent = {
|
||||
name: "navItemComponent",
|
||||
props: {
|
||||
text: "string"
|
||||
}
|
||||
};
|
||||
|
||||
const getComponent = name => ({
|
||||
rootComponent,
|
||||
todoListComponent,
|
||||
headerComponent,
|
||||
iconComponent,
|
||||
navItemComponent
|
||||
})[name];
|
||||
|
||||
const rootProps = () => ({
|
||||
_component: "rootComponent",
|
||||
width: 100,
|
||||
_children: [{
|
||||
_component: "todoListComponent",
|
||||
showTitle: true,
|
||||
_children : [
|
||||
{
|
||||
_component: "navItemComponent",
|
||||
text: "todos"
|
||||
},
|
||||
{
|
||||
_component: "headerComponent",
|
||||
text: "Your todo list"
|
||||
},
|
||||
{
|
||||
_component: "iconComponent",
|
||||
iconName: "fa fa-list"
|
||||
},
|
||||
{
|
||||
_component: "iconComponent",
|
||||
iconName:"fa fa-cog"
|
||||
}
|
||||
]
|
||||
}]
|
||||
});
|
||||
|
||||
it("should return no errors for valid structure", () => {
|
||||
const result = recursivelyValidate(
|
||||
rootProps(),
|
||||
getComponent);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("should return error on root component", () => {
|
||||
const root = rootProps();
|
||||
root.width = "yeeeoooo";
|
||||
const result = recursivelyValidate(root, getComponent);
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0].propName).toBe("width");
|
||||
});
|
||||
|
||||
it("should return error on first nested child component", () => {
|
||||
const root = rootProps();
|
||||
root._children[0].showTitle = "yeeeoooo";
|
||||
const result = recursivelyValidate(root, getComponent);
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0].stack).toEqual([0]);
|
||||
expect(result[0].propName).toBe("showTitle");
|
||||
});
|
||||
|
||||
it("should return error on second nested child component", () => {
|
||||
const root = rootProps();
|
||||
root._children[0]._children[0].text = false;
|
||||
const result = recursivelyValidate(root, getComponent);
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0].stack).toEqual([0,0]);
|
||||
expect(result[0].propName).toBe("text");
|
||||
});
|
||||
|
||||
});
|
2
packages/core/dist/budibase-core.cjs.js
vendored
2
packages/core/dist/budibase-core.cjs.js
vendored
File diff suppressed because one or more lines are too long
2
packages/core/dist/budibase-core.esm.mjs
vendored
2
packages/core/dist/budibase-core.esm.mjs
vendored
File diff suppressed because one or more lines are too long
2
packages/core/dist/budibase-core.umd.js
vendored
2
packages/core/dist/budibase-core.umd.js
vendored
File diff suppressed because one or more lines are too long
5
packages/server/.gitignore
vendored
5
packages/server/.gitignore
vendored
|
@ -1,3 +1,8 @@
|
|||
myapps/
|
||||
config.js
|
||||
<<<<<<< HEAD
|
||||
/builder/*
|
||||
!/builder/assets/
|
||||
=======
|
||||
builder/
|
||||
>>>>>>> ee5a4e8c962b29242152cbbd8065d8f3ccf65eaf
|
||||
|
|
Binary file not shown.
|
@ -3,28 +3,22 @@
|
|||
|
||||
|
||||
"@budibase/client@file:../../../client":
|
||||
version "0.0.3"
|
||||
version "0.0.15"
|
||||
dependencies:
|
||||
"@nx-js/compiler-util" "^2.0.0"
|
||||
date-fns "^1.29.0"
|
||||
lodash "^4.17.15"
|
||||
lunr "^2.3.5"
|
||||
shortid "^2.2.8"
|
||||
svelte "^3.9.2"
|
||||
|
||||
"@budibase/standard-components@file:../../../standard-components":
|
||||
version "0.0.5"
|
||||
version "0.0.15"
|
||||
|
||||
"@nx-js/compiler-util@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@nx-js/compiler-util/-/compiler-util-2.0.0.tgz#c74c12165fa2f017a292bb79af007e8fce0af297"
|
||||
integrity sha512-AxSQbwj9zqt8DYPZ6LwZdytqnwfiOEdcFdq4l8sdjkZmU2clTht7RDLCI8xvkp7KqgcNaOGlTeCM55TULWruyQ==
|
||||
|
||||
date-fns@^1.29.0:
|
||||
version "1.30.1"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
|
||||
integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
|
||||
|
||||
lodash@^4.17.15:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -8,7 +8,7 @@ export let _bb;
|
|||
let rootDiv;
|
||||
$:{
|
||||
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>
|
||||
|
||||
|
|
|
@ -73,9 +73,12 @@ if you then want to run the builder in dev mode (i.e. with hot reloading):
|
|||
|
||||
... keep the server running, and..
|
||||
1. Open a new console
|
||||
2. `cd packages/builder`
|
||||
3. `yarn start`
|
||||
4. Access the builder on http://localhost:3000
|
||||
2. `yarn dev`
|
||||
3. Access the builder on http://localhost:3000
|
||||
|
||||
This will enable watch mode for both the client AND the server.
|
||||
|
||||
### Running Commands from /server Directory
|
||||
|
||||
Notice that when inside `packages/server`, you can use any Budibase CLI command via yarn:
|
||||
|
||||
|
|
Loading…
Reference in a new issue