Merge branch 'master' of github.com:Budibase/budibase
This commit is contained in:
commit
634a7a8c3f
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"jest": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2019,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"ignorePatterns": ["node_modules", "dist", "public"],
|
||||
"plugins": ["prettier", "svelte3"],
|
||||
"extends": ["eslint:recommended"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/*.svelte"],
|
||||
"processor": "svelte3/svelte3"
|
||||
}
|
||||
],
|
||||
"rules": {
|
||||
"prettier/prettier": "error"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
name: Budibase CI
|
||||
|
||||
on:
|
||||
# Trigger the workflow on push or pull request,
|
||||
# but only for the master branch
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: yarn
|
||||
- run: yarn lint
|
||||
- run: yarn build
|
||||
- run: yarn test
|
||||
env:
|
||||
CI: true
|
||||
name: Budibase CI
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"plugins": ["prettier-plugin-svelte"]
|
||||
}
|
12
package.json
12
package.json
|
@ -2,7 +2,12 @@
|
|||
"name": "root",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"lerna": "^3.14.1"
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-prettier": "^3.1.2",
|
||||
"eslint-plugin-svelte3": "^2.7.3",
|
||||
"lerna": "^3.14.1",
|
||||
"prettier": "^1.19.1",
|
||||
"prettier-plugin-svelte": "^0.7.0"
|
||||
},
|
||||
"dependencies": {},
|
||||
"scripts": {
|
||||
|
@ -10,6 +15,9 @@
|
|||
"build": "lerna run build",
|
||||
"initialise": "lerna run initialise",
|
||||
"clean": "lerna clean",
|
||||
"dev": "lerna run --parallel --stream dev:builder"
|
||||
"dev": "lerna run --parallel --stream dev:builder",
|
||||
"test": "lerna run test",
|
||||
"lint": "eslint packages",
|
||||
"format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx}\""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"build": "rollup -c",
|
||||
"start": "rollup -c -w",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watchAll",
|
||||
"dev:builder": "rollup -c -w"
|
||||
},
|
||||
"jest": {
|
||||
|
|
|
@ -11,34 +11,34 @@ import browsersync from "rollup-plugin-browsersync";
|
|||
import proxy from "http-proxy-middleware";
|
||||
|
||||
const target = 'http://localhost:4001';
|
||||
const _builderProxy = proxy('/_builder', {
|
||||
target:"http://localhost:3000",
|
||||
pathRewrite: {'^/_builder' : ''}
|
||||
const _builderProxy = proxy('/_builder', {
|
||||
target: "http://localhost:3000",
|
||||
pathRewrite: { '^/_builder': '' }
|
||||
});
|
||||
|
||||
const apiProxy = proxy(['/_builder/api/**', '/_builder/**/componentlibrary', '/_builder/**/componentlibraryGenerators'] , {
|
||||
const apiProxy = proxy(['/_builder/api/**', '/_builder/**/componentlibrary', '/_builder/**/componentlibraryGenerators'], {
|
||||
target,
|
||||
logLevel: "debug",
|
||||
changeOrigin: true,
|
||||
cookieDomainRewrite: true,
|
||||
onProxyReq(proxyReq) {
|
||||
if (proxyReq.getHeader("origin")) {
|
||||
proxyReq.setHeader("origin", target)
|
||||
}
|
||||
if (proxyReq.getHeader("origin")) {
|
||||
proxyReq.setHeader("origin", target)
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH;
|
||||
|
||||
const lodash_fp_exports = ["union", "reduce", "isUndefined", "cloneDeep", "split", "some", "map", "filter", "isEmpty", "countBy", "includes", "last", "find", "constant",
|
||||
"take", "first", "intersection", "mapValues", "isNull", "has", "isInteger", "isNumber", "isString", "isBoolean", "isDate", "isArray", "isObject", "clone", "values", "keyBy", "isNaN",
|
||||
"keys", "orderBy", "concat", "reverse", "difference", "merge", "flatten", "each", "pull", "join", "defaultCase", "uniqBy", "every", "uniqWith", "isFunction", "groupBy",
|
||||
"differenceBy", "intersectionBy", "isEqual", "max", "sortBy", "assign", "uniq", "trimChars", "trimCharsStart", "isObjectLike", "flattenDeep", "indexOf", "isPlainObject",
|
||||
"toNumber", "takeRight"];
|
||||
const lodash_fp_exports = ["union", "reduce", "isUndefined", "cloneDeep", "split", "some", "map", "filter", "isEmpty", "countBy", "includes", "last", "find", "constant",
|
||||
"take", "first", "intersection", "mapValues", "isNull", "has", "isInteger", "isNumber", "isString", "isBoolean", "isDate", "isArray", "isObject", "clone", "values", "keyBy", "isNaN",
|
||||
"keys", "orderBy", "concat", "reverse", "difference", "merge", "flatten", "each", "pull", "join", "defaultCase", "uniqBy", "every", "uniqWith", "isFunction", "groupBy",
|
||||
"differenceBy", "intersectionBy", "isEqual", "max", "sortBy", "assign", "uniq", "trimChars", "trimCharsStart", "isObjectLike", "flattenDeep", "indexOf", "isPlainObject",
|
||||
"toNumber", "takeRight", "toPairs"];
|
||||
|
||||
const lodash_exports = ["flow", "join", "replace", "trim", "dropRight", "takeRight", "head", "reduce",
|
||||
"tail", "startsWith", "findIndex", "merge",
|
||||
"assign", "each", "find", "orderBy", "union"];
|
||||
const lodash_exports = ["flow", "join", "replace", "trim", "dropRight", "takeRight", "head", "reduce",
|
||||
"tail", "startsWith", "findIndex", "merge",
|
||||
"assign", "each", "find", "orderBy", "union"];
|
||||
|
||||
const outputpath = "../server/builder";
|
||||
|
||||
|
@ -63,7 +63,7 @@ export default {
|
|||
{ src: 'src/favicon.png', dest: outputpath },
|
||||
{ src: 'src/assets', dest: outputpath },
|
||||
{ src: 'node_modules/@budibase/client/dist/budibase-client.esm.mjs', dest: outputpath },
|
||||
]
|
||||
]
|
||||
}),
|
||||
|
||||
svelte({
|
||||
|
@ -80,28 +80,28 @@ export default {
|
|||
resolve({
|
||||
browser: true,
|
||||
dedupe: importee => {
|
||||
return importee === 'svelte'
|
||||
|| importee.startsWith('svelte/')
|
||||
|| coreExternal.includes(importee);
|
||||
return importee === 'svelte'
|
||||
|| importee.startsWith('svelte/')
|
||||
|| coreExternal.includes(importee);
|
||||
}
|
||||
|
||||
|
||||
}),
|
||||
commonjs({
|
||||
namedExports: {
|
||||
"lodash/fp": lodash_fp_exports,
|
||||
"lodash":lodash_exports,
|
||||
"lodash": lodash_exports,
|
||||
"shortid": ["generate"]
|
||||
}
|
||||
}),
|
||||
url({
|
||||
limit: 0,
|
||||
include: ["**/*.woff2", "**/*.png"],
|
||||
limit: 0,
|
||||
include: ["**/*.woff2", "**/*.png"],
|
||||
fileName: "[dirname][name][extname]",
|
||||
emitFiles: true
|
||||
}),
|
||||
url({
|
||||
limit: 0,
|
||||
include: ["**/*.css"],
|
||||
limit: 0,
|
||||
include: ["**/*.css"],
|
||||
fileName: "[name][extname]",
|
||||
emitFiles: true
|
||||
}),
|
||||
|
@ -113,7 +113,7 @@ export default {
|
|||
!production && livereload(outputpath),
|
||||
!production && browsersync({
|
||||
server: outputpath,
|
||||
middleware: [apiProxy,_builderProxy]
|
||||
middleware: [apiProxy, _builderProxy]
|
||||
}),
|
||||
|
||||
// If we're building for production (npm run build
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
import { filter, map, reduce, toPairs } from "lodash/fp";
|
||||
import { pipe } from "../common/core";
|
||||
|
||||
const self = n => n;
|
||||
const join_with = delimiter => a => a.join(delimiter);
|
||||
const empty_string_to_unset = s => s.length ? s : "0";
|
||||
const add_suffix = suffix => s => s + suffix;
|
||||
|
||||
export const make_margin = (values) => pipe(values, [
|
||||
map(empty_string_to_unset),
|
||||
map(add_suffix('px')),
|
||||
join_with(' ')
|
||||
]);
|
||||
|
||||
const tap = message => x => {
|
||||
console.log(x);
|
||||
return x;
|
||||
}
|
||||
|
||||
const css_map = {
|
||||
templaterows: {
|
||||
name: 'grid-template-columns',
|
||||
generate: self
|
||||
},
|
||||
templatecolumns: {
|
||||
name: 'grid-template-rows',
|
||||
generate: self
|
||||
},
|
||||
gridarea: {
|
||||
name: 'grid-area',
|
||||
generate: make_margin
|
||||
},
|
||||
gap: {
|
||||
name: 'grid-gap',
|
||||
generate: n => `${n}px`
|
||||
},
|
||||
columnstart: {
|
||||
name: 'grid-column-start',
|
||||
generate: self
|
||||
},
|
||||
columnend: {
|
||||
name: 'grid-column-end',
|
||||
generate: self
|
||||
},
|
||||
rowstart: {
|
||||
name: 'grid-row-start',
|
||||
generate: self
|
||||
},
|
||||
rowend: {
|
||||
name: 'grid-row-end',
|
||||
generate: self
|
||||
},
|
||||
padding: {
|
||||
name: 'padding',
|
||||
generate: make_margin
|
||||
},
|
||||
margin: {
|
||||
name: 'margin',
|
||||
generate: make_margin
|
||||
},
|
||||
zindex: {
|
||||
name: 'z-index',
|
||||
generate: self
|
||||
}
|
||||
}
|
||||
|
||||
export const generate_rule = ([name, values]) =>
|
||||
`${css_map[name].name}: ${css_map[name].generate(values)};`
|
||||
|
||||
const handle_grid = (acc, [name, value]) => {
|
||||
let tmp = [];
|
||||
|
||||
if (name === 'row' || name === 'column') {
|
||||
if (value[0]) tmp.push([`${name}start`, value[0]]);
|
||||
if (value[1]) tmp.push([`${name}end`, value[1]]);
|
||||
return acc.concat(tmp)
|
||||
}
|
||||
|
||||
return acc.concat([[name, value]]);
|
||||
}
|
||||
|
||||
const object_to_css_string = [
|
||||
toPairs,
|
||||
reduce(handle_grid, []),
|
||||
filter(v => Array.isArray(v[1]) ? v[1].some(s => s.length) : v[1].length),
|
||||
map(generate_rule),
|
||||
join_with('\n'),
|
||||
];
|
||||
|
||||
export const generate_css = ({ layout, position }) => {
|
||||
let _layout = pipe(layout, object_to_css_string);
|
||||
_layout = _layout.length ? _layout + "\ndisplay: grid;" : _layout;
|
||||
|
||||
return {
|
||||
layout: _layout,
|
||||
position: pipe(position, object_to_css_string)
|
||||
}
|
||||
}
|
||||
|
||||
const apply_class = (id, name, styles) => `.${name}-${id} {\n${styles}\n}`;
|
||||
|
||||
export const generate_screen_css = (component_array) => {
|
||||
let styles = "";
|
||||
|
||||
for (let i = 0; i < component_array.length; i += 1) {
|
||||
const { _styles, _id, _children } = component_array[i];
|
||||
const { layout, position } = generate_css(_styles);
|
||||
|
||||
styles += apply_class(_id, 'pos', position) + "\n";
|
||||
styles += apply_class(_id, 'lay', layout) + "\n";
|
||||
|
||||
if (_children && _children.length) {
|
||||
styles += generate_screen_css(_children) + "\n";
|
||||
}
|
||||
|
||||
}
|
||||
return styles.trim();
|
||||
}
|
|
@ -4,7 +4,7 @@ import {
|
|||
import {
|
||||
filter, cloneDeep, sortBy,
|
||||
map, last, keys, concat, keyBy,
|
||||
find, isEmpty, reduce, values, isEqual
|
||||
find, isEmpty, values,
|
||||
} from "lodash/fp";
|
||||
import {
|
||||
pipe, getNode, validate,
|
||||
|
@ -17,11 +17,13 @@ import api from "./api";
|
|||
import { isRootComponent, getExactComponent } from "../userInterface/pagesParsing/searchComponents";
|
||||
import { rename } from "../userInterface/pagesParsing/renameScreen";
|
||||
import {
|
||||
getNewComponentInfo, getScreenInfo, getComponentInfo
|
||||
getNewComponentInfo, getScreenInfo,
|
||||
} from "../userInterface/pagesParsing/createProps";
|
||||
import {
|
||||
loadLibs, loadLibUrls, loadGeneratorLibs
|
||||
} from "./loadComponentLibraries";
|
||||
import { uuid } from './uuid';
|
||||
import { generate_screen_css } from './generate_css';
|
||||
|
||||
let appname = "";
|
||||
|
||||
|
@ -710,7 +712,8 @@ const addChildComponent = store => component => {
|
|||
const component_definition = Object.assign(
|
||||
cloneDeep(newComponent.fullProps), {
|
||||
_component: component,
|
||||
_layout: {}
|
||||
_styles: { position: {}, layout: {} },
|
||||
_id: uuid()
|
||||
})
|
||||
|
||||
if (children) {
|
||||
|
@ -729,6 +732,8 @@ const addChildComponent = store => component => {
|
|||
|
||||
_saveScreen(store, s, s.currentFrontEndItem);
|
||||
|
||||
_saveScreen(store, s, s.currentFrontEndItem);
|
||||
|
||||
return s;
|
||||
})
|
||||
}
|
||||
|
@ -751,12 +756,13 @@ const setComponentProp = store => (name, value) => {
|
|||
})
|
||||
}
|
||||
|
||||
const setComponentStyle = store => (name, value) => {
|
||||
const setComponentStyle = store => (type, name, value) => {
|
||||
store.update(s => {
|
||||
if (!s.currentComponentInfo._layout) {
|
||||
s.currentComponentInfo._layout = {};
|
||||
if (!s.currentComponentInfo._styles) {
|
||||
s.currentComponentInfo._styles = {};
|
||||
}
|
||||
s.currentComponentInfo._layout[name] = value;
|
||||
s.currentComponentInfo._styles[type][name] = value;
|
||||
s.currentFrontEndItem._css = generate_screen_css(s.currentFrontEndItem.props._children)
|
||||
|
||||
// save without messing with the store
|
||||
_save(s.appname, s.currentFrontEndItem, store, s)
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export function uuid() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||
const r = (Math.random() * 16) | 0,
|
||||
v = c == 'x' ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<script>
|
||||
export let disabled = false;
|
||||
export let hidden = false;
|
||||
export let primary = true;
|
||||
export let alert = false;
|
||||
export let warning = false;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.primary {
|
||||
color: #0055ff;
|
||||
background: rgb(54, 133, 249, 0.1);
|
||||
}
|
||||
|
||||
.alert {
|
||||
color: rgba(255, 0, 31, 1);
|
||||
background: rgba(255, 0, 31, 0.1);;
|
||||
}
|
||||
|
||||
.button {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
width: 167px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button:disabled {
|
||||
color: rgba(22, 48, 87, 0.2);
|
||||
cursor: default;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<button
|
||||
on:click
|
||||
class="button"
|
||||
class:hidden
|
||||
class:primary
|
||||
class:alert
|
||||
class:warning
|
||||
{disabled}>
|
||||
<slot />
|
||||
</button>
|
|
@ -0,0 +1,9 @@
|
|||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24">
|
||||
<path fill="none" d="M0 0h24v24H0z" />
|
||||
<path
|
||||
d="M13 9h8L11 24v-9H4l9-15v9zm-2 2V7.22L7.532 13H13v4.394L17.263 11H11z" />
|
||||
</svg>
|
After Width: | Height: | Size: 228 B |
|
@ -0,0 +1,19 @@
|
|||
<svg
|
||||
on:click
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24">
|
||||
<path fill="none" d="M0 0h24v24H0z" />
|
||||
<path
|
||||
d="M15.728 9.686l-1.414-1.414L5
|
||||
17.586V19h1.414l9.314-9.314zm1.414-1.414l1.414-1.414-1.414-1.414-1.414 1.414
|
||||
1.414 1.414zM7.242 21H3v-4.243L16.435 3.322a1 1 0 0 1 1.414 0l2.829 2.829a1
|
||||
1 0 0 1 0 1.414L7.243 21z" />
|
||||
</svg>
|
||||
|
||||
<style>
|
||||
svg:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
After Width: | Height: | Size: 446 B |
|
@ -4,3 +4,5 @@ export { default as TerminalIcon } from './Terminal.svelte';
|
|||
export { default as InputIcon } from './Input.svelte';
|
||||
export { default as ImageIcon } from './Image.svelte';
|
||||
export { default as ArrowDownIcon } from './ArrowDown.svelte';
|
||||
export { default as EventsIcon } from './Events.svelte';
|
||||
export { default as PencilIcon } from './Pencil.svelte';
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<script>
|
||||
export let value = "";
|
||||
</script>
|
||||
|
||||
<style>
|
||||
input {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-family: sans-serif;
|
||||
font-weight: 500;
|
||||
color: #163057;
|
||||
line-height: 1.3;
|
||||
padding: 1em 2.6em 0.9em 1.4em;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: #fff;
|
||||
border: 1px solid #ccc;
|
||||
height: 50px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<input type="text" on:change bind:value />
|
|
@ -2,6 +2,7 @@
|
|||
export let meta = [];
|
||||
export let size = '';
|
||||
export let values = [];
|
||||
export let type = "number";
|
||||
export let onStyleChanged = () => {};
|
||||
|
||||
let _values = values.map(v => v);
|
||||
|
@ -11,7 +12,7 @@
|
|||
|
||||
<div class="inputs {size}">
|
||||
{#each meta as { placeholder }, i}
|
||||
<input type="number"
|
||||
<input {type}
|
||||
placeholder="{placeholder}"
|
||||
value={values[i]}
|
||||
on:input={(e) => _values[i] = e.target.value} />
|
||||
|
|
|
@ -1,41 +1,44 @@
|
|||
<script>
|
||||
import UIkit from "uikit";
|
||||
import UIkit from "uikit";
|
||||
|
||||
export let isOpen = false;
|
||||
export let onClosed = () => {};
|
||||
export let id = "";
|
||||
export let isOpen = false;
|
||||
export let onClosed = () => {};
|
||||
export let id = "";
|
||||
|
||||
let ukModal;
|
||||
let listenerAdded = false;
|
||||
let ukModal;
|
||||
let listenerAdded = false;
|
||||
|
||||
$: {
|
||||
if(ukModal && !listenerAdded) {
|
||||
listenerAdded = true;
|
||||
ukModal.addEventListener("hidden", onClosed);
|
||||
$: {
|
||||
if (ukModal && !listenerAdded) {
|
||||
listenerAdded = true;
|
||||
ukModal.addEventListener("hidden", onClosed);
|
||||
}
|
||||
|
||||
if(ukModal) {
|
||||
if(isOpen) {
|
||||
UIkit.modal(ukModal).show();
|
||||
} else {
|
||||
UIkit.modal(ukModal).hide();
|
||||
}
|
||||
if (ukModal) {
|
||||
if (isOpen) {
|
||||
UIkit.modal(ukModal).show();
|
||||
} else {
|
||||
UIkit.modal(ukModal).hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<div bind:this={ukModal} uk-modal {id}>
|
||||
<div class="uk-modal-dialog uk-modal-body" uk-overflow-auto>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.uk-modal-dialog {
|
||||
border-radius: 0.3rem;
|
||||
width: 60%;
|
||||
height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
||||
.uk-modal-dialog {
|
||||
border-radius: .3rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
<div bind:this={ukModal} uk-modal {id}>
|
||||
<div class="uk-modal-dialog uk-modal-body" uk-overflow-auto>
|
||||
{#if onClosed}
|
||||
<button class="uk-modal-close-default" type="button" uk-close />
|
||||
{/if}
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<style>
|
||||
button {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background: rgba(249, 249, 249, 1);
|
||||
|
||||
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: rgba(22, 48, 87, 1);
|
||||
}
|
||||
</style>
|
||||
|
||||
<button on:click>+</button>
|
|
@ -0,0 +1,55 @@
|
|||
<script>
|
||||
import getIcon from "./icon";
|
||||
export let value;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.select-container {
|
||||
padding-bottom: 10px;
|
||||
font-size: 0.9rem;
|
||||
color: var(--secondary50);
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
select {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-family: sans-serif;
|
||||
font-weight: 500;
|
||||
color: #163057;
|
||||
line-height: 1.3;
|
||||
padding: 1em 2.6em 0.9em 1.4em;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: #fff;
|
||||
border: 1px solid #ccc;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
pointer-events: none;
|
||||
color: var(--primary100);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="select-container">
|
||||
<select on:change {value}>
|
||||
<slot />
|
||||
</select>
|
||||
<span class="arrow">
|
||||
{@html getIcon('chevron-down', '24')}
|
||||
</span>
|
||||
</div>
|
|
@ -2,9 +2,10 @@
|
|||
import PropsView from "./PropsView.svelte";
|
||||
import { store } from "../builderStore";
|
||||
import IconButton from "../common/IconButton.svelte";
|
||||
import { LayoutIcon, PaintIcon, TerminalIcon } from '../common/Icons/';
|
||||
import { LayoutIcon, PaintIcon, TerminalIcon, EventsIcon } from '../common/Icons/';
|
||||
import CodeEditor from './CodeEditor.svelte';
|
||||
import LayoutEditor from './LayoutEditor.svelte';
|
||||
import EventsEditor from "./EventsEditor";
|
||||
|
||||
let current_view = 'props';
|
||||
|
||||
|
@ -17,6 +18,7 @@
|
|||
|
||||
const onPropChanged = store.setComponentProp;
|
||||
const onStyleChanged = store.setComponentStyle;
|
||||
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
|
@ -36,6 +38,11 @@
|
|||
<TerminalIcon />
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class:selected={current_view === 'events'} on:click={() => current_view = 'events'}>
|
||||
<EventsIcon />
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{#if !componentInfo.component}
|
||||
|
@ -45,6 +52,8 @@
|
|||
<PropsView {componentInfo} {components} {onPropChanged} />
|
||||
{:else if current_view === 'layout'}
|
||||
<LayoutEditor {onStyleChanged} {componentInfo}/>
|
||||
{:else if current_view === 'events'}
|
||||
<EventsEditor {componentInfo} {components} {onPropChanged} />
|
||||
{:else}
|
||||
<CodeEditor />
|
||||
{/if}
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
<script>
|
||||
import { last } from "lodash/fp";
|
||||
import { pipe } from "../common/core";
|
||||
|
||||
export let components = [];
|
||||
export let currentComponent;
|
||||
export let onSelect = () => {};
|
||||
export let level = 0;
|
||||
|
||||
|
||||
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]);
|
||||
|
@ -38,18 +35,15 @@
|
|||
padding-left: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: block;
|
||||
padding: 11px 67px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
background: #fafafa;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selected {
|
||||
color: var(--button-text);
|
||||
background: var(--background-button)!important;
|
||||
|
|
|
@ -4,7 +4,11 @@
|
|||
import { pipe } from "../common/core";
|
||||
import { buildPropsHierarchy } from "./pagesParsing/buildPropsHierarchy";
|
||||
|
||||
let iframe;
|
||||
|
||||
$: iframe && console.log(iframe.contentDocument.head.insertAdjacentHTML('beforeend', '<style></style>'))
|
||||
$: hasComponent = !!$store.currentFrontEndItem;
|
||||
$: styles = hasComponent ? $store.currentFrontEndItem._css : '';
|
||||
|
||||
$: stylesheetLinks = pipe($store.pages.stylesheets, [
|
||||
map(s => `<link rel="stylesheet" href="${s}"/>`),
|
||||
|
@ -27,6 +31,7 @@
|
|||
{#if hasComponent}
|
||||
<iframe style="height: 100%; width: 100%"
|
||||
title="componentPreview"
|
||||
bind:this={iframe}
|
||||
srcdoc={
|
||||
`<html>
|
||||
|
||||
|
@ -45,6 +50,7 @@
|
|||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
}
|
||||
${styles}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
<script>
|
||||
import IconButton from "../common/IconButton.svelte";
|
||||
import EventSelector from "./EventSelector.svelte";
|
||||
import {
|
||||
filter
|
||||
} from "lodash/fp";
|
||||
import {EVENT_TYPE_MEMBER_NAME} from "../common/eventHandlers";
|
||||
|
||||
export let parentProps;
|
||||
export let propDef;
|
||||
export let onValueChanged;
|
||||
|
||||
$: events = parentProps[propDef.____name];
|
||||
|
||||
const addHandler = () => {
|
||||
const newHandler = {parameters:{}};
|
||||
newHandler[EVENT_TYPE_MEMBER_NAME] = "";
|
||||
events = [...events, newHandler];
|
||||
onValueChanged(events);
|
||||
}
|
||||
|
||||
const onEventHandlerChanged = (oldEvent) => (newEvent) => {
|
||||
const indexOfOldEvent = events.indexOf(oldEvent);
|
||||
const newEvents = [...events];
|
||||
newEvents.splice(
|
||||
events.indexOf(oldEvent),
|
||||
1,
|
||||
newEvent);
|
||||
events = newEvents;
|
||||
onValueChanged(events);
|
||||
}
|
||||
|
||||
const removeHandler = (index) => () => {
|
||||
events = filter(e => e !== events[index])(events);
|
||||
onValueChanged(events);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
<div class="control-container">
|
||||
{#each events as ev, index}
|
||||
|
||||
<div class="handler-container">
|
||||
<EventSelector onChanged={onEventHandlerChanged(ev)}
|
||||
onRemoved={removeHandler(index)}
|
||||
event={ev} />
|
||||
|
||||
</div>
|
||||
|
||||
<div class="separator"></div>
|
||||
{/each}
|
||||
|
||||
<div class="addelement-container"
|
||||
on:click={addHandler}>
|
||||
<IconButton icon="plus"
|
||||
size="12"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.addelement-container {
|
||||
cursor: pointer;
|
||||
padding: 3px 0px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.addelement-container:hover {
|
||||
background-color: var(--primary25);
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
|
||||
.control-container {
|
||||
padding-left: 3px;
|
||||
background: var(--secondary10);
|
||||
}
|
||||
|
||||
.separator {
|
||||
width: 60%;
|
||||
margin: 10px auto;
|
||||
border-style:solid;
|
||||
border-width: 1px 0 0 0;
|
||||
border-color: var(--primary25);
|
||||
}
|
||||
</style>
|
|
@ -1,105 +0,0 @@
|
|||
<script>
|
||||
import IconButton from "../common/IconButton.svelte";
|
||||
import StateBindingControl from "./StateBindingControl.svelte";
|
||||
import {
|
||||
find, map, keys, reduce, keyBy
|
||||
} from "lodash/fp";
|
||||
import { pipe, userWithFullAccess } from "../common/core";
|
||||
import { EVENT_TYPE_MEMBER_NAME, allHandlers } from "../common/eventHandlers";
|
||||
import { store } from "../builderStore";
|
||||
|
||||
export let event;
|
||||
export let onChanged;
|
||||
export let onRemoved;
|
||||
|
||||
let eventType;
|
||||
let parameters = [];
|
||||
|
||||
|
||||
$: events = allHandlers(
|
||||
{hierarchy: $store.hierarchy},
|
||||
userWithFullAccess({
|
||||
hierarchy: s.hierarchy,
|
||||
actions: keyBy("name")($store.actions)
|
||||
})
|
||||
);
|
||||
|
||||
$: if(event) {
|
||||
eventType = event[EVENT_TYPE_MEMBER_NAME];
|
||||
parameters = pipe(event.parameters, [
|
||||
keys,
|
||||
map(k => ({name:k, value:event.parameters[k]}))
|
||||
]);
|
||||
} else {
|
||||
eventType = "";
|
||||
parameters = [];
|
||||
}
|
||||
|
||||
const eventChanged = (type, parameters) => {
|
||||
const paramsAsObject = reduce(
|
||||
(obj, p) => {
|
||||
obj[p.name] = p.value;
|
||||
return obj;
|
||||
}
|
||||
, {}
|
||||
)(parameters)
|
||||
|
||||
const ev = {};
|
||||
ev[EVENT_TYPE_MEMBER_NAME]=type;
|
||||
ev.parameters = paramsAsObject;
|
||||
|
||||
onChanged(ev);
|
||||
}
|
||||
|
||||
const eventTypeChanged = (ev) => {
|
||||
const eType = find(e => e.name === ev.target.value)(events);
|
||||
const emptyParameters = map(p => ({name:p, value:""}))(eType.parameters);
|
||||
eventChanged(eType.name, emptyParameters);
|
||||
}
|
||||
|
||||
const onParameterChanged = index => val => {
|
||||
const newparameters = [...parameters];
|
||||
newparameters[index].value = val;
|
||||
eventChanged(eventType, newparameters);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="type-selector-container">
|
||||
<select class="type-selector uk-select uk-form-small " value={eventType} on:change={eventTypeChanged}>
|
||||
<option></option>
|
||||
{#each events as ev}
|
||||
<option value={ev.name}>{ev.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
|
||||
<IconButton icon="trash"
|
||||
size="12"
|
||||
on:click={onRemoved}/>
|
||||
|
||||
</div>
|
||||
|
||||
{#if parameters}
|
||||
{#each parameters as p, index}
|
||||
|
||||
<div>
|
||||
{p.name}
|
||||
</div>
|
||||
<StateBindingControl onChanged={onParameterChanged(index)}
|
||||
value={p.value} />
|
||||
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.type-selector-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.type-selector {
|
||||
border-color: var(--primary50);
|
||||
border-radius: 2px;
|
||||
width: 50px;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,164 @@
|
|||
<script>
|
||||
import Modal from "../../common/Modal.svelte";
|
||||
import HandlerSelector from "./HandlerSelector.svelte";
|
||||
import IconButton from "../../common/IconButton.svelte";
|
||||
import ActionButton from "../../common/ActionButton.svelte";
|
||||
import PlusButton from "../../common/PlusButton.svelte";
|
||||
import Select from "../../common/Select.svelte";
|
||||
import Input from "../../common/Input.svelte";
|
||||
import getIcon from "../../common/icon";
|
||||
|
||||
import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers";
|
||||
|
||||
export let event;
|
||||
export let eventOptions;
|
||||
export let open;
|
||||
export let onClose;
|
||||
export let onPropChanged;
|
||||
|
||||
let eventType = "onClick";
|
||||
let draftEventHandler = { parameters: [] };
|
||||
|
||||
$: eventData = event || { handlers: [] };
|
||||
|
||||
const closeModal = () => {
|
||||
onClose();
|
||||
draftEventHandler = { parameters: [] };
|
||||
eventData = { handlers: [] };
|
||||
};
|
||||
|
||||
const updateEventHandler = (updatedHandler, index) => {
|
||||
eventData.handlers[index] = updatedHandler;
|
||||
};
|
||||
|
||||
const updateDraftEventHandler = updatedHandler => {
|
||||
draftEventHandler = updatedHandler;
|
||||
};
|
||||
|
||||
const deleteEventHandler = index => {
|
||||
eventData.handlers.splice(index, 1);
|
||||
eventData = eventData;
|
||||
};
|
||||
|
||||
const createNewEventHandler = handler => {
|
||||
const newHandler = handler || {
|
||||
parameters: {},
|
||||
[EVENT_TYPE_MEMBER_NAME]: ""
|
||||
};
|
||||
eventData.handlers.push(newHandler);
|
||||
eventData = eventData;
|
||||
};
|
||||
|
||||
const deleteEvent = () => {
|
||||
onPropChanged(eventType, []);
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const saveEventData = () => {
|
||||
onPropChanged(eventType, eventData.handlers);
|
||||
closeModal();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
h2 {
|
||||
color: var(--primary100);
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
h5 {
|
||||
color: rgba(22, 48, 87, 0.6);
|
||||
font-size: 15px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.event-options {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: 10px;
|
||||
}
|
||||
|
||||
.actions,
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
header {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(22, 48, 87, 0.6);
|
||||
font-size: 12px;
|
||||
margin-top: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<Modal bind:isOpen={open} onClosed={closeModal}>
|
||||
<h2>
|
||||
{eventData.name ? `${eventData.name} Event` : 'Create a New Component Event'}
|
||||
</h2>
|
||||
<a href="https://docs.budibase.com/" target="_blank">
|
||||
Click here to learn more about component events
|
||||
</a>
|
||||
|
||||
<div class="event-options">
|
||||
<div>
|
||||
<header>
|
||||
<h5>Event Type</h5>
|
||||
{@html getIcon('info', 20)}
|
||||
</header>
|
||||
<Select :value={eventType}>
|
||||
{#each eventOptions as option}
|
||||
<option value={option.name}>{option.name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<header>
|
||||
<h5>Event Action(s)</h5>
|
||||
{@html getIcon('info', 20)}
|
||||
</header>
|
||||
<HandlerSelector
|
||||
newHandler
|
||||
onChanged={updateDraftEventHandler}
|
||||
onCreate={() => {
|
||||
createNewEventHandler(draftEventHandler);
|
||||
draftEventHandler = { parameters: [] };
|
||||
}}
|
||||
handler={draftEventHandler} />
|
||||
{#if eventData}
|
||||
{#each eventData.handlers as handler, index}
|
||||
<HandlerSelector
|
||||
{index}
|
||||
onChanged={updateEventHandler}
|
||||
onRemoved={() => deleteEventHandler(index)}
|
||||
{handler} />
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<div class="actions">
|
||||
<ActionButton
|
||||
alert
|
||||
disabled={eventData.handlers.length === 0}
|
||||
hidden={!eventData.name}
|
||||
on:click={deleteEvent}>
|
||||
Delete
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
disabled={eventData.handlers.length === 0}
|
||||
on:click={saveEventData}>
|
||||
Save
|
||||
</ActionButton>
|
||||
</div>
|
||||
</Modal>
|
|
@ -0,0 +1,152 @@
|
|||
<script>
|
||||
import {
|
||||
keys,
|
||||
map,
|
||||
some,
|
||||
includes,
|
||||
cloneDeep,
|
||||
isEqual,
|
||||
sortBy,
|
||||
filter,
|
||||
difference
|
||||
} from "lodash/fp";
|
||||
import { pipe } from "../../common/core";
|
||||
import Checkbox from "../../common/Checkbox.svelte";
|
||||
import Textbox from "../../common/Textbox.svelte";
|
||||
import Dropdown from "../../common/Dropdown.svelte";
|
||||
import PlusButton from "../../common/PlusButton.svelte";
|
||||
import IconButton from "../../common/IconButton.svelte";
|
||||
import Modal from "../../common/Modal.svelte";
|
||||
import EventEditorModal from "./EventEditorModal.svelte";
|
||||
import HandlerSelector from "./HandlerSelector.svelte";
|
||||
|
||||
import { PencilIcon } from "../../common/Icons";
|
||||
import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers";
|
||||
|
||||
export const EVENT_TYPE = "event";
|
||||
|
||||
export let componentInfo;
|
||||
export let onPropChanged = () => {};
|
||||
export let components;
|
||||
|
||||
let modalOpen = false;
|
||||
let events = [];
|
||||
let selectedEvent = null;
|
||||
|
||||
$: {
|
||||
events = Object.keys(componentInfo)
|
||||
.filter(key => findType(key) === EVENT_TYPE)
|
||||
.map(key => ({ name: key, handlers: componentInfo[key] }));
|
||||
}
|
||||
|
||||
function findType(propName) {
|
||||
if (!componentInfo._component) return;
|
||||
return components.find(({ name }) => name === componentInfo._component)
|
||||
.props[propName];
|
||||
}
|
||||
|
||||
const openModal = event => {
|
||||
selectedEvent = event;
|
||||
modalOpen = true;
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
selectedEvent = null;
|
||||
modalOpen = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
h3 {
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #8997ab;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.root {
|
||||
font-size: 10pt;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-root {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.handler-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
border: 2px solid #f9f9f9;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.hierarchy-item {
|
||||
cursor: pointer;
|
||||
padding: 11px 7px;
|
||||
margin: 5px 0;
|
||||
border-radius: 5px;
|
||||
font-size: 1.5em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hierarchy-item:hover {
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.event-name {
|
||||
margin-top: 5px;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
color: rgba(22, 48, 87, 0.6);
|
||||
align-self: end;
|
||||
}
|
||||
|
||||
.edit-text {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-weight: bold;
|
||||
align-self: end;
|
||||
justify-self: end;
|
||||
font-size: 10px;
|
||||
color: rgba(35, 65, 105, 0.4);
|
||||
}
|
||||
|
||||
.selected {
|
||||
color: var(--button-text);
|
||||
background: var(--background-button) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<header>
|
||||
<h3>Events</h3>
|
||||
<PlusButton on:click={() => openModal()} />
|
||||
</header>
|
||||
|
||||
<div class="root">
|
||||
<form class="uk-form-stacked form-root">
|
||||
{#each events as event, index}
|
||||
{#if event.handlers.length > 0}
|
||||
<div
|
||||
class="handler-container hierarchy-item {selectedEvent && selectedEvent.index === index ? 'selected' : ''}"
|
||||
on:click={() => openModal({ ...event, index })}>
|
||||
<span class="event-name">{event.name}</span>
|
||||
<span class="edit-text">EDIT</span>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</form>
|
||||
</div>
|
||||
<EventEditorModal
|
||||
{onPropChanged}
|
||||
open={modalOpen}
|
||||
onClose={closeModal}
|
||||
eventOptions={events}
|
||||
event={selectedEvent}
|
||||
/>
|
|
@ -0,0 +1,148 @@
|
|||
<script>
|
||||
import IconButton from "../../common/IconButton.svelte";
|
||||
import PlusButton from "../../common/PlusButton.svelte";
|
||||
import Select from "../../common/Select.svelte";
|
||||
import StateBindingControl from "../StateBindingControl.svelte";
|
||||
import { find, map, keys, reduce, keyBy } from "lodash/fp";
|
||||
import { pipe, userWithFullAccess } from "../../common/core";
|
||||
import {
|
||||
EVENT_TYPE_MEMBER_NAME,
|
||||
allHandlers
|
||||
} from "../../common/eventHandlers";
|
||||
import { store } from "../../builderStore";
|
||||
|
||||
export let handler;
|
||||
export let onCreate;
|
||||
export let onChanged;
|
||||
export let onRemoved;
|
||||
|
||||
export let index;
|
||||
export let newHandler;
|
||||
|
||||
let eventOptions;
|
||||
let handlerType;
|
||||
let parameters = [];
|
||||
|
||||
$: eventOptions = allHandlers(
|
||||
{ hierarchy: $store.hierarchy },
|
||||
userWithFullAccess({
|
||||
hierarchy: $store.hierarchy,
|
||||
actions: keyBy("name")($store.actions)
|
||||
})
|
||||
);
|
||||
|
||||
$: {
|
||||
if (handler) {
|
||||
handlerType = handler[EVENT_TYPE_MEMBER_NAME];
|
||||
parameters = Object.entries(handler.parameters).map(([name, value]) => ({
|
||||
name,
|
||||
value
|
||||
}));
|
||||
} else {
|
||||
// Empty Handler
|
||||
handlerType = "";
|
||||
parameters = [];
|
||||
}
|
||||
}
|
||||
|
||||
const handlerChanged = (type, params) => {
|
||||
const handlerParams = {};
|
||||
for (let param of params) {
|
||||
handlerParams[param.name] = param.value;
|
||||
}
|
||||
|
||||
const updatedHandler = {
|
||||
[EVENT_TYPE_MEMBER_NAME]: type,
|
||||
parameters: handlerParams
|
||||
};
|
||||
|
||||
onChanged(updatedHandler, index);
|
||||
};
|
||||
|
||||
const handlerTypeChanged = e => {
|
||||
const handlerType = eventOptions.find(
|
||||
handler => handler.name === e.target.value
|
||||
);
|
||||
const defaultParams = handlerType.parameters.map(param => ({
|
||||
name: param,
|
||||
value: ""
|
||||
}));
|
||||
|
||||
handlerChanged(handlerType.name, defaultParams);
|
||||
};
|
||||
|
||||
const onParameterChanged = index => value => {
|
||||
const newParams = [...parameters];
|
||||
newParams[index].value = value;
|
||||
handlerChanged(handlerType, newParams);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.type-selector-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: rgba(223, 223, 223, 0.5);
|
||||
border: 1px solid #dfdfdf;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.handler-option {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.new-handler {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.handler-controls {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-gap: 10px;
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
.event-action-button {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="type-selector-container {newHandler && 'new-handler'}">
|
||||
<div class="handler-controls">
|
||||
<div class="handler-option">
|
||||
<span>Action</span>
|
||||
<Select value={handlerType} on:change={handlerTypeChanged}>
|
||||
<option />
|
||||
{#each eventOptions as option}
|
||||
<option value={option.name}>{option.name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
</div>
|
||||
{#if parameters}
|
||||
{#each parameters as param, idx}
|
||||
<div class="handler-option">
|
||||
<span>{param.name}</span>
|
||||
<StateBindingControl
|
||||
onChanged={onParameterChanged(idx)}
|
||||
value={param.value} />
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="event-action-button">
|
||||
{#if parameters.length > 0}
|
||||
{#if newHandler}
|
||||
<PlusButton on:click={onCreate} />
|
||||
{:else}
|
||||
<IconButton icon="x" on:click={onRemoved} />
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./EventsEditor.svelte";
|
|
@ -19,13 +19,16 @@
|
|||
const single = [{ placeholder: '' }];
|
||||
|
||||
|
||||
$: layout = componentInfo._layout;
|
||||
$: layout = { ...componentInfo._styles.position, ...componentInfo._styles.layout };
|
||||
|
||||
$: layouts = {
|
||||
templaterows: ['Grid Rows', single],
|
||||
templatecolumns: ['Grid Columns', single],
|
||||
};
|
||||
|
||||
$: positions = {
|
||||
gridarea: ['Grid Area', tbrl, 'small'],
|
||||
column: ['Column', se],
|
||||
row: ['Row', se],
|
||||
gap: ['Gap', single],
|
||||
};
|
||||
|
||||
$: spacing = {
|
||||
|
@ -41,15 +44,28 @@
|
|||
</script>
|
||||
|
||||
|
||||
<h3>Layout</h3>
|
||||
<h3>Styles</h3>
|
||||
|
||||
<h4>Positioning</h4>
|
||||
<div class="layout-pos">
|
||||
{#each Object.entries(layouts) as [key, [name, meta, size]]}
|
||||
<div class="grid">
|
||||
<h5>{name}:</h5>
|
||||
<InputGroup onStyleChanged={_value => onStyleChanged('layout',key, _value)}
|
||||
values={layout[key] || newValue(meta.length)}
|
||||
{meta}
|
||||
{size}
|
||||
type="text"/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<h4>Positioning</h4>
|
||||
<div class="layout-pos">
|
||||
{#each Object.entries(positions) as [key, [name, meta, size]]}
|
||||
<div class="grid">
|
||||
<h5>Grid Area:</h5>
|
||||
<InputGroup onStyleChanged={_value => onStyleChanged(key, _value)}
|
||||
<h5>{name}:</h5>
|
||||
<InputGroup onStyleChanged={_value => onStyleChanged('position',key, _value)}
|
||||
values={layout[key] || newValue(meta.length)}
|
||||
{meta}
|
||||
{size} />
|
||||
|
@ -61,8 +77,8 @@
|
|||
<div class="layout-spacing">
|
||||
{#each Object.entries(spacing) as [key, [name, meta, size]]}
|
||||
<div class="grid">
|
||||
<h5>Grid Area:</h5>
|
||||
<InputGroup onStyleChanged={_value => onStyleChanged(key, _value)}
|
||||
<h5>{name}:</h5>
|
||||
<InputGroup onStyleChanged={_value => onStyleChanged('position', key, _value)}
|
||||
values={layout[key] || newValue(meta.length)}
|
||||
{meta}
|
||||
{size} />
|
||||
|
@ -74,8 +90,8 @@
|
|||
<div class="layout-layer">
|
||||
{#each Object.entries(zindex) as [key, [name, meta, size]]}
|
||||
<div class="grid">
|
||||
<h5>Grid Area:</h5>
|
||||
<InputGroup onStyleChanged={_value => onStyleChanged(key, _value)}
|
||||
<h5>{name}:</h5>
|
||||
<InputGroup onStyleChanged={_value => onStyleChanged('position', key, _value)}
|
||||
values={layout[key] || newValue(meta.length)}
|
||||
{meta}
|
||||
{size} />
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
let errors = [];
|
||||
let props = {};
|
||||
|
||||
const props_to_ignore = ['_component','_children', '_layout'];
|
||||
const props_to_ignore = ['_component','_children', '_styles', '_id'];
|
||||
|
||||
$: propDefs = componentInfo && Object.entries(componentInfo).filter(([name])=> !props_to_ignore.includes(name));
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import IconButton from "../common/IconButton.svelte";
|
||||
import Input from "../common/Input.svelte";
|
||||
import {
|
||||
isBinding, getBinding, setBinding
|
||||
} from "../common/binding";
|
||||
|
@ -119,9 +120,9 @@
|
|||
{/each}
|
||||
</select>
|
||||
{:else}
|
||||
<input on:change={ev => onChanged(ev.target.value)}
|
||||
bind:value={value}
|
||||
style="flex: 1 0 auto;" />
|
||||
<Input
|
||||
on:change={ev => onChanged(ev.target.value)}
|
||||
bind:value={value} />
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,337 @@
|
|||
import { generate_css, make_margin, generate_screen_css } from '../src/builderStore/generate_css.js';
|
||||
|
||||
describe('make_margin', () => {
|
||||
test('it should generate a valid rule', () => {
|
||||
expect(make_margin(["1", "1", "1", "1"])).toEqual('1px 1px 1px 1px')
|
||||
})
|
||||
|
||||
test('empty values should output 0', () => {
|
||||
expect(make_margin(["1", "1", "", ""])).toEqual('1px 1px 0px 0px')
|
||||
expect(make_margin(["1", "", "", "1"])).toEqual('1px 0px 0px 1px')
|
||||
expect(make_margin(["", "", "", ""])).toEqual('0px 0px 0px 0px')
|
||||
})
|
||||
})
|
||||
|
||||
describe('generate_css', () => {
|
||||
test('it should generate a valid css rule: grid-area', () => {
|
||||
expect(
|
||||
generate_css({ layout: { gridarea: ["", "", "", ""] } })
|
||||
).toEqual({
|
||||
layout: '',
|
||||
position: ''
|
||||
});
|
||||
})
|
||||
|
||||
test('it should generate a valid css rule: grid-gap', () => {
|
||||
expect(
|
||||
generate_css({ layout: { gap: "10" } })
|
||||
).toEqual({
|
||||
layout: 'grid-gap: 10px;\ndisplay: grid;',
|
||||
position: ''
|
||||
});
|
||||
})
|
||||
|
||||
test('it should generate a valid css rule: column 1', () => {
|
||||
expect(
|
||||
generate_css({ position: { column: ["", ""] } }
|
||||
)).toEqual({ layout: '', position: '' });
|
||||
})
|
||||
|
||||
test('it should generate a valid css rule: column 2', () => {
|
||||
expect(
|
||||
generate_css({ position: { column: ["1", ""] } })
|
||||
).toEqual({
|
||||
position: 'grid-column-start: 1;',
|
||||
layout: ''
|
||||
});
|
||||
})
|
||||
|
||||
test('it should generate a valid css rule: column 3', () => {
|
||||
expect(
|
||||
generate_css({ position: { column: ["", "1"] } })
|
||||
).toEqual({
|
||||
position: 'grid-column-end: 1;',
|
||||
layout: ''
|
||||
});
|
||||
})
|
||||
|
||||
test('it should generate a valid css rule: column 4', () => {
|
||||
expect(
|
||||
generate_css({ position: { column: ["1", "1"] } })
|
||||
).toEqual({
|
||||
position: 'grid-column-start: 1;\ngrid-column-end: 1;',
|
||||
layout: ''
|
||||
});
|
||||
})
|
||||
|
||||
test('it should generate a valid css rule: row 1', () => {
|
||||
expect(
|
||||
generate_css({ position: { row: ["", ""] } })
|
||||
).toEqual({ layout: '', position: '' });
|
||||
})
|
||||
|
||||
test('it should generate a valid css rule: row 2', () => {
|
||||
expect(
|
||||
generate_css({ position: { row: ["1", ""] } })
|
||||
).toEqual({
|
||||
position: 'grid-row-start: 1;',
|
||||
layout: ''
|
||||
});
|
||||
})
|
||||
|
||||
test('it should generate a valid css rule: row 3', () => {
|
||||
expect(
|
||||
generate_css({ position: { row: ["", "1"] } })
|
||||
).toEqual({
|
||||
position: 'grid-row-end: 1;',
|
||||
layout: ''
|
||||
});
|
||||
})
|
||||
|
||||
test('it should generate a valid css rule: row 4', () => {
|
||||
expect(
|
||||
generate_css({ position: { row: ["1", "1"] } })
|
||||
).toEqual({
|
||||
position: 'grid-row-start: 1;\ngrid-row-end: 1;',
|
||||
layout: ''
|
||||
});
|
||||
})
|
||||
|
||||
test('it should generate a valid css rule: padding 1', () => {
|
||||
expect(
|
||||
generate_css({ position: { padding: ["1", "1", "1", "1"] } })
|
||||
).toEqual({
|
||||
position: 'padding: 1px 1px 1px 1px;',
|
||||
layout: ''
|
||||
});
|
||||
})
|
||||
|
||||
test('it should generate a valid css rule: padding 2', () => {
|
||||
expect(
|
||||
generate_css({ position: { padding: ["1", "", "", "1"] } })
|
||||
).toEqual({
|
||||
position: 'padding: 1px 0px 0px 1px;',
|
||||
layout: ''
|
||||
});
|
||||
})
|
||||
|
||||
test('it should generate a valid css rule: margin 1', () => {
|
||||
expect(
|
||||
generate_css({ position: { margin: ["1", "1", "1", "1"] } })
|
||||
).toEqual({
|
||||
position: 'margin: 1px 1px 1px 1px;',
|
||||
layout: ''
|
||||
});
|
||||
})
|
||||
|
||||
test('it should generate a valid css rule: margin 2', () => {
|
||||
expect(
|
||||
generate_css({ position: { margin: ["1", "", "", "1"] } })
|
||||
).toEqual({
|
||||
position: 'margin: 1px 0px 0px 1px;',
|
||||
layout: ''
|
||||
});
|
||||
})
|
||||
|
||||
test('it should generate a valid css rule: z-index 1', () => {
|
||||
expect(
|
||||
generate_css({ position: { zindex: "" } })
|
||||
).toEqual({
|
||||
position: '',
|
||||
layout: ''
|
||||
});
|
||||
})
|
||||
|
||||
test('it should generate a valid css rule: z-index 2', () => {
|
||||
expect(
|
||||
generate_css({ position: { zindex: "1" } })
|
||||
).toEqual({
|
||||
position: 'z-index: 1;',
|
||||
layout: ''
|
||||
});
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('generate_screen_css', () => {
|
||||
test('it should compile the css for a list of components', () => {
|
||||
const components = [
|
||||
{
|
||||
_styles: {
|
||||
layout: { gridarea: ["", "", "", ""] },
|
||||
position: { margin: ["1", "1", "1", "1"] }
|
||||
},
|
||||
_id: 1
|
||||
},
|
||||
{
|
||||
_styles: {
|
||||
layout: { gridarea: ["", "", "", ""] },
|
||||
position: { margin: ["1", "1", "1", "1"] }
|
||||
},
|
||||
_id: 2
|
||||
}, {
|
||||
_styles: {
|
||||
layout: { gridarea: ["", "", "", ""] },
|
||||
position: { margin: ["1", "1", "1", "1"] }
|
||||
},
|
||||
_id: 3
|
||||
}
|
||||
, {
|
||||
_styles: {
|
||||
layout: { gridarea: ["", "", "", ""] },
|
||||
position: { margin: ["1", "1", "1", "1"] }
|
||||
},
|
||||
_id: 4
|
||||
}
|
||||
]
|
||||
|
||||
const compiled = `.pos-1 {
|
||||
margin: 1px 1px 1px 1px;
|
||||
}
|
||||
.lay-1 {
|
||||
|
||||
}
|
||||
.pos-2 {
|
||||
margin: 1px 1px 1px 1px;
|
||||
}
|
||||
.lay-2 {
|
||||
|
||||
}
|
||||
.pos-3 {
|
||||
margin: 1px 1px 1px 1px;
|
||||
}
|
||||
.lay-3 {
|
||||
|
||||
}
|
||||
.pos-4 {
|
||||
margin: 1px 1px 1px 1px;
|
||||
}
|
||||
.lay-4 {
|
||||
|
||||
}`
|
||||
|
||||
expect(generate_screen_css(components)).toEqual(compiled)
|
||||
})
|
||||
|
||||
test('it should compile the css for a list of components', () => {
|
||||
const components = [
|
||||
{
|
||||
_styles: {
|
||||
layout: { gridarea: ["", "", "", ""] },
|
||||
position: { margin: ["1", "1", "1", "1"] }
|
||||
},
|
||||
_id: 1,
|
||||
_children: [
|
||||
{
|
||||
_styles: {
|
||||
layout: { gridarea: ["", "", "", ""] },
|
||||
position: { margin: ["1", "1", "1", "1"] }
|
||||
},
|
||||
_id: 2,
|
||||
_children: [
|
||||
{
|
||||
_styles: {
|
||||
layout: { gridarea: ["", "", "", ""] },
|
||||
position: { margin: ["1", "1", "1", "1"] }
|
||||
},
|
||||
_id: 3,
|
||||
_children: [
|
||||
{
|
||||
_styles: {
|
||||
layout: { gridarea: ["", "", "", ""] },
|
||||
position: { margin: ["1", "1", "1", "1"] }
|
||||
},
|
||||
_id: 4,
|
||||
_children: [
|
||||
{
|
||||
_styles: {
|
||||
layout: { gridarea: ["", "", "", ""] },
|
||||
position: { margin: ["1", "1", "1", "1"] }
|
||||
},
|
||||
_id: 5,
|
||||
_children: [
|
||||
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
_styles: {
|
||||
layout: { gridarea: ["", "", "", ""] },
|
||||
position: { margin: ["1", "1", "1", "1"] }
|
||||
},
|
||||
_id: 6
|
||||
}, {
|
||||
_styles: {
|
||||
layout: { gridarea: ["", "", "", ""] },
|
||||
position: { margin: ["1", "1", "1", "1"] }
|
||||
},
|
||||
_id: 7
|
||||
}
|
||||
, {
|
||||
_styles: {
|
||||
layout: { gridarea: ["", "", "", ""] },
|
||||
position: { margin: ["1", "1", "1", "1"] }
|
||||
},
|
||||
_id: 8
|
||||
}
|
||||
]
|
||||
|
||||
const compiled = `.pos-1 {
|
||||
margin: 1px 1px 1px 1px;
|
||||
}
|
||||
.lay-1 {
|
||||
|
||||
}
|
||||
.pos-2 {
|
||||
margin: 1px 1px 1px 1px;
|
||||
}
|
||||
.lay-2 {
|
||||
|
||||
}
|
||||
.pos-3 {
|
||||
margin: 1px 1px 1px 1px;
|
||||
}
|
||||
.lay-3 {
|
||||
|
||||
}
|
||||
.pos-4 {
|
||||
margin: 1px 1px 1px 1px;
|
||||
}
|
||||
.lay-4 {
|
||||
|
||||
}
|
||||
.pos-5 {
|
||||
margin: 1px 1px 1px 1px;
|
||||
}
|
||||
.lay-5 {
|
||||
|
||||
}
|
||||
.pos-6 {
|
||||
margin: 1px 1px 1px 1px;
|
||||
}
|
||||
.lay-6 {
|
||||
|
||||
}
|
||||
.pos-7 {
|
||||
margin: 1px 1px 1px 1px;
|
||||
}
|
||||
.lay-7 {
|
||||
|
||||
}
|
||||
.pos-8 {
|
||||
margin: 1px 1px 1px 1px;
|
||||
}
|
||||
.lay-8 {
|
||||
|
||||
}`
|
||||
|
||||
expect(generate_screen_css(components)).toEqual(compiled)
|
||||
})
|
||||
})
|
|
@ -27,6 +27,8 @@ export const _initialiseChildren = (initialiseOpts) =>
|
|||
htmlElement.removeChild(htmlElement.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
htmlElement.classList.add(`lay-${treeNode.props._id}`)
|
||||
|
||||
const renderedComponents = [];
|
||||
for(let childProps of childrenProps) {
|
||||
|
|
|
@ -23,6 +23,7 @@ export const renderComponent = ({
|
|||
const thisNode = createTreeNode();
|
||||
thisNode.context = componentContext;
|
||||
thisNode.parentNode = parentNode;
|
||||
thisNode.props = props;
|
||||
|
||||
parentNode.children.push(thisNode);
|
||||
renderedNodes.push(thisNode);
|
||||
|
@ -38,6 +39,10 @@ export const renderComponent = ({
|
|||
|
||||
thisNode.rootElement = htmlElement.children[
|
||||
htmlElement.children.length - 1];
|
||||
|
||||
if (initialProps._id) {
|
||||
thisNode.rootElement.classList.add(`pos-${initialProps._id}`)
|
||||
}
|
||||
}
|
||||
|
||||
if(func) {
|
||||
|
@ -51,6 +56,7 @@ export const renderComponent = ({
|
|||
|
||||
export const createTreeNode = () => ({
|
||||
context: {},
|
||||
props: {},
|
||||
rootElement: null,
|
||||
parentNode: null,
|
||||
children: [],
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"jest": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
}
|
||||
}
|
|
@ -51,15 +51,9 @@
|
|||
"@babel/plugin-transform-runtime": "^7.4.4",
|
||||
"@babel/preset-env": "^7.4.5",
|
||||
"@babel/runtime": "^7.4.5",
|
||||
"babel-eslint": "^10.0.2",
|
||||
"babel-jest": "^23.6.0",
|
||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
|
||||
"cross-env": "^5.1.4",
|
||||
"eslint": "^5.3.0",
|
||||
"eslint-config-airbnb": "^17.1.0",
|
||||
"eslint-plugin-import": "^2.14.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.1.1",
|
||||
"eslint-plugin-react": "^7.11.0",
|
||||
"jest": "^24.8.0",
|
||||
"readable-stream": "^3.1.1",
|
||||
"regenerator-runtime": "^0.11.1",
|
||||
|
|
|
@ -14,7 +14,7 @@ module.exports = async (budibaseContext) => {
|
|||
app.context.master,
|
||||
config.latestPackagesFolder
|
||||
);
|
||||
app.use(koaBody({ multipart : true }));
|
||||
app.use(koaBody({ multipart: true }));
|
||||
app.use(router(config, app).routes());
|
||||
return app.listen(config.port);
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue