|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
@ -1,142 +1,54 @@ |
|||||
import { pipe } from "components/common/core" |
export const generate_screen_css = component_arr => { |
||||
import { filter, map, reduce, toPairs } from "lodash/fp" |
let styles = "" |
||||
|
for (const { _styles, _id, _children, _component } of component_arr) { |
||||
const self = n => n |
let [componentName] = _component.match(/[a-z]*$/) |
||||
const join_with = delimiter => a => a.join(delimiter) |
Object.keys(_styles).forEach(selector => { |
||||
const empty_string_to_unset = s => (s.length ? s : "0") |
const cssString = generate_css(_styles[selector]) |
||||
const add_suffix_if_number = suffix => s => { |
if (cssString) { |
||||
try { |
styles += apply_class(_id, componentName, cssString, selector) |
||||
if (isNaN(s) || isNaN(parseFloat(s))) return s |
} |
||||
} catch (_) { |
}) |
||||
return s |
if (_children && _children.length) { |
||||
|
styles += generate_screen_css(_children) + "\n" |
||||
|
} |
||||
} |
} |
||||
return s + suffix |
return styles.trim() |
||||
} |
|
||||
|
|
||||
export const make_margin = values => |
|
||||
pipe(values, [ |
|
||||
map(empty_string_to_unset), |
|
||||
map(add_suffix_if_number("px")), |
|
||||
join_with(" "), |
|
||||
]) |
|
||||
|
|
||||
const css_map = { |
|
||||
templaterows: { |
|
||||
name: "grid-template-rows", |
|
||||
generate: self, |
|
||||
}, |
|
||||
templatecolumns: { |
|
||||
name: "grid-template-columns", |
|
||||
generate: self, |
|
||||
}, |
|
||||
align: { |
|
||||
name: "align-items", |
|
||||
generate: self, |
|
||||
}, |
|
||||
justify: { |
|
||||
name: "justify-content", |
|
||||
generate: self, |
|
||||
}, |
|
||||
direction: { |
|
||||
name: "flex-direction", |
|
||||
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, |
|
||||
}, |
|
||||
height: { |
|
||||
name: "height", |
|
||||
generate: self, |
|
||||
}, |
|
||||
width: { |
|
||||
name: "width", |
|
||||
generate: self, |
|
||||
}, |
|
||||
} |
} |
||||
|
|
||||
export const generate_rule = ([name, values]) => |
export const generate_css = style => { |
||||
`${css_map[name].name}: ${css_map[name].generate(values)};` |
let cssString = Object.entries(style).reduce((str, [key, value]) => { |
||||
|
//TODO Handle arrays and objects here also
|
||||
const handle_grid = (acc, [name, value]) => { |
if (typeof value === "string") { |
||||
let tmp = [] |
if (value) { |
||||
|
return (str += `${key}: ${value};\n`) |
||||
if (name === "row" || name === "column") { |
} |
||||
if (value[0]) tmp.push([`${name}start`, value[0]]) |
} else if (Array.isArray(value)) { |
||||
if (value[1]) tmp.push([`${name}end`, value[1]]) |
if (value.length > 0 && !value.every(v => v === "")) { |
||||
return acc.concat(tmp) |
return (str += `${key}: ${value |
||||
} |
.map(generate_array_styles) |
||||
|
.join(" ")};\n`)
|
||||
|
} |
||||
|
} |
||||
|
}, "") |
||||
|
|
||||
return acc.concat([[name, value]]) |
return (cssString || "").trim() |
||||
} |
} |
||||
|
|
||||
const object_to_css_string = [ |
export const generate_array_styles = item => { |
||||
toPairs, |
let safeItem = item === "" ? 0 : item |
||||
reduce(handle_grid, []), |
let hasPx = new RegExp("px$") |
||||
filter(v => (Array.isArray(v[1]) ? v[1].some(s => s.length) : v[1].length)), |
if (!hasPx.test(safeItem)) { |
||||
map(generate_rule), |
return `${safeItem}px` |
||||
join_with("\n"), |
} else { |
||||
] |
return safeItem |
||||
|
|
||||
export const generate_css = ({ layout, position }) => { |
|
||||
let _layout = pipe(layout, object_to_css_string) |
|
||||
if (_layout.length) { |
|
||||
_layout += `\ndisplay: ${_layout.includes("flex") ? "flex" : "grid"};` |
|
||||
} |
|
||||
|
|
||||
return { |
|
||||
layout: _layout, |
|
||||
position: pipe(position, object_to_css_string), |
|
||||
} |
} |
||||
} |
} |
||||
|
|
||||
const apply_class = (id, name, styles) => `.${name}-${id} {\n${styles}\n}` |
export const apply_class = (id, name = "element", styles, selector) => { |
||||
|
if (selector === "normal") { |
||||
export const generate_screen_css = component_array => { |
return `.${name}-${id} {\n${styles}\n}` |
||||
let styles = "" |
} else { |
||||
let emptyStyles = { layout: {}, position: {} } |
let sel = selector === "selected" ? "::selection" : `:${selector}` |
||||
|
return `.${name}-${id}${sel} {\n${styles}\n}` |
||||
for (let i = 0; i < component_array.length; i += 1) { |
|
||||
const { _styles, _id, _children } = component_array[i] |
|
||||
const { layout, position } = generate_css(_styles || emptyStyles) |
|
||||
|
|
||||
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() |
|
||||
} |
} |
||||
|
|||||
|
After Width: | Height: | Size: 370 B |
|
After Width: | Height: | Size: 561 B |
|
After Width: | Height: | Size: 529 B |
|
After Width: | Height: | Size: 493 B |
|
After Width: | Height: | Size: 309 B |
|
After Width: | Height: | Size: 352 B |
|
After Width: | Height: | Size: 316 B |
|
After Width: | Height: | Size: 310 B |
|
After Width: | Height: | Size: 689 B |
|
After Width: | Height: | Size: 428 B |
@ -0,0 +1,35 @@ |
|||||
|
<script> |
||||
|
export let categories = [] |
||||
|
export let selectedCategory = {} |
||||
|
export let onClick = category => {} |
||||
|
</script> |
||||
|
|
||||
|
<div class="tabs"> |
||||
|
{#each categories as category} |
||||
|
<li |
||||
|
on:click={() => onClick(category)} |
||||
|
class:active={selectedCategory === category}> |
||||
|
{category.name} |
||||
|
</li> |
||||
|
{/each} |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.tabs { |
||||
|
display: flex; |
||||
|
list-style: none; |
||||
|
font-size: 18px; |
||||
|
font-weight: 700; |
||||
|
} |
||||
|
|
||||
|
li { |
||||
|
color: var(--ink-lighter); |
||||
|
cursor: pointer; |
||||
|
margin-right: 20px; |
||||
|
} |
||||
|
|
||||
|
.active { |
||||
|
color: var(--ink); |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,71 @@ |
|||||
|
<script> |
||||
|
import PropertyGroup from "./PropertyGroup.svelte" |
||||
|
import FlatButtonGroup from "./FlatButtonGroup.svelte" |
||||
|
|
||||
|
export let panelDefinition = {} |
||||
|
export let componentInstance = {} |
||||
|
export let componentDefinition = {} |
||||
|
export let onStyleChanged = () => {} |
||||
|
|
||||
|
let selectedCategory = "normal" |
||||
|
|
||||
|
const getProperties = name => panelDefinition[name] |
||||
|
|
||||
|
function onChange(category) { |
||||
|
selectedCategory = category |
||||
|
} |
||||
|
|
||||
|
const buttonProps = [ |
||||
|
{ value: "normal", text: "Normal" }, |
||||
|
{ value: "hover", text: "Hover" }, |
||||
|
{ value: "active", text: "Active" }, |
||||
|
{ value: "selected", text: "Selected" }, |
||||
|
] |
||||
|
|
||||
|
$: propertyGroupNames = Object.keys(panelDefinition) |
||||
|
</script> |
||||
|
|
||||
|
<div class="design-view-container"> |
||||
|
|
||||
|
<div class="design-view-state-categories"> |
||||
|
<FlatButtonGroup value={selectedCategory} {buttonProps} {onChange} /> |
||||
|
</div> |
||||
|
|
||||
|
<div class="design-view-property-groups"> |
||||
|
{#if propertyGroupNames.length > 0} |
||||
|
{#each propertyGroupNames as groupName} |
||||
|
<PropertyGroup |
||||
|
name={groupName} |
||||
|
properties={getProperties(groupName)} |
||||
|
styleCategory={selectedCategory} |
||||
|
{onStyleChanged} |
||||
|
{componentDefinition} |
||||
|
{componentInstance} /> |
||||
|
{/each} |
||||
|
{:else} |
||||
|
<div class="no-design"> |
||||
|
<span>This component does not have any design properties</span> |
||||
|
</div> |
||||
|
{/if} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.design-view-container { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.design-view-state-categories { |
||||
|
flex: 0 0 50px; |
||||
|
} |
||||
|
|
||||
|
.design-view-property-groups { |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.no-design { |
||||
|
text-align: center; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,38 @@ |
|||||
|
<script> |
||||
|
export let value = "" |
||||
|
export let text = "" |
||||
|
export let icon = "" |
||||
|
export let onClick = value => {} |
||||
|
export let selected = false |
||||
|
|
||||
|
$: useIcon = !!icon |
||||
|
</script> |
||||
|
|
||||
|
<div class="flatbutton" class:selected on:click={() => onClick(value || text)}> |
||||
|
{#if useIcon} |
||||
|
<i class={icon} /> |
||||
|
{:else} |
||||
|
<span>{text}</span> |
||||
|
{/if} |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.flatbutton { |
||||
|
cursor: pointer; |
||||
|
padding: 8px 4px; |
||||
|
text-align: center; |
||||
|
background: #ffffff; |
||||
|
color: var(--ink-light); |
||||
|
border-radius: 5px; |
||||
|
font-family: Roboto; |
||||
|
font-size: 13px; |
||||
|
font-weight: 500; |
||||
|
transition: background 0.5s, color 0.5s ease; |
||||
|
text-rendering: optimizeLegibility; |
||||
|
} |
||||
|
|
||||
|
.selected { |
||||
|
background: #808192; |
||||
|
color: #ffffff; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,52 @@ |
|||||
|
<script> |
||||
|
import { onMount } from "svelte" |
||||
|
|
||||
|
import FlatButton from "./FlatButton.svelte" |
||||
|
export let buttonProps = [] |
||||
|
export let isMultiSelect = false |
||||
|
export let value = [] |
||||
|
export let initialValue = "" |
||||
|
export let onChange = selected => {} |
||||
|
|
||||
|
onMount(() => { |
||||
|
if (!value && !!initialValue) { |
||||
|
value = initialValue |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
function onButtonClicked(v) { |
||||
|
let val |
||||
|
if (isMultiSelect) { |
||||
|
if (value.includes(v)) { |
||||
|
let idx = value.findIndex(i => i === v) |
||||
|
val = [...value].splice(idx, 1) |
||||
|
} else { |
||||
|
val = [...value, v] |
||||
|
} |
||||
|
} else { |
||||
|
val = v |
||||
|
} |
||||
|
onChange(val) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div class="flatbutton-group"> |
||||
|
{#each buttonProps as props} |
||||
|
<div class="button-container"> |
||||
|
<FlatButton |
||||
|
selected={value.includes(props.value)} |
||||
|
onClick={onButtonClicked} |
||||
|
{...props} /> |
||||
|
</div> |
||||
|
{/each} |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.flatbutton-group { |
||||
|
display: flex; |
||||
|
} |
||||
|
|
||||
|
.button-container { |
||||
|
flex: 1; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,36 @@ |
|||||
|
<script> |
||||
|
import { onMount } from "svelte" |
||||
|
export let value = "" |
||||
|
export let onChange = value => {} |
||||
|
export let options = [] |
||||
|
export let initialValue = "" |
||||
|
export let styleBindingProperty = "" |
||||
|
|
||||
|
const handleStyleBind = value => |
||||
|
!!styleBindingProperty ? { style: `${styleBindingProperty}: ${value}` } : {} |
||||
|
|
||||
|
$: isOptionsObject = options.every(o => typeof o === "object") |
||||
|
|
||||
|
onMount(() => { |
||||
|
if (!value && !!initialValue) { |
||||
|
value = initialValue |
||||
|
} |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<select |
||||
|
class="uk-select uk-form-small" |
||||
|
{value} |
||||
|
on:change={ev => onChange(ev.target.value)}> |
||||
|
{#if isOptionsObject} |
||||
|
{#each options as { value, label }} |
||||
|
<option {...handleStyleBind(value || label)} value={value || label}> |
||||
|
{label} |
||||
|
</option> |
||||
|
{/each} |
||||
|
{:else} |
||||
|
{#each options as value} |
||||
|
<option {...handleStyleBind(value)} {value}>{value}</option> |
||||
|
{/each} |
||||
|
{/if} |
||||
|
</select> |
||||
@ -0,0 +1,66 @@ |
|||||
|
<script> |
||||
|
import { onMount, getContext } from "svelte" |
||||
|
|
||||
|
export let label = "" |
||||
|
export let control = null |
||||
|
export let key = "" |
||||
|
export let value |
||||
|
export let props = {} |
||||
|
export let onChange = () => {} |
||||
|
|
||||
|
function handleChange(key, v) { |
||||
|
if (v.target) { |
||||
|
let val = props.valueKey ? v.target[props.valueKey] : v.target.value |
||||
|
onChange(key, val) |
||||
|
} else { |
||||
|
onChange(key, v) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const safeValue = () => { |
||||
|
return value === undefined && props.defaultValue !== undefined |
||||
|
? props.defaultValue |
||||
|
: value |
||||
|
} |
||||
|
|
||||
|
//Incase the component has a different value key name |
||||
|
const handlevalueKey = value => |
||||
|
props.valueKey ? { [props.valueKey]: safeValue() } : { value: safeValue() } |
||||
|
</script> |
||||
|
|
||||
|
<div class="property-control"> |
||||
|
<div class="label">{label}</div> |
||||
|
<div class="control"> |
||||
|
<svelte:component |
||||
|
this={control} |
||||
|
{...handlevalueKey(value)} |
||||
|
on:change={val => handleChange(key, val)} |
||||
|
onChange={val => handleChange(key, val)} |
||||
|
{...props} /> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.property-control { |
||||
|
display: flex; |
||||
|
flex-flow: row; |
||||
|
margin: 8px 0px; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.label { |
||||
|
flex: 0 0 50px; |
||||
|
font-size: 12px; |
||||
|
font-weight: 400; |
||||
|
text-align: left; |
||||
|
color: var(--ink); |
||||
|
margin-right: auto; |
||||
|
text-transform: capitalize; |
||||
|
} |
||||
|
|
||||
|
.control { |
||||
|
flex: 1; |
||||
|
padding-left: 2px; |
||||
|
max-width: 164px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,82 @@ |
|||||
|
<script> |
||||
|
import { excludeProps } from "./propertyCategories.js" |
||||
|
import PropertyControl from "./PropertyControl.svelte" |
||||
|
|
||||
|
export let name = "" |
||||
|
export let styleCategory = "normal" |
||||
|
export let properties = [] |
||||
|
export let componentInstance = {} |
||||
|
export let onStyleChanged = () => {} |
||||
|
|
||||
|
export let show = false |
||||
|
|
||||
|
const capitalize = name => name[0].toUpperCase() + name.slice(1) |
||||
|
|
||||
|
$: icon = show ? "ri-arrow-down-s-fill" : "ri-arrow-right-s-fill" |
||||
|
$: style = componentInstance["_styles"][styleCategory] || {} |
||||
|
</script> |
||||
|
|
||||
|
<div class="property-group-container"> |
||||
|
<div class="property-group-name" on:click={() => (show = !show)}> |
||||
|
<div class="icon"> |
||||
|
<i class={icon} /> |
||||
|
</div> |
||||
|
<div class="name">{capitalize(name)}</div> |
||||
|
</div> |
||||
|
<div class="property-panel" class:show> |
||||
|
|
||||
|
{#each properties as props} |
||||
|
<PropertyControl |
||||
|
label={props.label} |
||||
|
control={props.control} |
||||
|
key={props.key} |
||||
|
value={style[props.key]} |
||||
|
onChange={(key, value) => onStyleChanged(styleCategory, key, value)} |
||||
|
props={{ ...excludeProps(props, ['control', 'label']) }} /> |
||||
|
{/each} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.property-group-container { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
height: auto; |
||||
|
background: var(--grey-light); |
||||
|
margin: 0px 0px 4px 0px; |
||||
|
padding: 8px 12px; |
||||
|
justify-content: center; |
||||
|
border-radius: 4px; |
||||
|
} |
||||
|
|
||||
|
.property-group-name { |
||||
|
cursor: pointer; |
||||
|
display: flex; |
||||
|
flex-flow: row nowrap; |
||||
|
} |
||||
|
|
||||
|
.name { |
||||
|
flex: 1; |
||||
|
text-align: left; |
||||
|
padding-top: 2px; |
||||
|
font-size: 14px; |
||||
|
font-weight: 500; |
||||
|
letter-spacing: 0.14px; |
||||
|
color: var(--ink); |
||||
|
} |
||||
|
|
||||
|
.icon { |
||||
|
flex: 0 0 20px; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.property-panel { |
||||
|
height: 0px; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.show { |
||||
|
overflow: auto; |
||||
|
height: auto; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,41 @@ |
|||||
|
<script> |
||||
|
import PropertyControl from "./PropertyControl.svelte" |
||||
|
import InputGroup from "../common/Inputs/InputGroup.svelte" |
||||
|
import Colorpicker from "../common/Colorpicker.svelte" |
||||
|
import { excludeProps } from "./propertyCategories.js" |
||||
|
|
||||
|
export let panelDefinition = [] |
||||
|
export let componentDefinition = {} |
||||
|
export let componentInstance = {} |
||||
|
export let onChange = () => {} |
||||
|
|
||||
|
const propExistsOnComponentDef = prop => prop in componentDefinition.props |
||||
|
|
||||
|
function handleChange(key, data) { |
||||
|
data.target ? onChange(key, data.target.value) : onChange(key, data) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
{#if panelDefinition.length > 0} |
||||
|
{#each panelDefinition as definition} |
||||
|
{#if propExistsOnComponentDef(definition.key)} |
||||
|
<PropertyControl |
||||
|
control={definition.control} |
||||
|
label={definition.label} |
||||
|
key={definition.key} |
||||
|
value={componentInstance[definition.key]} |
||||
|
{onChange} |
||||
|
props={{ ...excludeProps(definition, ['control', 'label']) }} /> |
||||
|
{/if} |
||||
|
{/each} |
||||
|
{:else} |
||||
|
<div> |
||||
|
<span>This component does not have any settings.</span> |
||||
|
</div> |
||||
|
{/if} |
||||
|
|
||||
|
<style> |
||||
|
div { |
||||
|
text-align: center; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,240 @@ |
|||||
|
<script> |
||||
|
import ComponentsHierarchy from "./ComponentsHierarchy.svelte" |
||||
|
import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte" |
||||
|
import PageLayout from "./PageLayout.svelte" |
||||
|
import PagesList from "./PagesList.svelte" |
||||
|
import { store } from "builderStore" |
||||
|
import IconButton from "components/common/IconButton.svelte" |
||||
|
import NewScreen from "./NewScreen.svelte" |
||||
|
import CurrentItemPreview from "./CurrentItemPreview.svelte" |
||||
|
import SettingsView from "./SettingsView.svelte" |
||||
|
import PageView from "./PageView.svelte" |
||||
|
import ComponentsPaneSwitcher from "./ComponentsPaneSwitcher.svelte" |
||||
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte" |
||||
|
import { last } from "lodash/fp" |
||||
|
import { AddIcon } from "components/common/Icons" |
||||
|
|
||||
|
let newScreenPicker |
||||
|
let confirmDeleteDialog |
||||
|
let componentToDelete = "" |
||||
|
|
||||
|
const newScreen = () => { |
||||
|
newScreenPicker.show() |
||||
|
} |
||||
|
|
||||
|
let settingsView |
||||
|
const settings = () => { |
||||
|
settingsView.show() |
||||
|
} |
||||
|
|
||||
|
const confirmDeleteComponent = component => { |
||||
|
componentToDelete = component |
||||
|
confirmDeleteDialog.show() |
||||
|
} |
||||
|
|
||||
|
const lastPartOfName = c => (c ? last(c.split("/")) : "") |
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
|
||||
|
<div class="ui-nav"> |
||||
|
|
||||
|
<div class="pages-list-container"> |
||||
|
<div class="nav-header"> |
||||
|
<span class="navigator-title">Navigator</span> |
||||
|
<div class="border-line" /> |
||||
|
|
||||
|
<span class="components-nav-page">Pages</span> |
||||
|
</div> |
||||
|
|
||||
|
<div class="nav-items-container"> |
||||
|
<PagesList /> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="border-line" /> |
||||
|
|
||||
|
<PageLayout layout={$store.pages[$store.currentPageName]} /> |
||||
|
|
||||
|
<div class="border-line" /> |
||||
|
|
||||
|
<div class="components-list-container"> |
||||
|
<div class="nav-group-header"> |
||||
|
<span class="components-nav-header" style="margin-top: 0;"> |
||||
|
Screens |
||||
|
</span> |
||||
|
<div> |
||||
|
<button on:click={newScreen}> |
||||
|
<AddIcon /> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="nav-items-container"> |
||||
|
<ComponentsHierarchy screens={$store.screens} /> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<div class="preview-pane"> |
||||
|
<CurrentItemPreview /> |
||||
|
</div> |
||||
|
|
||||
|
{#if $store.currentFrontEndType === 'screen' || $store.currentFrontEndType === 'page'} |
||||
|
<div class="components-pane"> |
||||
|
<ComponentsPaneSwitcher /> |
||||
|
</div> |
||||
|
{/if} |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<NewScreen bind:this={newScreenPicker} /> |
||||
|
<SettingsView bind:this={settingsView} /> |
||||
|
|
||||
|
<ConfirmDialog |
||||
|
bind:this={confirmDeleteDialog} |
||||
|
title="Confirm Delete" |
||||
|
body={`Are you sure you wish to delete this '${lastPartOfName(componentToDelete)}' component`} |
||||
|
okText="Delete Component" |
||||
|
onOk={() => store.deleteComponent(componentToDelete)} /> |
||||
|
|
||||
|
<style> |
||||
|
button { |
||||
|
cursor: pointer; |
||||
|
outline: none; |
||||
|
border: none; |
||||
|
border-radius: 5px; |
||||
|
width: 20px; |
||||
|
padding-bottom: 10px; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
.root { |
||||
|
display: grid; |
||||
|
grid-template-columns: 275px 1fr 300px; |
||||
|
height: 100%; |
||||
|
width: 100%; |
||||
|
background: #fafafa; |
||||
|
} |
||||
|
|
||||
|
@media only screen and (min-width: 1800px) { |
||||
|
.root { |
||||
|
display: grid; |
||||
|
grid-template-columns: 300px 1fr 300px; |
||||
|
height: 100%; |
||||
|
width: 100%; |
||||
|
background: #fafafa; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.ui-nav { |
||||
|
grid-column: 1; |
||||
|
background-color: var(--white); |
||||
|
height: calc(100vh - 49px); |
||||
|
padding: 0; |
||||
|
overflow: scroll; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.preview-pane { |
||||
|
grid-column: 2; |
||||
|
margin: 40px; |
||||
|
background: #fff; |
||||
|
border-radius: 5px; |
||||
|
box-shadow: 0 0px 6px rgba(0, 0, 0, 0.05); |
||||
|
} |
||||
|
|
||||
|
.components-pane { |
||||
|
grid-column: 3; |
||||
|
background-color: var(--white); |
||||
|
height: 100vh; |
||||
|
overflow-y: scroll; |
||||
|
} |
||||
|
|
||||
|
.components-nav-page { |
||||
|
font-size: 13px; |
||||
|
color: #000333; |
||||
|
text-transform: uppercase; |
||||
|
padding-left: 20px; |
||||
|
margin-top: 20px; |
||||
|
font-weight: 600; |
||||
|
opacity: 0.4; |
||||
|
letter-spacing: 1px; |
||||
|
} |
||||
|
|
||||
|
.components-nav-header { |
||||
|
font-size: 13px; |
||||
|
color: #000333; |
||||
|
text-transform: uppercase; |
||||
|
margin-top: 20px; |
||||
|
font-weight: 600; |
||||
|
opacity: 0.4; |
||||
|
letter-spacing: 1px; |
||||
|
} |
||||
|
|
||||
|
.nav-header { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
margin-top: 20px; |
||||
|
} |
||||
|
|
||||
|
.nav-items-container { |
||||
|
padding: 1rem 0rem 0rem 0rem; |
||||
|
} |
||||
|
|
||||
|
.nav-group-header { |
||||
|
display: flex; |
||||
|
padding: 0px 20px 0px 20px; |
||||
|
font-size: 0.9rem; |
||||
|
font-weight: bold; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.nav-group-header > div:nth-child(1) { |
||||
|
padding: 0rem 0.5rem 0rem 0rem; |
||||
|
vertical-align: bottom; |
||||
|
grid-column-start: icon; |
||||
|
margin-right: 5px; |
||||
|
} |
||||
|
|
||||
|
.nav-group-header > span:nth-child(3) { |
||||
|
margin-left: 5px; |
||||
|
vertical-align: bottom; |
||||
|
grid-column-start: title; |
||||
|
margin-top: auto; |
||||
|
} |
||||
|
|
||||
|
.nav-group-header > div:nth-child(3) { |
||||
|
vertical-align: bottom; |
||||
|
grid-column-start: button; |
||||
|
cursor: pointer; |
||||
|
color: var(--primary75); |
||||
|
} |
||||
|
|
||||
|
.nav-group-header > div:nth-child(3):hover { |
||||
|
color: var(--primary75); |
||||
|
} |
||||
|
|
||||
|
.navigator-title { |
||||
|
font-size: 14px; |
||||
|
color: var(--secondary100); |
||||
|
font-weight: 600; |
||||
|
text-transform: uppercase; |
||||
|
padding: 0 20px 20px 20px; |
||||
|
line-height: 1rem !important; |
||||
|
letter-spacing: 1px; |
||||
|
} |
||||
|
|
||||
|
.border-line { |
||||
|
border-bottom: 1px solid #d8d8d8; |
||||
|
} |
||||
|
|
||||
|
.components-list-container { |
||||
|
padding: 20px 0px 0 0; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,173 @@ |
|||||
|
import Input from "../common/Input.svelte" |
||||
|
import OptionSelect from "./OptionSelect.svelte" |
||||
|
import InputGroup from "../common/Inputs/InputGroup.svelte" |
||||
|
// import Colorpicker from "../common/Colorpicker.svelte"
|
||||
|
/* |
||||
|
TODO: Allow for default values for all properties |
||||
|
*/ |
||||
|
|
||||
|
export const layout = [ |
||||
|
{ |
||||
|
label: "Direction", |
||||
|
key: "flex-direction", |
||||
|
control: OptionSelect, |
||||
|
initialValue: "columnReverse", |
||||
|
options: [ |
||||
|
{ label: "row" }, |
||||
|
{ label: "row-reverse", value: "rowReverse" }, |
||||
|
{ label: "column" }, |
||||
|
{ label: "column-reverse", value: "columnReverse" }, |
||||
|
], |
||||
|
}, |
||||
|
{ label: "Justify", key: "justify-content", control: Input }, |
||||
|
{ label: "Align", key: "align-items", control: Input }, |
||||
|
{ |
||||
|
label: "Wrap", |
||||
|
key: "flex-wrap", |
||||
|
control: OptionSelect, |
||||
|
options: [{ label: "wrap" }, { label: "no wrap", value: "noWrap" }], |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
const spacingMeta = [ |
||||
|
{ placeholder: "T" }, |
||||
|
{ placeholder: "R" }, |
||||
|
{ placeholder: "B" }, |
||||
|
{ placeholder: "L" }, |
||||
|
] |
||||
|
export const spacing = [ |
||||
|
{ |
||||
|
label: "Padding", |
||||
|
key: "padding", |
||||
|
control: InputGroup, |
||||
|
meta: spacingMeta, |
||||
|
}, |
||||
|
{ label: "Margin", key: "margin", control: InputGroup, meta: spacingMeta }, |
||||
|
] |
||||
|
|
||||
|
export const size = [ |
||||
|
{ label: "Width", key: "width", control: Input }, |
||||
|
{ label: "Height", key: "height", control: Input }, |
||||
|
{ label: "Min W", key: "min-width", control: Input }, |
||||
|
{ label: "Min H", key: "min-height", control: Input }, |
||||
|
{ label: "Max W", key: "max-width", control: Input }, |
||||
|
{ label: "Max H", key: "max-height", control: Input }, |
||||
|
] |
||||
|
|
||||
|
export const position = [ |
||||
|
{ |
||||
|
label: "Position", |
||||
|
key: "position", |
||||
|
control: OptionSelect, |
||||
|
options: [ |
||||
|
{ label: "static" }, |
||||
|
{ label: "relative" }, |
||||
|
{ label: "fixed" }, |
||||
|
{ label: "absolute" }, |
||||
|
{ label: "sticky" }, |
||||
|
], |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
export const typography = [ |
||||
|
{ |
||||
|
label: "Font", |
||||
|
key: "font-family", |
||||
|
control: OptionSelect, |
||||
|
defaultValue: "initial", |
||||
|
options: [ |
||||
|
"initial", |
||||
|
"Times New Roman", |
||||
|
"Georgia", |
||||
|
"Arial", |
||||
|
"Arial Black", |
||||
|
"Comic Sans MS", |
||||
|
"Impact", |
||||
|
"Lucida Sans Unicode", |
||||
|
], |
||||
|
styleBindingProperty: "font-family", |
||||
|
}, |
||||
|
{ |
||||
|
label: "Weight", |
||||
|
key: "font-weight", |
||||
|
control: OptionSelect, |
||||
|
options: [ |
||||
|
{ label: "normal" }, |
||||
|
{ label: "bold" }, |
||||
|
{ label: "bolder" }, |
||||
|
{ label: "lighter" }, |
||||
|
], |
||||
|
}, |
||||
|
{ label: "size", key: "font-size", defaultValue: "", control: Input }, |
||||
|
{ label: "Line H", key: "line-height", control: Input }, |
||||
|
{ |
||||
|
label: "Color", |
||||
|
key: "color", |
||||
|
control: OptionSelect, |
||||
|
options: ["black", "white", "red", "blue", "green"], |
||||
|
}, |
||||
|
{ |
||||
|
label: "align", |
||||
|
key: "text-align", |
||||
|
control: OptionSelect, |
||||
|
options: ["initial", "left", "right", "center", "justify"], |
||||
|
}, //custom
|
||||
|
{ label: "transform", key: "text-transform", control: Input }, //custom
|
||||
|
{ label: "style", key: "font-style", control: Input }, //custom
|
||||
|
] |
||||
|
|
||||
|
export const background = [ |
||||
|
{ |
||||
|
label: "Background", |
||||
|
key: "background", |
||||
|
control: OptionSelect, |
||||
|
options: ["black", "white", "red", "blue", "green"], |
||||
|
}, |
||||
|
{ label: "Image", key: "image", control: Input }, //custom
|
||||
|
] |
||||
|
|
||||
|
export const border = [ |
||||
|
{ label: "Radius", key: "border-radius", control: Input }, |
||||
|
{ label: "Width", key: "border-width", control: Input }, //custom
|
||||
|
{ |
||||
|
label: "Color", |
||||
|
key: "border-color", |
||||
|
control: OptionSelect, |
||||
|
options: ["black", "white", "red", "blue", "green"], |
||||
|
}, |
||||
|
{ label: "Style", key: "border-style", control: Input }, |
||||
|
] |
||||
|
|
||||
|
export const effects = [ |
||||
|
{ label: "Opacity", key: "opacity", control: Input }, |
||||
|
{ label: "Rotate", key: "transform", control: Input }, //needs special control
|
||||
|
{ label: "Shadow", key: "box-shadow", control: Input }, |
||||
|
] |
||||
|
|
||||
|
export const transitions = [ |
||||
|
{ label: "Property", key: "transition-property", control: Input }, |
||||
|
{ label: "Duration", key: "transition-timing-function", control: Input }, |
||||
|
{ label: "Ease", key: "transition-ease", control: Input }, |
||||
|
] |
||||
|
|
||||
|
export const all = { |
||||
|
layout, |
||||
|
spacing, |
||||
|
size, |
||||
|
position, |
||||
|
typography, |
||||
|
background, |
||||
|
border, |
||||
|
effects, |
||||
|
transitions, |
||||
|
} |
||||
|
|
||||
|
export function excludeProps(props, propsToExclude) { |
||||
|
const modifiedProps = {} |
||||
|
for (const prop in props) { |
||||
|
if (!propsToExclude.includes(prop)) { |
||||
|
modifiedProps[prop] = props[prop] |
||||
|
} |
||||
|
} |
||||
|
return modifiedProps |
||||
|
} |
||||
@ -1 +0,0 @@ |
|||||
export { default as BlockPanel } from "./BlockPanel.svelte"; |
|
||||
@ -0,0 +1,88 @@ |
|||||
|
<script> |
||||
|
import { onMount } from "svelte" |
||||
|
import { backendUiStore, workflowStore } from "builderStore" |
||||
|
import api from "builderStore/api" |
||||
|
import WorkflowBlockSetup from "./WorkflowBlockSetup.svelte"; |
||||
|
|
||||
|
$: workflow = $workflowStore.workflows.find( |
||||
|
wf => wf._id === $workflowStore.selectedWorkflowId |
||||
|
) |
||||
|
$: workflowBlock = $workflowStore.selectedWorkflowBlock |
||||
|
|
||||
|
function deleteWorkflow() { |
||||
|
workflowStore.actions.deleteWorkflow(workflow) |
||||
|
} |
||||
|
|
||||
|
function deleteWorkflowBlock() { |
||||
|
// TODO: implement |
||||
|
workflowStore.actions.deleteWorkflowBlock(workflowBlock) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<section> |
||||
|
<header> |
||||
|
<span>Setup</span> |
||||
|
</header> |
||||
|
<div class="panel-body"> |
||||
|
{#if workflowBlock} |
||||
|
<WorkflowBlockSetup {workflowBlock} /> |
||||
|
<button class="delete-workflow-button hoverable" on:click={deleteWorkflowBlock}> |
||||
|
Delete {workflowBlock.type} |
||||
|
</button> |
||||
|
{:else if $workflowStore.selectedWorkflowId} |
||||
|
<label class="uk-form-label">Workflow: {workflow.name}</label> |
||||
|
<div class="uk-margin"> |
||||
|
<label class="uk-form-label">Name</label> |
||||
|
<div class="uk-form-controls"> |
||||
|
<input |
||||
|
type="text" |
||||
|
class="budibase__input" |
||||
|
bind:value={workflow.name} /> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="uk-margin"> |
||||
|
<label class="uk-form-label">User Access</label> |
||||
|
Some User Access Stuff Here |
||||
|
</div> |
||||
|
<button class="delete-workflow-button hoverable" on:click={deleteWorkflow}> |
||||
|
Delete Workflow |
||||
|
</button> |
||||
|
{/if} |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<style> |
||||
|
section { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
header { |
||||
|
font-size: 20px; |
||||
|
font-weight: bold; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
} |
||||
|
|
||||
|
span:not(.selected) { |
||||
|
color: var(--dark-grey); |
||||
|
} |
||||
|
|
||||
|
label { |
||||
|
font-weight: 500; |
||||
|
font-size: 14px; |
||||
|
color: var(--font); |
||||
|
} |
||||
|
|
||||
|
.delete-workflow-button { |
||||
|
font-family: Roboto; |
||||
|
width: 100%; |
||||
|
border: solid 1px #f2f2f2; |
||||
|
border-radius: 2px; |
||||
|
background: var(--white); |
||||
|
height: 32px; |
||||
|
font-size: 12px; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,17 @@ |
|||||
|
<script> |
||||
|
export let workflowBlock |
||||
|
$: workflowArgs = Object.keys(workflowBlock.args) |
||||
|
</script> |
||||
|
|
||||
|
<label class="uk-form-label">{workflowBlock.heading}: {workflowBlock.heading}</label> |
||||
|
{#each workflowArgs as workflowArg} |
||||
|
<div class="uk-margin"> |
||||
|
<label class="uk-form-label">Name</label> |
||||
|
<div class="uk-form-controls"> |
||||
|
<input |
||||
|
type="text" |
||||
|
class="budibase__input" |
||||
|
bind:value={workflowBlock.args[workflowArg]} /> |
||||
|
</div> |
||||
|
</div> |
||||
|
{/each} |
||||
@ -0,0 +1 @@ |
|||||
|
export { default as SetupPanel } from "./SetupPanel.svelte"; |
||||
@ -0,0 +1,51 @@ |
|||||
|
<script> |
||||
|
import { onMount } from "svelte" |
||||
|
import { workflowStore, backendUiStore } from "builderStore" |
||||
|
import Flowchart from "./svelte-flows/Flowchart.svelte" |
||||
|
import api from "builderStore/api" |
||||
|
|
||||
|
let canvas |
||||
|
let workflow |
||||
|
let uiTree |
||||
|
let instanceId = $backendUiStore.selectedDatabase._id |
||||
|
|
||||
|
$: workflow = $workflowStore.workflows.find( |
||||
|
wf => wf._id === $workflowStore.selectedWorkflowId |
||||
|
) |
||||
|
|
||||
|
// Build a renderable UI Tree for the flowchart generator |
||||
|
function buildUiTree(block, tree = []) { |
||||
|
if (!block) return tree |
||||
|
|
||||
|
tree.push({ |
||||
|
type: block.type, |
||||
|
heading: block.actionId, |
||||
|
args: block.args, |
||||
|
body: JSON.stringify(block.args), |
||||
|
}) |
||||
|
|
||||
|
return buildUiTree(block.next, tree) |
||||
|
} |
||||
|
|
||||
|
$: if (workflow) uiTree = workflow.definition ? buildUiTree(workflow.definition.next) : [] |
||||
|
|
||||
|
function onDelete(block) { |
||||
|
// TODO finish |
||||
|
workflowStore.actions.deleteWorkflowBlock(block); |
||||
|
} |
||||
|
|
||||
|
function onSelect(block) { |
||||
|
workflowStore.update(state => { |
||||
|
state.selectedWorkflowBlock = block |
||||
|
return state |
||||
|
}) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<section> |
||||
|
<Flowchart |
||||
|
blocks={uiTree} |
||||
|
onSelect={onSelect} |
||||
|
on:delete={onDelete} |
||||
|
/> |
||||
|
</section> |
||||
@ -0,0 +1,23 @@ |
|||||
|
<script> |
||||
|
import FlowItem from "./FlowItem.svelte" |
||||
|
|
||||
|
export let blocks = [] |
||||
|
export let onSelect |
||||
|
</script> |
||||
|
|
||||
|
<section class="canvas"> |
||||
|
{#each blocks as block, idx} |
||||
|
<FlowItem onSelect={onSelect} {block} /> |
||||
|
{#if idx !== blocks.length - 1} |
||||
|
<i class="ri-arrow-down-line"></i> |
||||
|
{/if} |
||||
|
{/each} |
||||
|
</section> |
||||
|
|
||||
|
<style> |
||||
|
.canvas { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,33 @@ |
|||||
|
<script> |
||||
|
export let onSelect |
||||
|
export let block |
||||
|
|
||||
|
function selectBlock() { |
||||
|
onSelect(block); |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div class="hoverable" on:click={selectBlock}> |
||||
|
<header>{block.heading}</header> |
||||
|
<hr /> |
||||
|
<p>{block.body}</p> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
div { |
||||
|
border: 1px solid black; |
||||
|
width: 320px; |
||||
|
padding: 20px; |
||||
|
margin-bottom: 60px; |
||||
|
border-radius: 5px; |
||||
|
transition: 0.3s all; |
||||
|
box-shadow: 0 4px 30px 0 rgba(57, 60, 68, 0.08); |
||||
|
background-color: var(--font); |
||||
|
font-size: 16px; |
||||
|
color: var(--white); |
||||
|
} |
||||
|
|
||||
|
div:hover { |
||||
|
transform: scale(1.05); |
||||
|
} |
||||
|
</style> |
||||
@ -1 +0,0 @@ |
|||||
export { default as WorkflowList } from "./WorkflowList.svelte"; |
|
||||
@ -0,0 +1,51 @@ |
|||||
|
<script> |
||||
|
import { workflowStore } from "builderStore"; |
||||
|
|
||||
|
export let blockType |
||||
|
export let blockDefinition |
||||
|
|
||||
|
function addBlockToWorkflow() { |
||||
|
// TODO: store the block type in the DB as well |
||||
|
workflowStore.actions.addBlockToWorkflow({ blockDefinition, blockType }); |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div class="workflow-block hoverable" on:click={addBlockToWorkflow}> |
||||
|
<div> |
||||
|
<i class={blockDefinition.icon} /> |
||||
|
</div> |
||||
|
<div class="workflow-text"> |
||||
|
<h4>{blockDefinition.name}</h4> |
||||
|
<p>{blockDefinition.description}</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.workflow-block { |
||||
|
display: flex; |
||||
|
padding: 20px; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.workflow-text { |
||||
|
margin-left: 10px; |
||||
|
} |
||||
|
|
||||
|
i { |
||||
|
background: var(--secondary); |
||||
|
color: var(--dark-grey); |
||||
|
padding: 10px; |
||||
|
} |
||||
|
|
||||
|
h4 { |
||||
|
font-size: 14px; |
||||
|
font-weight: 500; |
||||
|
margin-bottom: 5px; |
||||
|
} |
||||
|
|
||||
|
p { |
||||
|
font-size: 12px; |
||||
|
color: var(--dark-grey); |
||||
|
margin: 0; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,61 @@ |
|||||
|
<script> |
||||
|
import { onMount } from "svelte" |
||||
|
import { backendUiStore, workflowStore } from "builderStore" |
||||
|
import { WorkflowList, BlockList } from "./" |
||||
|
import api from "builderStore/api" |
||||
|
import blockDefinitions from "./blockDefinitions" |
||||
|
|
||||
|
const WORKFLOW_TABS = [ |
||||
|
{ |
||||
|
name: "Workflows", |
||||
|
key: "WORKFLOWS", |
||||
|
}, |
||||
|
{ |
||||
|
name: "Add", |
||||
|
key: "ADD", |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
let selectedTab = "WORKFLOWS" |
||||
|
let definitions = [] |
||||
|
</script> |
||||
|
|
||||
|
<section> |
||||
|
<header> |
||||
|
<span |
||||
|
class="hoverable" |
||||
|
class:selected={selectedTab === 'WORKFLOWS'} |
||||
|
on:click={() => (selectedTab = 'WORKFLOWS')}> |
||||
|
Workflows |
||||
|
</span> |
||||
|
{#if $workflowStore.selectedWorkflowId} |
||||
|
<span |
||||
|
class="hoverable" |
||||
|
class:selected={selectedTab === 'ADD'} |
||||
|
on:click={() => (selectedTab = 'ADD')}> |
||||
|
Add |
||||
|
</span> |
||||
|
{/if} |
||||
|
</header> |
||||
|
{#if selectedTab === 'WORKFLOWS'} |
||||
|
<WorkflowList /> |
||||
|
{:else if selectedTab === 'ADD'} |
||||
|
<BlockList /> |
||||
|
{/if} |
||||
|
</section> |
||||
|
|
||||
|
<style> |
||||
|
header { |
||||
|
font-size: 20px; |
||||
|
font-weight: bold; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
margin-bottom: 20px; |
||||
|
} |
||||
|
|
||||
|
span:not(.selected) { |
||||
|
color: var(--dark-grey); |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,3 @@ |
|||||
|
export { default as WorkflowPanel } from "./WorkflowPanel.svelte"; |
||||
|
export { default as BlockList } from "./BlockList/BlockList.svelte"; |
||||
|
export { default as WorkflowList } from "./WorkflowList/WorkflowList.svelte"; |
||||
@ -1,49 +0,0 @@ |
|||||
<script> |
|
||||
import { onMount } from "svelte" |
|
||||
import { workflowStore, backendUiStore } from "builderStore"; |
|
||||
import api from "builderStore/api"; |
|
||||
|
|
||||
let canvas |
|
||||
let workflow |
|
||||
let instanceId = $backendUiStore.selectedDatabase._id; |
|
||||
|
|
||||
$: workflow = $workflowStore.workflows.find(wf => wf._id === $workflowStore.selectedWorkflowId) |
|
||||
|
|
||||
// $: if (workflow && workflow.uiTree) flowy.import(workflow.uiTree); |
|
||||
|
|
||||
onMount(() => { |
|
||||
flowy(canvas, onGrab, onRelease, onSnap); |
|
||||
}); |
|
||||
|
|
||||
function onGrab(block) { |
|
||||
console.log(block); |
|
||||
} |
|
||||
|
|
||||
function onSnap(block, first, parent){ |
|
||||
workflow.uiTree = flowy.output(); |
|
||||
workflowStore.actions.update({ instanceId, workflow }) |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
function onRelease() { |
|
||||
} |
|
||||
|
|
||||
// function onGrab(block) { |
|
||||
// // When the user grabs a block |
|
||||
// } |
|
||||
// function onRelease() { |
|
||||
// // When the user releases a block |
|
||||
// console.log(flowy.output()) |
|
||||
// } |
|
||||
// function onSnap(block, first, parent) { |
|
||||
// console.log(flowy.output()) |
|
||||
// console.log(block, first, parent) |
|
||||
// // When a block snaps with another one |
|
||||
// } |
|
||||
// function onRearrange(block, parent) { |
|
||||
// console.log(block, parent) |
|
||||
// // When a block is rearranged |
|
||||
// } |
|
||||
</script> |
|
||||
|
|
||||
<section bind:this={canvas} class="canvas" /> |
|
||||
@ -1,5 +1,5 @@ |
|||||
<script> |
<script> |
||||
import WorkflowBuilder from "./flowy/WorkflowBuilder.svelte"; |
import WorkflowBuilder from "./WorkflowBuilder/WorkflowBuilder.svelte"; |
||||
</script> |
</script> |
||||
|
|
||||
<WorkflowBuilder /> |
<WorkflowBuilder /> |
||||
@ -1,38 +0,0 @@ |
|||||
import api from "builderStore/api"; |
|
||||
|
|
||||
class Orchestrator { |
|
||||
set strategy(strategy) { |
|
||||
this._stategy = strategy |
|
||||
} |
|
||||
|
|
||||
execute(workflow) { |
|
||||
this._strategy.execute(workflow); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
const ClientStrategy = { |
|
||||
execute: function(workflow) { |
|
||||
const block = workflow.next; |
|
||||
const EXECUTE_WORKFLOW_URL = `api/${workflow.instanceId}/workflows/${workflow._id}`; |
|
||||
|
|
||||
switch (block.type) { |
|
||||
case "CLIENT": |
|
||||
// fetch the workflow code from the server, then execute it here in the client
|
|
||||
// catch any errors
|
|
||||
// check against the conditions in the workflow
|
|
||||
// if everything is fine, recurse
|
|
||||
this.execute(workflow.next); |
|
||||
break; |
|
||||
case "SERVER": |
|
||||
// hit the server endpoint and wait for the response
|
|
||||
// catch any errors
|
|
||||
// check against the conditions in the workflow
|
|
||||
// if everything is fine, recurse
|
|
||||
await api.post() |
|
||||
break; |
|
||||
default: |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
} |
|
||||
@ -1,320 +1,55 @@ |
|||||
import { |
import { |
||||
generate_css, |
generate_css, |
||||
make_margin, |
|
||||
generate_screen_css, |
generate_screen_css, |
||||
|
generate_array_styles |
||||
} from "../src/builderStore/generate_css.js" |
} 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", () => { |
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", () => { |
test("Check how partially empty arrays are handled", () => { |
||||
expect( |
expect(["", "5", "", ""].map(generate_array_styles)).toEqual(["0px", "5px", "0px", "0px"]) |
||||
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", () => { |
test("Check how array styles are output", () => { |
||||
expect(generate_css({ position: { padding: ["1", "", "", "1"] } })).toEqual( |
expect(generate_css({ margin: ["0", "10", "0", "15"] })).toBe("margin: 0px 10px 0px 15px;") |
||||
{ |
|
||||
position: "padding: 1px 0px 0px 1px;", |
|
||||
layout: "", |
|
||||
} |
|
||||
) |
|
||||
}) |
}) |
||||
|
|
||||
test("it should generate a valid css rule: margin 1", () => { |
test("Check handling of an array with empty string values", () => { |
||||
expect( |
expect(generate_css({ padding: ["", "", "", ""] })).toBe("") |
||||
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", () => { |
test("Check handling of an empty array", () => { |
||||
expect(generate_css({ position: { margin: ["1", "", "", "1"] } })).toEqual({ |
expect(generate_css({ margin: [] })).toBe("") |
||||
position: "margin: 1px 0px 0px 1px;", |
|
||||
layout: "", |
|
||||
}) |
|
||||
}) |
}) |
||||
|
|
||||
test("it should generate a valid css rule: z-index 1", () => { |
test("Check handling of valid font property", () => { |
||||
expect(generate_css({ position: { zindex: "" } })).toEqual({ |
expect(generate_css({ "font-size": "10px" })).toBe("font-size: 10px;") |
||||
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) |
describe("generate_screen_css", () => { |
||||
|
const normalComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: { "font-size": "16px" }, hover: {}, active: {}, selected: {} } } |
||||
|
|
||||
|
test("Test generation of normal css styles", () => { |
||||
|
expect(generate_screen_css([normalComponent])).toBe(".header-123-456 {\nfont-size: 16px;\n}") |
||||
}) |
}) |
||||
|
|
||||
test("it should compile the css for a list of components", () => { |
const hoverComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {"font-size": "16px"}, active: {}, selected: {} } } |
||||
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 { |
|
||||
|
|
||||
} |
test("Test generation of hover css styles", () => { |
||||
.pos-5 { |
expect(generate_screen_css([hoverComponent])).toBe(".header-123-456:hover {\nfont-size: 16px;\n}") |
||||
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 { |
|
||||
|
|
||||
} |
const selectedComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: { "font-size": "16px" } } } |
||||
.pos-8 { |
|
||||
margin: 1px 1px 1px 1px; |
test("Test generation of selection css styles", () => { |
||||
} |
expect(generate_screen_css([selectedComponent])).toBe(".header-123-456::selection {\nfont-size: 16px;\n}") |
||||
.lay-8 { |
}) |
||||
|
|
||||
}` |
const emptyComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: {} } } |
||||
|
|
||||
expect(generate_screen_css(components)).toEqual(compiled) |
test.only("Testing handling of empty component styles", () => { |
||||
}) |
expect(generate_screen_css([emptyComponent])).toBe("") |
||||
}) |
}) |
||||
|
}) |
||||
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 241 KiB |
|
After Width: | Height: | Size: 381 KiB |
@ -0,0 +1,27 @@ |
|||||
|
const sgMail = require('@sendgrid/mail'); |
||||
|
|
||||
|
sgMail.setApiKey(process.env.SENDGRID_API_KEY); |
||||
|
|
||||
|
module.exports = async function sendEmail(args) { |
||||
|
|
||||
|
const msg = { |
||||
|
to: args.to, |
||||
|
from: args.from, |
||||
|
subject: args.subject, |
||||
|
text: args.text |
||||
|
}; |
||||
|
|
||||
|
try { |
||||
|
await sgMail.send(msg); |
||||
|
return { |
||||
|
success: true, |
||||
|
err |
||||
|
} |
||||
|
} catch (err) { |
||||
|
return { |
||||
|
success: false, |
||||
|
err |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||