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>
|
|
@ -17,7 +17,7 @@ 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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -134,6 +138,7 @@ const initialise = (store, initial) => async () => {
|
|||
shadowHierarchy, initial.currentNode.nodeId
|
||||
);
|
||||
}
|
||||
|
||||
store.set(initial);
|
||||
return initial;
|
||||
}
|
||||
|
@ -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)})),
|
||||
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>
|
||||
|
||||
|
@ -132,33 +132,20 @@ 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 {
|
||||
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 {
|
||||
|
@ -176,5 +163,15 @@ const makeBinding = () => {
|
|||
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>
|
|
@ -48,7 +48,7 @@ export const getComponentInfo = (components, comp) => {
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { recursivelyValidate } from "./validateProps";
|
||||
import {
|
||||
isString,
|
||||
keys,
|
||||
|
|
|
@ -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