Merge pull request #253 from Budibase/property-panel/master

Complete CSS State Styles and Property Panel Structure
This commit is contained in:
Conor_Mack 2020-05-26 14:32:49 +01:00 committed by GitHub
commit b4c1468889
36 changed files with 1379 additions and 1066 deletions

View File

@ -79,7 +79,8 @@
"rollup-plugin-svelte": "^5.0.3", "rollup-plugin-svelte": "^5.0.3",
"rollup-plugin-terser": "^4.0.4", "rollup-plugin-terser": "^4.0.4",
"rollup-plugin-url": "^2.2.2", "rollup-plugin-url": "^2.2.2",
"svelte": "^3.0.0" "svelte": "^3.0.0",
"svelte-color-picker": "^1.0.7"
}, },
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072" "gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
} }

View File

@ -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"
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_if_number = suffix => s => {
try {
if (isNaN(s) || isNaN(parseFloat(s))) return s
} catch (_) {
return s
}
return s + suffix
}
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]) =>
`${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)
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 generate_screen_css = component_array => {
let styles = "" let styles = ""
let emptyStyles = { layout: {}, position: {} } for (const { _styles, _id, _children, _component } of component_arr) {
let [componentName] = _component.match(/[a-z]*$/)
for (let i = 0; i < component_array.length; i += 1) { Object.keys(_styles).forEach(selector => {
const { _styles, _id, _children } = component_array[i] const cssString = generate_css(_styles[selector])
const { layout, position } = generate_css(_styles || emptyStyles) if (cssString) {
styles += apply_class(_id, componentName, cssString, selector)
styles += apply_class(_id, "pos", position) + "\n" }
styles += apply_class(_id, "lay", layout) + "\n" })
if (_children && _children.length) { if (_children && _children.length) {
styles += generate_screen_css(_children) + "\n" styles += generate_screen_css(_children) + "\n"
} }
} }
return styles.trim() return styles.trim()
} }
export const generate_css = style => {
let cssString = Object.entries(style).reduce((str, [key, value]) => {
//TODO Handle arrays and objects here also
if (typeof value === "string") {
if (value) {
return (str += `${key}: ${value};\n`)
}
} else if (Array.isArray(value)) {
if (value.length > 0 && !value.every(v => v === "")) {
return (str += `${key}: ${value
.map(generate_array_styles)
.join(" ")};\n`)
}
}
}, "")
return (cssString || "").trim()
}
export const generate_array_styles = item => {
let safeItem = item === "" ? 0 : item
let hasPx = new RegExp("px$")
if (!hasPx.test(safeItem)) {
return `${safeItem}px`
} else {
return safeItem
}
}
export const apply_class = (id, name = "element", styles, selector) => {
if (selector === "normal") {
return `.${name}-${id} {\n${styles}\n}`
} else {
let sel = selector === "selected" ? "::selection" : `:${selector}`
return `.${name}-${id}${sel} {\n${styles}\n}`
}
}

View File

@ -140,10 +140,11 @@ const _saveScreen = async (store, s, screen) => {
return s return s
} }
const _saveScreenApi = (screen, s) => const _saveScreenApi = (screen, s) => {
api api
.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen) .post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen)
.then(() => _savePage(s)) .then(() => _savePage(s))
}
const createScreen = store => (screenName, route, layoutComponentName) => { const createScreen = store => (screenName, route, layoutComponentName) => {
store.update(state => { store.update(state => {
@ -278,7 +279,6 @@ const removeStylesheet = store => stylesheet => {
const _savePage = async s => { const _savePage = async s => {
const page = s.pages[s.currentPageName] const page = s.pages[s.currentPageName]
await api.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}`, { await api.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}`, {
page: { componentLibraries: s.pages.componentLibraries, ...page }, page: { componentLibraries: s.pages.componentLibraries, ...page },
uiFunctions: s.currentPageFunctions, uiFunctions: s.currentPageFunctions,
@ -427,6 +427,7 @@ const setComponentStyle = store => (type, name, value) => {
state.currentComponentInfo._styles = {} state.currentComponentInfo._styles = {}
} }
state.currentComponentInfo._styles[type][name] = value state.currentComponentInfo._styles[type][name] = value
state.currentPreviewItem._css = generate_screen_css([ state.currentPreviewItem._css = generate_screen_css([
state.currentPreviewItem.props, state.currentPreviewItem.props,
]) ])

View File

@ -1,13 +1,78 @@
<script> <script>
import { onMount, beforeUpdate, afterUpdate } from "svelte" import { onMount } from "svelte"
import { HsvPicker } from "svelte-color-picker"
export let value = null // export let initialValue = "#ffffff"
export let onChanged = () => {} export let onChange = color => {}
export let swatches = [] export let open = false
let value = "#ffffff"
let picker let _justMounted = true //see onColorChange
let cp = null let pickerHeight = 275
let colorPreview
let pickerTopPosition = null
function rbgaToHexa({ r, g, b, a }) {
r = r.toString(16)
g = g.toString(16)
b = b.toString(16)
a = Math.round(a * 255).toString(16)
if (r.length == 1) r = "0" + r
if (g.length == 1) g = "0" + g
if (b.length == 1) b = "0" + b
if (a.length == 1) a = "0" + a
return "#" + r + g + b + a
}
function onColourChange(rgba) {
value = rbgaToHexa(rgba.detail)
//Hack: so that color change doesn't fire onMount
if (!_justMounted) {
// onChange(value)
}
_justMounted = false
}
function toggleColorpicker(isOpen) {
if (isOpen) {
const {
y: previewYPosition,
height: previewHeight,
} = colorPreview.getBoundingClientRect()
let wiggleRoom = window.innerHeight - previewYPosition
let displayTop = wiggleRoom < pickerHeight
if (displayTop) {
pickerTopPosition = previewYPosition - (pickerHeight - window.scrollY)
} else {
pickerTopPosition = null
}
}
open = isOpen
}
$: style = open ? "display: block;" : "display: none;"
$: pickerStyle = pickerTopPosition ? `top: ${pickerTopPosition}px;` : ""
</script>
<div
bind:this={colorPreview}
on:click={() => toggleColorpicker(true)}
class="color-preview"
style={`background: ${value}`} />
<div class="colorpicker" {style}>
<div class="overlay" on:click|self={() => toggleColorpicker(false)} />
<div class="cp" style={pickerStyle}>
<HsvPicker on:colorChange={onColourChange} startColor={value} />
</div>
</div>
<!--
OLD LOCAL STORAGE OPTIONS. INCLUDING FOR ADDING LATER
function getRecentColors() { function getRecentColors() {
let colorStore = localStorage.getItem("bb:recentColors") let colorStore = localStorage.getItem("bb:recentColors")
if (!!colorStore) { if (!!colorStore) {
@ -25,47 +90,27 @@
picker.addSwatch(color) picker.addSwatch(color)
localStorage.setItem("bb:recentColors", JSON.stringify(swatches)) localStorage.setItem("bb:recentColors", JSON.stringify(swatches))
} }
} -->
<style>
.overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
/* background: rgba(5, 5, 5, 0.25); */
} }
function createPicker() { .cp {
picker = Pickr.create({ position: absolute;
el: cp, right: 25px;
theme: "nano",
default: value || "#000000",
swatches,
closeWithKey: "Escape",
components: {
preview: true,
opacity: true,
hue: true,
interaction: {
hex: true,
rgba: true,
input: true,
save: true,
},
},
})
} }
.color-preview {
afterUpdate(() => { height: 30px;
picker.setColor(value) width: 100%;
}) margin: 5px;
cursor: pointer;
onMount(() => { border: 1px solid gainsboro;
getRecentColors() }
createPicker() </style>
picker.on("save", (colour, instance) => {
let color = colour.toHEXA().toString()
onChanged(color)
setRecentColor(color)
picker.hide()
})
})
</script>
<div bind:this={cp} class="color-picker" />

View File

@ -1,8 +1,11 @@
<script> <script>
export let value = "" export let value = ""
export let width = ""
let style = { width }
</script> </script>
<input type="text" on:change bind:value /> <input type="text" style={`width: ${width};`} on:change bind:value />
<style> <style>
input { input {

View File

@ -2,36 +2,50 @@
import { onMount } from "svelte" import { onMount } from "svelte"
export let meta = [] export let meta = []
export let size = "" export let label = ""
export let values = [] export let value = [0, 0, 0, 0]
export let type = "number" export let type = "number"
export let onStyleChanged = () => {} export let onChange = () => {}
let _values = values.map(v => v) function handleChange(val, idx) {
value.splice(idx, 1, val)
$: onStyleChanged(_values) value = value
onChange(value)
}
</script> </script>
<div class="inputs {size}"> <div class="input-container">
{#each meta as { placeholder }, i} <div class="label">{label}</div>
<input <div class="inputs">
{type} {#each meta as { placeholder }, i}
{placeholder} <input
value={values[i]} {type}
on:input={e => (_values[i] = e.target.value)} /> placeholder={placeholder || ''}
{/each} value={!value || value[i] === 0 ? '' : value[i]}
on:change={e => handleChange(e.target.value || 0, i)} />
{/each}
</div>
</div> </div>
<style> <style>
.inputs { .input-container {
display: flex; display: flex;
justify-content: space-between; }
.label {
flex: 0;
}
.inputs {
flex: 1;
} }
input { input {
width: 83px; width: 40px;
height: 32px;
font-size: 13px; font-size: 13px;
font-weight: 700; font-weight: 700;
margin: 0px 5px;
color: #163057; color: #163057;
opacity: 0.7; opacity: 0.7;
padding: 5px 10px; padding: 5px 10px;
@ -49,17 +63,11 @@
margin: 0; margin: 0;
} }
.small > input { input[type="number"] {
width: 38px; -moz-appearance: textfield;
height: 38px;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
padding: 0;
} }
.small > input::placeholder { input::placeholder {
text-align: center; text-align: center;
} }
</style> </style>

View File

@ -16,6 +16,11 @@
return props return props
} }
const getComponentTypeName = component => {
let [componentName] = component._component.match(/[a-z]*$/)
return componentName || "element"
}
$: iframe && $: iframe &&
console.log( console.log(
iframe.contentDocument.head.insertAdjacentHTML( iframe.contentDocument.head.insertAdjacentHTML(
@ -60,7 +65,7 @@
_children: [ _children: [
{ {
_component: "@budibase/standard-components/container", _component: "@budibase/standard-components/container",
_styles: { position: {}, layout: {} }, _styles: { normal: {}, hover: {}, active: {}, selected: {} },
_id: "__screenslot__text", _id: "__screenslot__text",
_code: "", _code: "",
className: "", className: "",
@ -69,7 +74,12 @@
_children: [ _children: [
{ {
_component: "@budibase/standard-components/text", _component: "@budibase/standard-components/text",
_styles: { position: {}, layout: {} }, _styles: {
normal: {},
hover: {},
active: {},
selected: {},
},
_id: "__screenslot__text_2", _id: "__screenslot__text_2",
_code: "", _code: "",
text: "content", text: "content",
@ -88,6 +98,8 @@
appRootPath: "", appRootPath: "",
} }
$: selectedComponentType = getComponentTypeName($store.currentComponentInfo)
$: selectedComponentId = $store.currentComponentInfo $: selectedComponentId = $store.currentComponentInfo
? $store.currentComponentInfo._id ? $store.currentComponentInfo._id
: "" : ""
@ -102,6 +114,7 @@
srcdoc={iframeTemplate({ srcdoc={iframeTemplate({
styles, styles,
stylesheetLinks, stylesheetLinks,
selectedComponentType,
selectedComponentId, selectedComponentId,
frontendDefinition: JSON.stringify(frontendDefinition), frontendDefinition: JSON.stringify(frontendDefinition),
currentPageFunctions: $store.currentPageFunctions, currentPageFunctions: $store.currentPageFunctions,

View File

@ -1,6 +1,7 @@
export default ({ export default ({
styles, styles,
stylesheetLinks, stylesheetLinks,
selectedComponentType,
selectedComponentId, selectedComponentId,
frontendDefinition, frontendDefinition,
currentPageFunctions, currentPageFunctions,
@ -11,7 +12,7 @@ export default ({
<style> <style>
${styles || ""} ${styles || ""}
.pos-${selectedComponentId} { .${selectedComponentType}-${selectedComponentId} {
border: 2px solid #0055ff; border: 2px solid #0055ff;
} }

View File

@ -0,0 +1,43 @@
<script>
export let categories = []
export let selectedCategory = {}
export let onClick = category => {}
</script>
<ul class="tabs">
{#each categories as category}
<li
on:click={() => onClick(category)}
class:active={selectedCategory === category}>
{category.name}
</li>
{/each}
</ul>
<style>
.tabs {
display: flex;
justify-content: center;
list-style: none;
margin: 0 auto;
padding: 0 30px;
border-bottom: 1px solid #d8d8d8;
font-size: 14px;
font-weight: 500;
letter-spacing: 0.14px;
}
li {
color: #808192;
margin: 0 5px;
padding: 0 8px;
cursor: pointer;
}
.active {
border-bottom: solid 3px #0055ff;
color: #393c44;
}
</style>

View File

@ -1,4 +1,5 @@
<script> <script>
import { setContext, onMount } from "svelte"
import PropsView from "./PropsView.svelte" import PropsView from "./PropsView.svelte"
import { store } from "builderStore" import { store } from "builderStore"
import IconButton from "components/common/IconButton.svelte" import IconButton from "components/common/IconButton.svelte"
@ -13,23 +14,63 @@
import LayoutEditor from "./LayoutEditor.svelte" import LayoutEditor from "./LayoutEditor.svelte"
import EventsEditor from "./EventsEditor" import EventsEditor from "./EventsEditor"
let current_view = "props" import panelStructure from "./temporaryPanelStructure.js"
let codeEditor import CategoryTab from "./CategoryTab.svelte"
import DesignView from "./DesignView.svelte"
import SettingsView from "./SettingsView.svelte"
let current_view = "design"
let codeEditor
let flattenedPanel = flattenComponents(panelStructure.categories)
let categories = [
{ value: "design", name: "Design" },
{ value: "settings", name: "Settings" },
{ value: "actions", name: "Actions" },
]
let selectedCategory = categories[0]
$: component = $store.currentComponentInfo
$: originalName = component.name
$: name =
$store.currentView === "detail"
? $store.currentPreviewItem.name
: component._component
$: description = component.description
$: components = $store.components $: components = $store.components
$: componentInstance = $store.currentComponentInfo
$: componentDefinition = $store.components[componentInstance._component]
$: componentPropDefinition =
flattenedPanel.find(
//use for getting controls for each component property
c => c._component === componentInstance._component
) || {}
$: panelDefinition = componentPropDefinition.properties
? componentPropDefinition.properties[selectedCategory.value]
: {}
// SCREEN PROPS =============================================
$: screen_props = $: screen_props =
$store.currentFrontEndType === "page" $store.currentFrontEndType === "page"
? getProps($store.currentPreviewItem, ["name", "favicon"]) ? getProps($store.currentPreviewItem, ["name", "favicon"])
: getProps($store.currentPreviewItem, ["name", "description", "route"]) : getProps($store.currentPreviewItem, ["name", "description", "route"])
const onStyleChanged = store.setComponentStyle const onStyleChanged = store.setComponentStyle
const onPropChanged = store.setComponentProp
function walkProps(component, action) {
action(component)
if (component.children) {
for (let child of component.children) {
walkProps(child, action)
}
}
}
function flattenComponents(props) {
const components = []
props.forEach(comp =>
walkProps(comp, c => {
if ("_component" in c) {
components.push(c)
}
})
)
return components
}
function getProps(obj, keys) { function getProps(obj, keys) {
return keys.map((key, i) => [key, obj[key], obj.props._id + i]) return keys.map((key, i) => [key, obj[key], obj.props._id + i])
@ -37,116 +78,33 @@
</script> </script>
<div class="root"> <div class="root">
<ul>
<li> <CategoryTab
<button onClick={category => (selectedCategory = category)}
class:selected={current_view === 'props'} {categories}
on:click={() => (current_view = 'props')}> {selectedCategory} />
<PaintIcon />
</button>
</li>
<li>
<button
class:selected={current_view === 'layout'}
on:click={() => (current_view = 'layout')}>
<LayoutIcon />
</button>
</li>
{#if !component._component.startsWith('##')}
<li>
<button
class:selected={current_view === 'code'}
on:click={() => codeEditor && codeEditor.show()}>
{#if component._code && component._code.trim().length > 0}
<div class="button-indicator">
<CircleIndicator />
</div>
{/if}
<TerminalIcon />
</button>
</li>
<li>
<button
class:selected={current_view === 'events'}
on:click={() => (current_view = 'events')}>
<EventsIcon />
</button>
</li>
{/if}
</ul>
<div class="component-props-container"> <div class="component-props-container">
{#if selectedCategory.value === 'design'}
{#if current_view === 'props'} <DesignView {panelDefinition} {componentInstance} {onStyleChanged} />
{#if $store.currentView === 'detail'} {:else if selectedCategory.value === 'settings'}
{#each screen_props as [k, v, id] (id)} <SettingsView
<div class="detail-prop" for={k}> {componentInstance}
<label>{k}:</label> {componentDefinition}
<input {panelDefinition}
id={k} onChange={onPropChanged} />
value={v}
on:input={({ target }) => store.setMetadataProp(k, target.value)} />
</div>
{/each}
<PropsView {component} {components} />
{:else}
<PropsView {component} {components} />
{/if}
{:else if current_view === 'layout'}
<LayoutEditor {onStyleChanged} {component} />
{:else if current_view === 'events'}
<EventsEditor {component} {components} />
{/if} {/if}
<CodeEditor
bind:this={codeEditor}
code={component._code}
onCodeChanged={store.setComponentCode} />
</div> </div>
</div> </div>
<style> <style>
.detail-prop {
height: 40px;
margin-bottom: 15px;
display: grid;
grid-template-rows: 1fr;
grid-template-columns: 70px 1fr;
grid-gap: 10px;
}
.detail-prop label {
word-wrap: break-word;
font-size: 13px;
font-weight: 700;
color: #163057;
opacity: 0.6;
padding-top: 13px;
margin-bottom: 0;
}
input {
height: 30px;
padding-left: 8px;
padding-right: 8px;
border: 1px solid #dbdbdb;
border-radius: 2px;
opacity: 0.5;
}
input:focus {
outline: 0;
background-color: #fff;
color: #666;
border-color: #1e87f0;
}
.root { .root {
height: 100%; height: 100%;
padding: 20px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow-x: hidden;
} }
.title > div:nth-child(1) { .title > div:nth-child(1) {
@ -162,52 +120,4 @@
margin-top: 10px; margin-top: 10px;
flex: 1 1 auto; flex: 1 1 auto;
} }
ul {
list-style: none;
display: flex;
justify-content: space-between;
padding: 0;
}
li {
background: none;
border-radius: 3px;
width: 48px;
height: 48px;
}
li button {
width: 48px;
height: 48px;
background: none;
border: none;
border-radius: 3px;
padding: 7px;
outline: none;
cursor: pointer;
position: relative;
}
li:nth-last-child(1) {
margin-right: 0px;
background: none;
border-radius: 3px;
width: 48px;
height: 48px;
}
.selected {
color: var(--button-text);
background: #f9f9f9 !important;
width: 48px;
height: 48px;
}
.button-indicator {
position: absolute;
top: 8px;
right: 10px;
color: var(--button-text);
}
</style> </style>

View File

@ -2,6 +2,7 @@
import { splitName } from "./pagesParsing/splitRootComponentName.js" import { splitName } from "./pagesParsing/splitRootComponentName.js"
import components from "./temporaryPanelStructure.js" import components from "./temporaryPanelStructure.js"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import CategoryTab from "./CategoryTab.svelte"
import { import {
find, find,
sortBy, sortBy,
@ -36,15 +37,12 @@
</script> </script>
<div class="root"> <div class="root">
<ul class="tabs">
{#each categories as category} <CategoryTab
<li onClick={category => (selectedCategory = category)}
on:click={() => (selectedCategory = category)} {selectedCategory}
class:active={selectedCategory === category}> {categories} />
{category.name}
</li>
{/each}
</ul>
<div class="panel"> <div class="panel">
<Tab <Tab
list={selectedCategory} list={selectedCategory}

View File

@ -30,7 +30,7 @@
<button <button
class:selected={selected === PROPERTIES_TAB} class:selected={selected === PROPERTIES_TAB}
on:click={() => selectTab(PROPERTIES_TAB)}> on:click={() => selectTab(PROPERTIES_TAB)}>
Properties Edit
</button> </button>
</div> </div>
@ -50,6 +50,14 @@
</div> </div>
<style> <style>
.root {
height: 100%;
display: flex;
flex-direction: column;
padding: 20px 0;
border-left: solid 1px #e8e8ef;
}
.switcher { .switcher {
display: flex; display: flex;
margin: 0px 20px 20px 20px; margin: 0px 20px 20px 20px;

View File

@ -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>

View File

@ -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: 5px;
text-align: center;
background: #ffffff;
color: #808192;
border-radius: 4px;
font-family: Roboto;
font-size: 11px;
font-weight: 500;
letter-spacing: 0.11px;
transition: background 0.5s, color 0.5s ease;
}
.selected {
background: #808192;
color: #ffffff;
}
</style>

View File

@ -0,0 +1,54 @@
<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;
flex-flow: row nowrap;
}
.button-container {
flex: 1;
margin: 5px;
}
</style>

View File

@ -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>

View File

@ -0,0 +1,63 @@
<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 nowrap;
margin: 8px 0px;
}
.label {
flex: 0 0 50px;
padding: 0px 5px;
font-size: 12px;
font-weight: 500;
letter-spacing: 0.12px;
text-align: left;
}
.control {
flex: 1;
padding-left: 5px;
}
</style>

View File

@ -0,0 +1,81 @@
<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: #fbfbfb;
margin: 5px;
padding: 5px;
}
.property-group-name {
cursor: pointer;
flex: 0 0 20px;
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: #393c44;
}
.icon {
flex: 0 0 20px;
text-align: center;
}
.property-panel {
height: 0px;
overflow: hidden;
}
.show {
overflow: auto;
height: auto;
}
</style>

View File

@ -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>

View File

@ -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>

View File

@ -24,7 +24,7 @@ export const createProps = (componentDefinition, derivedFromProps) => {
const props = { const props = {
_id: uuid(), _id: uuid(),
_component: componentDefinition._component, _component: componentDefinition._component,
_styles: { position: {}, layout: {} }, _styles: { normal: {}, hover: {}, active: {}, selected: {} },
_code: "", _code: "",
} }
@ -71,7 +71,7 @@ export const makePropsSafe = (componentDefinition, props) => {
} }
if (!props._styles) { if (!props._styles) {
props._styles = { layout: {}, position: {} } props._styles = { normal: {}, hover: {}, active: {}, selected: {} }
} }
return props return props

View File

@ -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
}

View File

@ -1,3 +1,9 @@
import Input from "../common/Input.svelte"
import OptionSelect from "./OptionSelect.svelte"
import Checkbox from "../common/Checkbox.svelte"
import { all } from "./propertyCategories.js"
export default { export default {
categories: [ categories: [
{ {
@ -20,6 +26,31 @@ export default {
icon: "ri-layout-row-fill", icon: "ri-layout-row-fill",
commonProps: {}, commonProps: {},
children: [], children: [],
properties: {
design: { ...all },
settings: [
{
key: "type",
label: "Type",
control: OptionSelect,
options: [
{ label: "article" },
{ label: "aside" },
{ label: "details" },
{ label: "div" },
{ label: "figure" },
{ label: "figcaption" },
{ label: "footer" },
{ label: "header" },
{ label: "main" },
{ label: "mark" },
{ label: "nav" },
{ label: "paragraph" },
{ label: "summary" },
],
},
],
},
}, },
{ {
name: "Text", name: "Text",
@ -32,13 +63,21 @@ export default {
name: "Headline", name: "Headline",
description: "A component for displaying heading text", description: "A component for displaying heading text",
icon: "ri-heading", icon: "ri-heading",
props: { properties: {
type: { design: { ...all },
type: "options", settings: [
options: ["h1", "h2", "h3", "h4", "h5", "h6"], {
default: "h1", key: "text",
}, label: "Text",
text: "string", control: Input,
},
{
key: "type",
label: "Type",
control: OptionSelect,
options: ["h1", "h2", "h3", "h4", "h5", "h6"],
},
],
}, },
}, },
{ {
@ -46,7 +85,34 @@ export default {
name: "Paragraph", name: "Paragraph",
description: "A component for displaying paragraph text.", description: "A component for displaying paragraph text.",
icon: "ri-paragraph", icon: "ri-paragraph",
props: {}, properties: {
design: { ...all },
settings: [
{
label: "Text",
key: "text",
control: Input,
},
{
label: "Type",
key: "type",
control: OptionSelect,
options: [
"none",
"bold",
"strong",
"italic",
"emphasis",
"mark",
"small",
"del",
"ins",
"sub",
"sup",
],
},
],
},
}, },
], ],
}, },
@ -62,21 +128,38 @@ export default {
description: description:
"A textfield component that allows the user to input text.", "A textfield component that allows the user to input text.",
icon: "ri-edit-box-line", icon: "ri-edit-box-line",
props: {}, properties: {
design: { ...all },
settings: [
{ label: "Label", key: "label", control: Input },
{
label: "Type",
key: "type",
control: OptionSelect,
options: ["text", "password"],
},
],
},
}, },
{ {
_component: "@budibase/standard-components/checkbox", _component: "@budibase/standard-components/checkbox",
name: "Checkbox", name: "Checkbox",
description: "A selectable checkbox component", description: "A selectable checkbox component",
icon: "ri-checkbox-line", icon: "ri-checkbox-line",
props: {}, properties: {
design: { ...all },
settings: [{ label: "Label", key: "label", control: Input }],
},
}, },
{ {
_component: "@budibase/standard-components/radiobutton", _component: "@budibase/standard-components/radiobutton",
name: "Radiobutton", name: "Radiobutton",
description: "A selectable radiobutton component", description: "A selectable radiobutton component",
icon: "ri-radio-button-line", icon: "ri-radio-button-line",
props: {}, properties: {
design: { ...all },
settings: [{ label: "Label", key: "label", control: Input }],
},
}, },
{ {
_component: "@budibase/standard-components/select", _component: "@budibase/standard-components/select",
@ -84,7 +167,10 @@ export default {
description: description:
"A select component for choosing from different options", "A select component for choosing from different options",
icon: "ri-file-list-line", icon: "ri-file-list-line",
props: {}, properties: {
design: { ...all },
settings: [],
},
}, },
], ],
}, },
@ -93,24 +179,51 @@ export default {
name: "Button", name: "Button",
description: "A basic html button that is ready for styling", description: "A basic html button that is ready for styling",
icon: "ri-radio-button-fill", icon: "ri-radio-button-fill",
commonProps: {},
children: [], children: [],
properties: {
design: {
...all,
},
settings: [
{ label: "Text", key: "text", control: Input },
{
label: "Disabled",
key: "disabled",
valueKey: "checked",
control: Checkbox,
},
],
},
}, },
{ {
_component: "@budibase/standard-components/icon", _component: "@budibase/standard-components/icon",
name: "Icon", name: "Icon",
description: "A basic component for displaying icons", description: "A basic component for displaying icons",
icon: "ri-sun-fill", icon: "ri-sun-fill",
commonProps: {},
children: [], children: [],
properties: {
design: { ...all },
},
}, },
{ {
_component: "@budibase/standard-components/link", _component: "@budibase/standard-components/link",
name: "Link", name: "Link",
description: "A basic link component for internal and external links", description: "A basic link component for internal and external links",
icon: "ri-link", icon: "ri-link",
commonProps: {},
children: [], children: [],
properties: {
design: { ...all },
settings: [
{ label: "Text", key: "text", control: Input },
{ label: "Url", key: "url", control: Input },
{
label: "Open New Tab",
key: "openInNewTab",
valueKey: "checked",
control: Checkbox,
},
],
},
}, },
], ],
}, },
@ -124,17 +237,16 @@ export default {
description: description:
"A basic card component that can contain content and actions.", "A basic card component that can contain content and actions.",
icon: "ri-layout-bottom-line", icon: "ri-layout-bottom-line",
commonProps: {},
children: [], children: [],
properties: { design: { ...all } },
}, },
{ {
_component: "@budibase/standard-components/login",
name: "Login", name: "Login",
description: description:
"A component that automatically generates a login screen for your app.", "A component that automatically generates a login screen for your app.",
icon: "ri-login-box-fill", icon: "ri-login-box-fill",
commonProps: {},
children: [], children: [],
properties: { design: { ...all } },
}, },
{ {
name: "Navigation Bar", name: "Navigation Bar",
@ -142,8 +254,8 @@ export default {
description: description:
"A component for handling the navigation within your app.", "A component for handling the navigation within your app.",
icon: "ri-navigation-fill", icon: "ri-navigation-fill",
commonProps: {},
children: [], children: [],
properties: { design: { ...all } },
}, },
], ],
}, },
@ -153,19 +265,17 @@ export default {
children: [ children: [
{ {
name: "Table", name: "Table",
_component: "@budibase/materialdesign-components/Datatable",
description: "A component that generates a table from your data.", description: "A component that generates a table from your data.",
icon: "ri-archive-drawer-fill", icon: "ri-archive-drawer-fill",
commonProps: {}, properties: { design: { ...all } },
children: [], children: [],
}, },
{ {
_component: "@budibase/materialdesign-components/Form",
name: "Form", name: "Form",
description: "A component that generates a form from your data.", description: "A component that generates a form from your data.",
icon: "ri-file-edit-fill", icon: "ri-file-edit-fill",
commonProps: {}, properties: { design: { ...all } },
component: "@budibase/materialdesign-components/Form", _component: "@budibase/materialdesign-components/Form",
template: { template: {
component: "@budibase/materialdesign-components/Form", component: "@budibase/materialdesign-components/Form",
description: "Form for saving a record", description: "Form for saving a record",
@ -178,7 +288,7 @@ export default {
name: "DataTable", name: "DataTable",
description: "A table for displaying data from the backend.", description: "A table for displaying data from the backend.",
icon: "ri-archive-drawer-fill", icon: "ri-archive-drawer-fill",
commonProps: {}, properties: { design: { ...all } },
children: [], children: [],
}, },
{ {
@ -186,7 +296,7 @@ export default {
name: "DataForm", name: "DataForm",
description: "Form stuff", description: "Form stuff",
icon: "ri-file-edit-fill", icon: "ri-file-edit-fill",
commonProps: {}, properties: { design: { ...all } },
children: [], children: [],
}, },
{ {
@ -194,7 +304,7 @@ export default {
_component: "@budibase/standard-components/datachart", _component: "@budibase/standard-components/datachart",
description: "Shiny chart", description: "Shiny chart",
icon: "ri-bar-chart-line", icon: "ri-bar-chart-line",
commonProps: {}, properties: { design: { ...all } },
children: [], children: [],
}, },
{ {
@ -202,7 +312,7 @@ export default {
_component: "@budibase/standard-components/datalist", _component: "@budibase/standard-components/datalist",
description: "Shiny list", description: "Shiny list",
icon: "ri-file-list-line", icon: "ri-file-list-line",
commonProps: {}, properties: { design: { ...all } },
children: [], children: [],
}, },
{ {
@ -210,7 +320,7 @@ export default {
_component: "@budibase/standard-components/datamap", _component: "@budibase/standard-components/datamap",
description: "Shiny map", description: "Shiny map",
icon: "ri-map-pin-line", icon: "ri-map-pin-line",
commonProps: {}, properties: { design: { ...all } },
children: [], children: [],
}, },
], ],

View File

@ -38,12 +38,12 @@
height: 100%; height: 100%;
} }
@media only screen and (min-width: 1800px) { @media only screen and (min-width: 1800px) {
.nav { .nav {
overflow: auto; overflow: auto;
flex: 0 1 auto; flex: 0 1 auto;
width: 300px; width: 300px;
height: 100%; height: 100%;
}
} }
}
</style> </style>

View File

@ -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({ test("Check how partially empty arrays are handled", () => {
layout: "", expect(["", "5", "", ""].map(generate_array_styles)).toEqual(["0px", "5px", "0px", "0px"])
position: "",
})
}) })
test("it should generate a valid css rule: grid-gap", () => { test("Check how array styles are output", () => {
expect(generate_css({ layout: { gap: "10" } })).toEqual({ expect(generate_css({ margin: ["0", "10", "0", "15"] })).toBe("margin: 0px 10px 0px 15px;")
layout: "grid-gap: 10px;\ndisplay: grid;",
position: "",
})
}) })
test("it should generate a valid css rule: column 1", () => { test("Check handling of an array with empty string values", () => {
expect(generate_css({ position: { column: ["", ""] } })).toEqual({ expect(generate_css({ padding: ["", "", "", ""] })).toBe("")
layout: "",
position: "",
})
}) })
test("it should generate a valid css rule: column 2", () => { test("Check handling of an empty array", () => {
expect(generate_css({ position: { column: ["1", ""] } })).toEqual({ expect(generate_css({ margin: [] })).toBe("")
position: "grid-column-start: 1;",
layout: "",
})
}) })
test("it should generate a valid css rule: column 3", () => { test("Check handling of valid font property", () => {
expect(generate_css({ position: { column: ["", "1"] } })).toEqual({ expect(generate_css({ "font-size": "10px" })).toBe("font-size: 10px;")
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", () => { describe("generate_screen_css", () => {
test("it should compile the css for a list of components", () => { const normalComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: { "font-size": "16px" }, hover: {}, active: {}, selected: {} } }
const components = [
{ test("Test generation of normal css styles", () => {
_styles: { expect(generate_screen_css([normalComponent])).toBe(".header-123-456 {\nfont-size: 16px;\n}")
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 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 { test("Test generation of hover css styles", () => {
margin: 1px 1px 1px 1px; expect(generate_screen_css([hoverComponent])).toBe(".header-123-456:hover {\nfont-size: 16px;\n}")
}
.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)
}) })
})
const selectedComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: { "font-size": "16px" } } }
test("Test generation of selection css styles", () => {
expect(generate_screen_css([selectedComponent])).toBe(".header-123-456::selection {\nfont-size: 16px;\n}")
})
const emptyComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: {} } }
test.only("Testing handling of empty component styles", () => {
expect(generate_screen_css([emptyComponent])).toBe("")
})
})

View File

@ -9,8 +9,10 @@
"_id": 0, "_id": 0,
"type": "div", "type": "div",
"_styles": { "_styles": {
"layout": {}, "normal": {},
"position": {} "hover": {},
"active": {},
"selected": {}
}, },
"_code": "" "_code": ""
}, },

View File

@ -9,8 +9,10 @@
"_id": 1, "_id": 1,
"type": "div", "type": "div",
"_styles": { "_styles": {
"layout": {}, "normal": {},
"position": {} "hover": {},
"active": {},
"selected": {}
}, },
"_code": "" "_code": ""
}, },

View File

@ -9,8 +9,10 @@
"_id": 0, "_id": 0,
"type": "div", "type": "div",
"_styles": { "_styles": {
"layout": {}, "normal": {},
"position": {} "hover": {},
"active": {},
"selected": {}
}, },
"_code": "" "_code": ""
}, },

View File

@ -31,7 +31,7 @@ export const attachChildren = initialiseOpts => (htmlElement, options) => {
} }
} }
htmlElement.classList.add(`lay-${treeNode.props._id}`) // htmlElement.classList.add(`lay-${treeNode.props._id}`)
const childNodes = [] const childNodes = []
for (let childProps of treeNode.props._children) { for (let childProps of treeNode.props._children) {

View File

@ -35,8 +35,9 @@ export const prepareRenderComponent = ({
thisNode.rootElement = thisNode.rootElement =
htmlElement.children[htmlElement.children.length - 1] htmlElement.children[htmlElement.children.length - 1]
let [componentName] = props._component.match(/[a-z]*$/)
if (props._id && thisNode.rootElement) { if (props._id && thisNode.rootElement) {
thisNode.rootElement.classList.add(`pos-${props._id}`) thisNode.rootElement.classList.add(`${componentName}-${props._id}`)
} }
} }
} }

View File

@ -12,10 +12,10 @@
"props": { "props": {
"logoUrl": "string", "logoUrl": "string",
"title": "string", "title": "string",
"backgroundColor": "colour", "backgroundColor": "string",
"color": "colour", "color": "string",
"borderWidth": "string", "borderWidth": "string",
"borderColor": "colour", "borderColor": "string",
"borderStyle": "string" "borderStyle": "string"
} }
}, },
@ -23,35 +23,10 @@
"name": "Button", "name": "Button",
"description": "an html <button />", "description": "an html <button />",
"props": { "props": {
"contentText": { "text": "string",
"type": "string",
"default": "Button"
},
"className": "string", "className": "string",
"disabled": "bool", "disabled": "bool",
"onClick": "event", "onClick": "event"
"background": "colour",
"color": "colour",
"border": "string",
"padding": "string",
"hoverColor": "string",
"hoverBackground": "string",
"hoverBorder": "string",
"fontFamily": {
"type": "options",
"default": "initial",
"styleBindingProperty": "font-family",
"options": [
"initial",
"Times New Roman",
"Georgia",
"Arial",
"Arial Black",
"Comic Sans MS",
"Impact",
"Lucida Sans Unicode"
]
}
}, },
"tags": [ "tags": [
"layout" "layout"
@ -167,58 +142,7 @@
"children": false, "children": false,
"props": { "props": {
"text": "string", "text": "string",
"color": "colour", "type": {"type": "string", "default": "none"}
"fontFamily": {
"type": "options",
"default": "initial",
"styleBindingProperty": "font-family",
"options": [
"initial",
"Times New Roman",
"Georgia",
"Arial",
"Arial Black",
"Comic Sans MS",
"Impact",
"Lucida Sans Unicode"
]
},
"fontSize": "string",
"textAlign": {
"type": "options",
"default": "inline",
"options": [
"left",
"center",
"right"
]
},
"verticalAlign": {
"type": "options",
"default": "inline",
"options": [
"top",
"middle",
"bottom"
]
},
"formattingTag": {
"type": "options",
"default": "none",
"options": [
"none",
"<b> - bold",
"<strong> - important",
"<i> - italic",
"<em> - emphasized",
"<mark> - marked text",
"<small> - small",
"<del> - deleted",
"<ins> - inserted",
"<sub> - subscript",
"<sup> - superscript"
]
}
}, },
"tags": [ "tags": [
"div", "div",
@ -230,6 +154,7 @@
"description": "A component that allows the user to input text.", "description": "A component that allows the user to input text.",
"props": { "props": {
"label": "string", "label": "string",
"type": "string",
"value": "string", "value": "string",
"onchange": "event" "onchange": "event"
} }
@ -259,7 +184,7 @@
"props": { "props": {
"icon": "string", "icon": "string",
"fontSize": "string", "fontSize": "string",
"color": "colour" "color": "string"
} }
}, },
"datatable": { "datatable": {
@ -328,8 +253,8 @@
"url": "string", "url": "string",
"openInNewTab": "bool", "openInNewTab": "bool",
"text": "string", "text": "string",
"color": "colour", "color": "string",
"hoverColor": "colour", "hoverColor": "string",
"underline": "bool", "underline": "bool",
"fontSize": "string", "fontSize": "string",
"fontFamily": { "fontFamily": {
@ -383,27 +308,8 @@
"summary" "summary"
], ],
"default": "div" "default": "div"
},
"backgroundColor": "string",
"color": "string",
"borderWidth": "string",
"borderColor": "string",
"borderStyle": {
"type": "options",
"options": [
"none",
"solid",
"dotted",
"dashed",
"double",
"groove",
"ridge",
"inset",
"outset"
],
"default": "none"
} }
}, },
"container": true, "container": true,
"tags": [ "tags": [
"div", "div",
@ -416,7 +322,6 @@
"description": "An HTML H1 - H6 tag", "description": "An HTML H1 - H6 tag",
"props": { "props": {
"className": "string", "className": "string",
"color":"colour",
"text": "string", "text": "string",
"type": { "type": {
"type": "options", "type": "options",
@ -429,21 +334,6 @@
"h5", "h5",
"h6" "h6"
] ]
},
"fontFamily": {
"type": "options",
"default": "initial",
"styleBindingProperty": "font-family",
"options": [
"initial",
"Times New Roman",
"Georgia",
"Arial",
"Arial Black",
"Comic Sans MS",
"Impact",
"Lucida Sans Unicode"
]
} }
}, },
"tags": [] "tags": []

View File

@ -1,58 +1,15 @@
<script> <script>
import { cssVars, createClasses } from "./cssVars"
import { buildStyle } from "./buildStyle"
export let className = "default" export let className = "default"
export let disabled = false export let disabled = false
export let contentText export let text
export let onClick export let onClick
export let background
export let color
export let border
export let padding
export let hoverColor
export let hoverBackground
export let hoverBorder
export let fontFamily
export let _bb export let _bb
let theButton let theButton
let cssVariables
let buttonStyles
let customHoverColorClass
let customHoverBorderClass
let customHoverBackClass
let customClasses = ""
$: if (_bb.props._children && _bb.props._children.length > 0) $: if (_bb.props._children && _bb.props._children.length > 0)
theButton && _bb.attachChildren(theButton) theButton && _bb.attachChildren(theButton)
$: {
cssVariables = {
hoverColor,
hoverBorder,
hoverBackground,
background,
color,
border,
}
buttonStyles = buildStyle({
padding,
"font-family": fontFamily,
})
customClasses = createClasses({
hoverColor,
hoverBorder,
hoverBackground,
background,
border,
color,
})
}
const clickHandler = () => { const clickHandler = () => {
_bb.call(onClick) _bb.call(onClick)
} }
@ -60,15 +17,10 @@
<button <button
bind:this={theButton} bind:this={theButton}
use:cssVars={cssVariables} class={className}
class="{className}
{customClasses}"
disabled={disabled || false} disabled={disabled || false}
on:click={clickHandler} on:click={clickHandler}>
style={buttonStyles}> {#if !_bb.props._children || _bb.props._children.length === 0}{text}{/if}
{#if !_bb.props._children || _bb.props._children.length === 0}
{contentText}
{/if}
</button> </button>
<style> <style>

View File

@ -4,26 +4,12 @@
export let className = "" export let className = ""
export let onLoad export let onLoad
export let type = "div" export let type = "div"
export let backgroundColor
export let color
export let borderWidth
export let borderColor
export let borderStyle
export let _bb export let _bb
let containerElement let containerElement
let hasLoaded let hasLoaded
let currentChildren let currentChildren
$: cssVariables = {
backgroundColor,
color,
borderWidth,
borderColor,
borderStyle,
}
$: classes = `${createClasses(cssVariables)} ${className}`
$: { $: {
if (containerElement) { if (containerElement) {
_bb.attachChildren(containerElement) _bb.attachChildren(containerElement)
@ -36,87 +22,29 @@
</script> </script>
{#if type === 'div'} {#if type === 'div'}
<div <div bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'header'} {:else if type === 'header'}
<header <header bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'main'} {:else if type === 'main'}
<main <main bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'footer'} {:else if type === 'footer'}
<footer <footer bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'aside'} {:else if type === 'aside'}
<aside <aside bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'summary'} {:else if type === 'summary'}
<summary <summary bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'details'} {:else if type === 'details'}
<details <details bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'article'} {:else if type === 'article'}
<article <article bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'nav'} {:else if type === 'nav'}
<nav <nav bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'mark'} {:else if type === 'mark'}
<mark <mark bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'figure'} {:else if type === 'figure'}
<figure <figure bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'figcaption'} {:else if type === 'figcaption'}
<figcaption <figcaption bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'paragraph'} {:else if type === 'paragraph'}
<p class={classes} bind:this={containerElement} use:cssVars={cssVariables} /> <p bind:this={containerElement} />
{/if} {/if}
<style>
.backgroundColor {
background-color: var(--backgroundColor);
}
.color {
color: var(--color);
}
.borderColor {
border-color: var(--borderColor);
}
.borderWidth {
border-width: var(--borderWidth);
}
.borderStyle {
border-style: var(--borderStyle);
}
</style>

View File

@ -2,28 +2,25 @@
import { buildStyle } from "./buildStyle.js" import { buildStyle } from "./buildStyle.js"
export let className = "" export let className = ""
export let type export let type
export let _bb
export let text = "" export let text = ""
export let fontFamily = ""
export let color = "" export let _bb
let containerElement let containerElement
$: containerElement && !text && _bb.attachChildren(containerElement) $: containerElement && !text && _bb.attachChildren(containerElement)
$: style = buildStyle({ "font-family": fontFamily, color })
// $: console.log("HEADING", color)
</script> </script>
{#if type === 'h1'} {#if type === 'h1'}
<h1 class={className} {style} bind:this={containerElement}>{text}</h1> <h1 class={className} bind:this={containerElement}>{text}</h1>
{:else if type === 'h2'} {:else if type === 'h2'}
<h2 class={className} {style} bind:this={containerElement}>{text}</h2> <h2 class={className} bind:this={containerElement}>{text}</h2>
{:else if type === 'h3'} {:else if type === 'h3'}
<h3 class={className} {style} bind:this={containerElement}>{text}</h3> <h3 class={className} bind:this={containerElement}>{text}</h3>
{:else if type === 'h4'} {:else if type === 'h4'}
<h4 class={className} {style} bind:this={containerElement}>{text}</h4> <h4 class={className} bind:this={containerElement}>{text}</h4>
{:else if type === 'h5'} {:else if type === 'h5'}
<h5 class={className} {style} bind:this={containerElement}>{text}</h5> <h5 class={className} bind:this={containerElement}>{text}</h5>
{:else if type === 'h6'} {:else if type === 'h6'}
<h6 class={className} {style} bind:this={containerElement}>{text}</h6> <h6 class={className} bind:this={containerElement}>{text}</h6>
{/if} {/if}

View File

@ -4,11 +4,6 @@
export let url = "" export let url = ""
export let text = "" export let text = ""
export let openInNewTab = false export let openInNewTab = false
export let color
export let hoverColor
export let underline = false
export let fontFamily
export let fontSize
export let _bb export let _bb
@ -16,43 +11,12 @@
$: anchorElement && !text && _bb.attachChildren(anchorElement) $: anchorElement && !text && _bb.attachChildren(anchorElement)
$: target = openInNewTab ? "_blank" : "_self" $: target = openInNewTab ? "_blank" : "_self"
$: cssVariables = {
hoverColor,
color,
textDecoration: underline ? "underline" : "none",
fontSize,
fontFamily,
}
$: classes = createClasses(cssVariables)
</script> </script>
<a <a href={url} bind:this={anchorElement} {target}>{text}</a>
href={url}
bind:this={anchorElement}
class={classes}
{target}
use:cssVars={cssVariables}>
{text}
</a>
<style> <style>
.color {
color: var(--color);
}
.hoverColor:hover {
color: var(--color);
}
.textDecoration { .textDecoration {
text-decoration: var(--textDecoration); text-decoration: var(--textDecoration);
} }
.fontSize {
font-size: var(--fontSize);
}
.fontFamily {
font-family: var(--fontFamily);
}
</style> </style>

View File

@ -4,45 +4,35 @@
export let text = "" export let text = ""
export let className = "" export let className = ""
export let formattingTag = "" export let type = ""
export let fontFamily = ""
export let fontSize = "1em"
export let textAlign = ""
export let verticalAlign = ""
export let color = ""
export let _bb export let _bb
const isTag = tag => (formattingTag || "").indexOf(tag) > -1 const isTag = tag => type === tag
$: style = buildStyle({
"font-size": fontSize,
"font-family": fontFamily,
color,
})
</script> </script>
{#if isTag('none')} {#if isTag('none')}
<span {style}>{text}</span> <span>{text}</span>
{:else if isTag('<b>')} {:else if isTag('bold')}
<b class={className} {style}>{text}</b> <b class={className}>{text}</b>
{:else if isTag('<strong>')} {:else if isTag('strong')}
<strong class={className} {style}>{text}</strong> <strong class={className}>{text}</strong>
{:else if isTag('<i>')} {:else if isTag('italic')}
<i class={className} {style}>{text}</i> <i class={className}>{text}</i>
{:else if isTag('<em>')} {:else if isTag('emphasis')}
<em class={className} {style}>{text}</em> <em class={className}>{text}</em>
{:else if isTag('<mark>')} {:else if isTag('mark')}
<mark class={className} {style}>{text}</mark> <mark class={className}>{text}</mark>
{:else if isTag('<small>')} {:else if isTag('small')}
<small class={className} {style}>{text}</small> <small class={className}>{text}</small>
{:else if isTag('<del>')} {:else if isTag('del')}
<del class={className} {style}>{text}</del> <del class={className}>{text}</del>
{:else if isTag('<ins>')} {:else if isTag('ins')}
<ins class={className} {style}>{text}</ins> <ins class={className}>{text}</ins>
{:else if isTag('<sub>')} {:else if isTag('sub')}
<sub class={className} {style}>{text}</sub> <sub class={className}>{text}</sub>
{:else if isTag('<sup>')} {:else if isTag('sup')}
<sup class={className} {style}>{text}</sup> <sup class={className}>{text}</sup>
{:else}{text}{/if} {:else}
<span>{text}</span>
{/if}