Merge branch 'master' of github.com:Budibase/budibase into workflow-v1

This commit is contained in:
Martin McKeaveney 2020-05-27 10:13:42 +01:00
commit 6829f9ab06
61 changed files with 2011 additions and 1165 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 128 128" style="enable-background:new 0 0 128 128;" xml:space="preserve">
<style type="text/css">
.st0{fill:#393C44;}
.st1{fill:#FFFFFF;}
</style>
<g>
<path class="st0" d="M117.33,128H10.67C4.78,128,0,123.22,0,117.33V10.67C0,4.78,4.78,0,10.67,0h106.67
C123.22,0,128,4.78,128,10.67v106.67C128,123.22,123.22,128,117.33,128z"/>
<g>
<path class="st1" d="M75.94,32v27.84c3.14-3.38,7.07-5.07,11.79-5.07c2.8,0,5.37,0.54,7.7,1.63c2.33,1.09,4.32,2.55,5.97,4.39
c1.65,1.84,2.93,4.01,3.87,6.51c0.93,2.5,1.4,5.16,1.4,7.98c0,2.87-0.48,5.57-1.44,8.09c-0.96,2.52-2.3,4.72-4.02,6.58
c-1.72,1.87-3.75,3.34-6.08,4.43C92.8,95.46,90.28,96,87.58,96c-4.91,0-8.79-1.84-11.64-5.52v4.31H62.6V32H75.94z M92.74,75.5
c0-1.21-0.22-2.36-0.66-3.44c-0.44-1.08-1.06-2.03-1.84-2.84c-0.79-0.81-1.7-1.45-2.73-1.93c-1.03-0.48-2.16-0.72-3.39-0.72
c-1.18,0-2.28,0.23-3.32,0.68c-1.03,0.45-1.93,1.09-2.69,1.89c-0.76,0.81-1.38,1.75-1.84,2.84c-0.47,1.09-0.7,2.23-0.7,3.44
s0.22,2.34,0.66,3.4c0.44,1.06,1.06,1.98,1.84,2.76c0.79,0.78,1.71,1.41,2.76,1.89c1.06,0.48,2.17,0.72,3.35,0.72
c1.18,0,2.28-0.23,3.32-0.68c1.03-0.45,1.93-1.08,2.69-1.89c0.76-0.81,1.37-1.74,1.84-2.8C92.51,77.77,92.74,76.66,92.74,75.5z"/>
</g>
<g>
<path class="st1" d="M34.67,32v27.84c3.14-3.38,7.07-5.07,11.79-5.07c2.8,0,5.37,0.54,7.7,1.63c2.33,1.09,4.32,2.55,5.97,4.39
c1.65,1.84,2.93,4.01,3.87,6.51c0.93,2.5,1.4,5.16,1.4,7.98c0,2.87-0.48,5.57-1.44,8.09c-0.96,2.52-2.3,4.72-4.02,6.58
c-1.72,1.87-3.75,3.34-6.08,4.43C51.53,95.46,49.01,96,46.31,96c-4.91,0-8.79-1.84-11.64-5.52v4.31H21.33V32H34.67z M51.47,75.5
c0-1.21-0.22-2.36-0.66-3.44c-0.44-1.08-1.06-2.03-1.84-2.84c-0.79-0.81-1.7-1.45-2.73-1.93c-1.03-0.48-2.16-0.72-3.39-0.72
c-1.18,0-2.28,0.23-3.32,0.68c-1.03,0.45-1.93,1.09-2.69,1.89c-0.76,0.81-1.38,1.75-1.84,2.84c-0.47,1.09-0.7,2.23-0.7,3.44
s0.22,2.34,0.66,3.4c0.44,1.06,1.06,1.98,1.84,2.76c0.79,0.78,1.71,1.41,2.76,1.89c1.06,0.48,2.17,0.72,3.35,0.72
c1.18,0,2.28-0.23,3.32-0.68c1.03-0.45,1.93-1.08,2.69-1.89c0.76-0.81,1.37-1.74,1.84-2.8C51.24,77.77,51.47,76.66,51.47,75.5z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

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

@ -0,0 +1,12 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M7 11.5a4.5 4.5 0 1 1 0-9 4.5 4.5 0 0 1 0 9zm0 10a4.5 4.5 0 1 1 0-9 4.5
4.5 0 0 1 0 9zm10-10a4.5 4.5 0 1 1 0-9 4.5 4.5 0 0 1 0 9zm0 10a4.5 4.5 0 1 1
0-9 4.5 4.5 0 0 1 0 9z"
fill="rgba(128,129,146,1)" />
</svg>

After

Width:  |  Height:  |  Size: 370 B

View File

@ -0,0 +1,15 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M5.07 16A7.06 7.06 0 0 1 5
15v-1H3v-2h2v-1c0-.34.024-.673.07-1H3V8h2.674a7.03 7.03 0 0 1
2.84-3.072l-1.05-1.05L8.88 2.465l1.683 1.684a7.03 7.03 0 0 1 2.876
0l1.683-1.684 1.415 1.415-1.05 1.05A7.03 7.03 0 0 1 18.326
8H21v2h-2.07c.046.327.07.66.07 1v1h2v2h-2v1c0 .34-.024.673-.07
1H21v2h-2.674a7 7 0 0 1-12.652 0H3v-2h2.07zM9 10v2h6v-2H9zm0 4v2h6v-2H9z"
fill="rgba(128,129,146,1)" />
</svg>

After

Width:  |  Height:  |  Size: 561 B

View File

@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M12 11a5 5 0 0 1 5 5v6H7v-6a5 5 0 0 1 5-5zm-6.712 3.006a6.983 6.983 0 0
0-.28 1.65L5 16v6H2v-4.5a3.5 3.5 0 0 1 3.119-3.48l.17-.014zm13.424 0A3.501
3.501 0 0 1 22 17.5V22h-3v-6c0-.693-.1-1.362-.288-1.994zM5.5 8a2.5 2.5 0 1 1
0 5 2.5 2.5 0 0 1 0-5zm13 0a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5zM12 2a4 4 0
1 1 0 8 4 4 0 0 1 0-8z"
fill="rgba(128,129,146,1)" />
</svg>

After

Width:  |  Height:  |  Size: 529 B

View File

@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M14.121 10.48a1 1 0 0 0-1.414 0l-.707.706a2 2 0 1
1-2.828-2.828l5.63-5.632a6.5 6.5 0 0 1 6.377 10.568l-2.108
2.135-4.95-4.95zM3.161 4.468a6.503 6.503 0 0 1 8.009-.938L7.757 6.944a4 4 0
0 0 5.513 5.794l.144-.137 4.243 4.242-4.243 4.243a2 2 0 0 1-2.828 0L3.16
13.66a6.5 6.5 0 0 1 0-9.192z"
fill="rgba(128,129,146,1)" />
</svg>

After

Width:  |  Height:  |  Size: 493 B

View File

@ -0,0 +1,11 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M20 22H4a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v18a1 1 0 0 1-1
1zM8 7v2h8V7H8zm0 4v2h8v-2H8zm0 4v2h5v-2H8z"
fill="rgba(128,129,146,1)" />
</svg>

After

Width:  |  Height:  |  Size: 309 B

View File

@ -0,0 +1,12 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1
1-1zm9.06 8.683L5.648 6.238 4.353 7.762l7.72 6.555
7.581-6.56-1.308-1.513-6.285 5.439z"
fill="rgba(128,129,146,1)" />
</svg>

After

Width:  |  Height:  |  Size: 352 B

View File

@ -0,0 +1,11 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M7 20.981a6.5 6.5 0 0 1-2.936-12 8.001 8.001 0 0 1 15.872 0 6.5 6.5 0 0
1-2.936 12V21H7v-.019zM13 13h3l-4-5-4 5h3v4h2v-4z"
fill="rgba(128,129,146,1)" />
</svg>

After

Width:  |  Height:  |  Size: 316 B

View File

@ -0,0 +1,11 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M13 16.938V19h5v2H6v-2h5v-2.062A8.001 8.001 0 0 1 4 9V3h16v6a8.001 8.001
0 0 1-7 7.938zM1 5h2v4H1V5zm20 0h2v4h-2V5z"
fill="rgba(128,129,146,1)" />
</svg>

After

Width:  |  Height:  |  Size: 310 B

View File

@ -0,0 +1,16 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M22.162 5.656a8.384 8.384 0 0 1-2.402.658A4.196 4.196 0 0 0 21.6
4c-.82.488-1.719.83-2.656 1.015a4.182 4.182 0 0 0-7.126 3.814 11.874 11.874
0 0 1-8.62-4.37 4.168 4.168 0 0 0-.566 2.103c0 1.45.738 2.731 1.86
3.481a4.168 4.168 0 0 1-1.894-.523v.052a4.185 4.185 0 0 0 3.355 4.101 4.21
4.21 0 0 1-1.89.072A4.185 4.185 0 0 0 7.97 16.65a8.394 8.394 0 0 1-6.191
1.732 11.83 11.83 0 0 0 6.41 1.88c7.693 0 11.9-6.373 11.9-11.9
0-.18-.005-.362-.013-.54a8.496 8.496 0 0 0 2.087-2.165z"
fill="rgba(128,129,146,1)" />
</svg>

After

Width:  |  Height:  |  Size: 689 B

View File

@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10
10zm4.82-4.924A7 7 0 0 0 9.032 5.658l.975 1.755A5 5 0 0 1 17 12h-3l2.82
5.076zm-1.852 1.266l-.975-1.755A5 5 0 0 1 7 12h3L7.18 6.924a7 7 0 0 0 7.788
11.418z"
fill="rgba(128,129,146,1)" />
</svg>

After

Width:  |  Height:  |  Size: 428 B

View File

@ -19,3 +19,13 @@ export { default as AddIcon } from "./Add.svelte"
export { default as JavaScriptIcon } from "./JavaScript.svelte" export { default as JavaScriptIcon } from "./JavaScript.svelte"
export { default as PreviewIcon } from "./Preview.svelte" export { default as PreviewIcon } from "./Preview.svelte"
export { default as SettingsIcon } from "./Settings.svelte" export { default as SettingsIcon } from "./Settings.svelte"
export { default as AppsIcon } from "./Apps.svelte"
export { default as UpdatesIcon } from "./Updates.svelte"
export { default as HostingIcon } from "./Hosting.svelte"
export { default as DocumentationIcon } from "./Documentation.svelte"
export { default as TutorialsIcon } from "./Tutorials.svelte"
export { default as CommunityIcon } from "./Community.svelte"
export { default as ContributionIcon } from "./Contribution.svelte"
export { default as BugIcon } from "./Bug.svelte"
export { default as EmailIcon } from "./Email.svelte"
export { default as TwitterIcon } from "./Twitter.svelte"

View File

@ -1,19 +1,22 @@
<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 {
display: block; display: block;
font-size: 14px; font-size: 14px;
font-family: sans-serif;
font-weight: 500; font-weight: 500;
color: #163057; color: var(--ink);
line-height: 1.3; line-height: 1.3;
padding: 1em 2.6em 0.9em 1.4em; padding: 12px;
width: 100%; width: 164px;
float: right;
max-width: 100%; max-width: 100%;
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
@ -21,7 +24,7 @@
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
background: #fff; background: #fff;
border: 1px solid #ccc; border: 1px solid var(--grey-dark);
height: 35px; height: 32px;
} }
</style> </style>

View File

@ -2,43 +2,58 @@
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">
<div class="label">{label}</div>
<div class="inputs">
{#each meta as { placeholder }, i} {#each meta as { placeholder }, i}
<input <input
{type} {type}
{placeholder} placeholder={placeholder || ''}
value={values[i]} value={!value || value[i] === 0 ? '' : value[i]}
on:input={e => (_values[i] = e.target.value)} /> on:change={e => handleChange(e.target.value || 0, i)} />
{/each} {/each}
</div> </div>
</div>
<style> <style>
.input-container {
}
.label {
flex: 0;
}
.inputs { .inputs {
display: flex; flex: 1;
justify-content: space-between;
} }
input { input {
width: 83px; width: 40px;
font-size: 13px; height: 32px;
font-size: 12px;
font-weight: 700; font-weight: 700;
color: #163057; margin: 0px 0px 0px 1px;
text-align: center;
color: var(--ink);
opacity: 0.7; opacity: 0.7;
padding: 5px 10px; padding: 0px 4px;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid #dbdbdb; border: 1px solid var(--grey);
border-radius: 2px; border-radius: 2px;
outline: none; outline: none;
float: right;
} }
input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-inner-spin-button,
@ -49,17 +64,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

@ -81,12 +81,10 @@
.hierarchy-title { .hierarchy-title {
align-items: center; align-items: center;
text-transform: uppercase; font-size: 18px;
font-size: 13px; font-weight: 700;
font-weight: bold;
opacity: 0.6;
letter-spacing: 1px;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
color: var(--ink);
} }
.hierarchy { .hierarchy {

View File

@ -125,12 +125,10 @@
.hierarchy-title { .hierarchy-title {
align-items: center; align-items: center;
text-transform: uppercase; font-size: 18px;
font-size: 13px; font-weight: 700;
font-weight: bold;
opacity: 0.6;
letter-spacing: 1px;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
color: var(--ink);
} }
.hierarchy { .hierarchy {

View File

@ -1,19 +1,32 @@
<script> <script>
import Button from "components/common/Button.svelte" import Button from "components/common/Button.svelte"
export let apps export let apps
function myFunction() {
var x = new Date(document.lastModified)
document.getElementById("demo").innerHTML = x
}
</script> </script>
<div class="root"> <div class="root">
<div class="inner"> <div class="inner">
<img
src="/_builder/assets/budibase-logo.png"
class="logo"
alt="budibase logo" />
<div> <div>
<div> <div>
<h4 style="margin-bottom: 20px">Choose an Application</h4> <div class="app-section-title">Your Web Apps</div>
{#each apps as app} {#each apps as app}
<a href={`/_builder/${app._id}`} class="app-link">{app.name}</a> <div class="apps-card">
<h3 class="app-title">{app.name}</h3>
<p class="app-desc">
A minimalist CRM which removes the noise and allows you to focus
on your business.
</p>
<div class="card-footer">
<div class="modified-date">Last Edited - 25th May 2020</div>
<a href={`/_builder/${app._id}`} class="app-button">
Open Web App
</a>
</div>
</div>
{/each} {/each}
</div> </div>
</div> </div>
@ -22,31 +35,68 @@
<style> <style>
.root { .root {
position: fixed; margin: 40px 80px;
margin: 0 auto;
text-align: center;
top: 20%;
/*color: #333333;
background-color: #fdfdfd;*/
width: 100%;
} }
.inner { .app-section-title {
display: inline-block; font-size: 20px;
margin: auto; color: var(--ink);
font-weight: 700;
margin-bottom: 20px;
} }
.logo { .apps {
width: 300px; display: flex;
margin-bottom: 40px; flex-wrap: wrap;
gap: 40px;
}
.apps-card {
background-color: var(--white);
padding: 20px;
max-width: 400px;
max-height: 150px;
border-radius: 5px;
border: 1px solid var(--grey-dark);
} }
.root :global(.option) { .app-button:hover {
width: 250px; background-color: var(--grey-light);
text-decoration: none;
} }
.app-link { .app-title {
margin-top: 10px; font-size: 18px;
display: block; font-weight: 700;
color: var(--ink);
text-transform: capitalize;
}
.app-desc {
color: var(--ink-light);
}
.card-footer {
display: flex;
flex-direction: row;
align-items: baseline;
justify-content: space-between;
}
.modified-date {
font-size: 14px;
color: var(--ink-light);
}
.app-button {
background-color: var(--white);
color: var(--ink);
padding: 12px 20px;
border-radius: 5px;
border: 1px var(--grey) solid;
font-size: 14px;
font-weight: 400;
cursor: pointer;
transition: all 0.2s;
box-sizing: border-box;
} }
</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,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>

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} {/if}
{:else if current_view === 'layout'}
<LayoutEditor {onStyleChanged} {component} />
{:else if current_view === 'events'}
<EventsEditor {component} {components} />
{/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) {
@ -159,55 +117,7 @@
} }
.component-props-container { .component-props-container {
margin-top: 10px; margin-top: 20px;
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

@ -24,13 +24,13 @@
<button <button
class:selected={selected === COMPONENT_SELECTION_TAB} class:selected={selected === COMPONENT_SELECTION_TAB}
on:click={() => selectTab(COMPONENT_SELECTION_TAB)}> on:click={() => selectTab(COMPONENT_SELECTION_TAB)}>
Components Add
</button> </button>
<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>
@ -54,33 +54,28 @@
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 20px 0; padding: 20px 20px;
border-left: solid 1px #e8e8ef;
} }
.switcher { .switcher {
display: flex; display: flex;
justify-content: space-between; margin: 0px 20px 20px 0px;
margin: 20px;
} }
.switcher > button { .switcher > button {
text-rendering: optimizeLegibility;
display: inline-block; display: inline-block;
border: none; border: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 18px;
text-transform: uppercase; font-weight: 700;
background: rgba(0, 0, 0, 0); color: var(--ink-lighter);
font-weight: 500;
color: var(--secondary40);
margin-right: 20px; margin-right: 20px;
letter-spacing: 1px;
} }
.switcher > .selected { .switcher > .selected {
color: var(--secondary100); color: var(--ink);
font-weight: 600;
} }
</style> </style>

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

View File

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

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

View File

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

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",
control: Input,
}, },
text: "string", {
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

@ -1,5 +1,20 @@
:root { :root {
--white: #FFFFFF;
--blue: #0055ff;
--blue-light: #F1F4FC;
--blue-dark: #2F4C9B;
--ink: #393C44;
--ink-light: #808192;
--ink-lighter: #ADAEC4;
--grey: #F2F2F2;
--grey-light: #FBFBFB;
--grey-dark: #E6E6E6;
--primary100: #0055ff; --primary100: #0055ff;
--primary80: rgba(0, 85, 255, 0.8); --primary80: rgba(0, 85, 255, 0.8);
--primary60: #rgba(0, 85, 255, 0.6); --primary60: #rgba(0, 85, 255, 0.6);
@ -9,7 +24,7 @@
--primary5: #rgba(0, 85, 255, 0.05); --primary5: #rgba(0, 85, 255, 0.05);
--primarydark: #0044cc; --primarydark: #0044cc;
--secondary100:#000333; --secondary100:#393C44;
--secondary80: rgba(0, 3, 51, 0.8); --secondary80: rgba(0, 3, 51, 0.8);
--secondary60: rgba(0, 3, 51, 0.6); --secondary60: rgba(0, 3, 51, 0.6);
--secondary40: rgba(0, 3, 51, 0.4); --secondary40: rgba(0, 3, 51, 0.4);
@ -34,7 +49,7 @@
--deletion10: #F2545B1A; --deletion10: #F2545B1A;
--deletiondark: #CF4046; --deletiondark: #CF4046;
--white: #FFFFFF;
--darkslate: #1a202c; --darkslate: #1a202c;
--slate: #d8d8d8; --slate: #d8d8d8;
--lightslate: #f9f9f9; --lightslate: #f9f9f9;
@ -105,7 +120,7 @@ h3 {
h4 { h4 {
font-family: var(--fontbold); font-family: var(--fontbold);
font-size: 18pt; font-size: 18pt;
color: var(--secondary100); color: var(--ink);
} }
h5 { h5 {

View File

@ -31,8 +31,9 @@
<div class="topleftnav"> <div class="topleftnav">
<button class="home-logo"> <button class="home-logo">
<img <img
src="/_builder/assets/budibase-emblem-white.svg" src="/_builder/assets/bb-logo.svg"
alt="budibase icon" /> alt="budibase icon"
on:click={() => $goto(`/`)} />
</button> </button>
<!-- This gets all indexable subroutes and sticks them in the top nav. --> <!-- This gets all indexable subroutes and sticks them in the top nav. -->
@ -85,13 +86,14 @@
.top-nav { .top-nav {
flex: 0 0 auto; flex: 0 0 auto;
height: 48px; height: 60px;
background: #0d203b; background: #fff;
padding: 0px 20px 0 20px; padding: 0px 20px 0 20px;
display: flex; display: flex;
box-sizing: border-box; box-sizing: border-box;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
border-bottom: 1px solid var(--grey);
} }
.content > div { .content > div {
@ -110,8 +112,8 @@
.topnavitem { .topnavitem {
cursor: pointer; cursor: pointer;
color: rgb(255, 255, 255, 0.6); color: var(--ink-lighter);
margin: 0px 10px; margin: 0px 00px 0px 20px;
padding-top: 4px; padding-top: 4px;
font-weight: 500; font-weight: 500;
font-size: 1rem; font-size: 1rem;
@ -121,19 +123,19 @@
} }
.topnavitem:hover { .topnavitem:hover {
color: rgb(255, 255, 255, 0.8); color: var(--ink-light);
font-weight: 500; font-weight: 500;
} }
.active { .active {
color: white; color: var(--ink);
font-weight: 600; font-weight: 500;
} }
.topnavitemright { .topnavitemright {
cursor: pointer; cursor: pointer;
color: rgb(255, 255, 255, 0.6); color: var(--ink-light);
margin: 0px 5px; margin: 0px 20px 0px 0px;
padding-top: 4px; padding-top: 4px;
font-weight: 500; font-weight: 500;
font-size: 1rem; font-size: 1rem;
@ -155,7 +157,8 @@
cursor: pointer; cursor: pointer;
outline: none; outline: none;
height: 40px; height: 40px;
padding: 8px 10px 8px 0; padding: 0px 10px 8px 0;
align-items: center;
} }
.home-logo:hover { .home-logo:hover {
@ -167,7 +170,7 @@
} }
.home-logo img { .home-logo img {
height: 100%; height: 40px;
} }
span:first-letter { span:first-letter {
text-transform: capitalize; text-transform: capitalize;

View File

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

View File

@ -54,9 +54,7 @@
<div class="pages-list-container"> <div class="pages-list-container">
<div class="nav-header"> <div class="nav-header">
<span class="navigator-title">Navigator</span> <span class="navigator-title">Navigate</span>
<div class="border-line" />
<span class="components-nav-page">Pages</span> <span class="components-nav-page">Pages</span>
</div> </div>
@ -129,18 +127,16 @@
.root { .root {
display: grid; display: grid;
grid-template-columns: 275px 1fr 275px; grid-template-columns: 275px 1fr 275px;
height: 100%;
width: 100%; width: 100%;
background: #fafafa; background: var(--grey-light);
} }
@media only screen and (min-width: 1800px) { @media only screen and (min-width: 1800px) {
.root { .root {
display: grid; display: grid;
grid-template-columns: 300px 1fr 300px; grid-template-columns: 300px 1fr 300px;
height: 100%;
width: 100%; width: 100%;
background: #fafafa; background: var(--grey-light);
} }
} }
@ -159,20 +155,16 @@
margin: 40px; margin: 40px;
background: #fff; background: #fff;
border-radius: 5px; border-radius: 5px;
box-shadow: 0 0px 6px rgba(0, 0, 0, 0.05);
} }
.components-pane { .components-pane {
grid-column: 3; grid-column: 3;
background-color: var(--white); background-color: var(--white);
min-height: 0px;
overflow-y: scroll;
} }
.components-nav-page { .components-nav-page {
font-size: 13px; font-size: 13px;
color: #000333; color: var(--ink);
text-transform: uppercase;
padding-left: 20px; padding-left: 20px;
margin-top: 20px; margin-top: 20px;
font-weight: 600; font-weight: 600;
@ -182,8 +174,7 @@
.components-nav-header { .components-nav-header {
font-size: 13px; font-size: 13px;
color: #000333; color: var(--ink);
text-transform: uppercase;
margin-top: 20px; margin-top: 20px;
font-weight: 600; font-weight: 600;
opacity: 0.4; opacity: 0.4;
@ -235,13 +226,10 @@
} }
.navigator-title { .navigator-title {
font-size: 14px; font-size: 18px;
color: var(--secondary100); color: var(--ink);
font-weight: 600; font-weight: bold;
text-transform: uppercase;
padding: 0 20px 20px 20px; padding: 0 20px 20px 20px;
line-height: 1rem !important;
letter-spacing: 1px;
} }
.border-line { .border-line {

View File

@ -2,7 +2,21 @@
import { store } from "builderStore" import { store } from "builderStore"
import AppList from "components/start/AppList.svelte" import AppList from "components/start/AppList.svelte"
import { onMount } from "svelte" import { onMount } from "svelte"
import ActionButton from "components/common/ActionButton.svelte"
import IconButton from "components/common/IconButton.svelte" import IconButton from "components/common/IconButton.svelte"
import {
SettingsIcon,
AppsIcon,
UpdatesIcon,
HostingIcon,
DocumentationIcon,
TutorialsIcon,
CommunityIcon,
ContributionIcon,
BugIcon,
EmailIcon,
TwitterIcon,
} from "components/common/Icons/"
import Spinner from "components/common/Spinner.svelte" import Spinner from "components/common/Spinner.svelte"
let promise = getApps() let promise = getApps()
@ -19,7 +33,116 @@
} }
</script> </script>
<main> <div class="root">
<div class="ui-nav">
<div class="home-logo">
<img src="/_builder/assets/bb-logo.svg" alt="Budibase icon" />
</div>
<div class="nav-section">
<div class="nav-section-title">Build</div>
<div class="nav-item-home">
<span class="nav-item-icon">
<AppsIcon />
</span>
<div class="nav-item-title">Apps</div>
</div>
<div class="nav-item">
<span class="nav-item-icon">
<SettingsIcon />
</span>
<div class="nav-item-title">Settings</div>
</div>
<a href="https://budibase.con/login" target="_blank" class="nav-item">
<span class="nav-item-icon">
<UpdatesIcon />
</span>
<div class="nav-item-title">Updates</div>
</a>
<a href="https://budibase.con/login" target="_blank" class="nav-item">
<span class="nav-item-icon">
<HostingIcon />
</span>
<div class="nav-item-title">Hosting</div>
</a>
</div>
<div class="nav-section">
<div class="nav-section-title">Learn</div>
<a href="https://docs.budibase.com/" target="_blank" class="nav-item">
<span class="nav-item-icon">
<DocumentationIcon />
</span>
<div class="nav-item-title">Documentation</div>
</a>
<a
href="https://docs.budibase.com/tutorial/quick-start"
target="_blank"
class="nav-item">
<span class="nav-item-icon">
<TutorialsIcon />
</span>
<div class="nav-item-title">Tutorials</div>
</a>
<a href="https://forum.budibase.com/" target="_blank" class="nav-item">
<span class="nav-item-icon">
<CommunityIcon />
</span>
<div class="nav-item-title">Community</div>
</a>
</div>
<div class="nav-section">
<div class="nav-section-title">Contact</div>
<a
href="https://github.com/Budibase/budibase/blob/master/CONTRIBUTING.md"
target="_blank"
class="nav-item">
<span class="nav-item-icon">
<ContributionIcon />
</span>
<div class="nav-item-title">Contribute to our product</div>
</a>
<a
href="https://github.com/Budibase/budibase/issues"
target="_blank"
class="nav-item">
<span class="nav-item-icon">
<BugIcon />
</span>
<div class="nav-item-title">Report bug</div>
</a>
<a href="mailto:support@budibase.com" target="_blank" class="nav-item">
<span class="nav-item-icon">
<EmailIcon />
</span>
<div class="nav-item-title">Email</div>
</a>
<a href="https://twitter.com/budibase" target="_blank" class="nav-item">
<span class="nav-item-icon">
<TwitterIcon />
</span>
<div class="nav-item-title">Twitter</div>
</a>
</div>
</div>
<div class="main">
<div class="welcome">Welcome to Budibase</div>
<div class="banner">
<div class="banner-content">
<div class="banner-header">
Every accomplishment starts with a decision to try.
</div>
<button class="banner-button" type="button">
<i class="ri-add-circle-fill" />
Create New Web App
</button>
</div>
<div class="banner-image">
<img src="/_builder/assets/banner-image.png" alt="Bannerimage" />
</div>
</div>
{#await promise} {#await promise}
<div class="spinner-container"> <div class="spinner-container">
<Spinner /> <Spinner />
@ -29,13 +152,156 @@
{:catch err} {:catch err}
<h1 style="color:red">{err}</h1> <h1 style="color:red">{err}</h1>
{/await} {/await}
</main> </div>
</div>
<style> <style>
main { .root {
display: grid;
grid-template-columns: 275px 1fr;
height: 100%; height: 100%;
width: 100%; width: 100%;
font-family: "Roboto", Helvetica, Arial, sans-serif; background: var(--grey-light);
}
@media only screen and (min-width: 1800px) {
.root {
display: grid;
grid-template-columns: 300px 1fr;
height: 100%;
width: 100%;
background: var(--grey-light);
}
}
.main {
grid-column: 2;
}
.ui-nav {
grid-column: 1;
background-color: var(--white);
padding: 20px;
display: flex;
flex-direction: column;
border-right: 1px solid var(--grey-dark);
}
.home-logo {
cursor: pointer;
height: 40px;
margin-bottom: 20px;
}
.home-logo img {
height: 40px;
}
.nav-section {
margin: 20px 0px;
display: flex;
flex-direction: column;
}
.nav-section-title {
font-size: 20px;
color: var(--ink);
font-weight: 700;
margin-bottom: 12px;
}
.nav-item {
cursor: pointer;
margin: 0px 0px 4px 0px;
padding: 0px 0px 0px 12px;
height: 40px;
display: flex;
flex-direction: row;
align-items: center;
box-sizing: border-box;
}
.nav-item-home {
cursor: pointer;
margin: 0px 0px 4px 0px;
padding: 0px 0px 0px 12px;
height: 40px;
display: flex;
flex-direction: row;
align-items: center;
box-sizing: border-box;
background-color: var(--blue-light);
}
.nav-item:hover {
background-color: var(--grey-light);
border-radius: 3px;
}
.nav-item::selection {
background-color: var(--blue-light);
border-radius: 3px;
}
.nav-item-title {
font-size: 14px;
color: var(--ink);
font-weight: 500;
margin-left: 12px;
}
.nav-item-icon {
color: var(--ink-light);
}
.welcome {
margin: 60px 80px 0px 80px;
font-size: 42px;
color: var(--ink);
font-weight: 900;
}
.banner {
display: grid;
grid-template-columns: 1fr 1fr;
margin: 20px 80px 40px 80px;
background-image: linear-gradient(-45deg, #7f9ceb, #1d2f77);
border-radius: 10px;
max-height: 280px;
}
.banner-content {
padding: 60px;
}
@media only screen and (min-width: 1800px) {
.banner-content {
padding: 80px;
}
}
.banner-header {
font-size: 24px;
color: var(--white);
font-weight: 500;
margin-bottom: 20px;
}
@media only screen and (min-width: 1800px) {
.banner-header {
font-size: 36px;
color: var(--white);
font-weight: 500;
margin-bottom: 20px;
}
}
.banner-image {
z-index: 1;
}
.banner-image img {
max-height: 400px;
} }
.spinner-container { .spinner-container {
@ -45,4 +311,28 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.banner-button {
background-color: var(--white);
color: var(--ink);
padding: 12px 20px;
border-radius: 5px;
border: 0px transparent solid;
font-size: 16px;
font-weight: 400;
cursor: pointer;
transition: all 0.2s;
box-sizing: border-box;
align-items: center;
display: flex;
}
.ri-add-circle-fill {
margin-right: 4px;
font-size: 24px;
}
.banner-button:hover {
background-color: var(--grey);
}
</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("Check how array styles are output", () => {
expect(generate_css({ margin: ["0", "10", "0", "15"] })).toBe("margin: 0px 10px 0px 15px;")
})
test("Check handling of an array with empty string values", () => {
expect(generate_css({ padding: ["", "", "", ""] })).toBe("")
})
test("Check handling of an empty array", () => {
expect(generate_css({ margin: [] })).toBe("")
})
test("Check handling of valid font property", () => {
expect(generate_css({ "font-size": "10px" })).toBe("font-size: 10px;")
}) })
}) })
test("it should generate a valid css rule: grid-gap", () => {
expect(generate_css({ layout: { gap: "10" } })).toEqual({
layout: "grid-gap: 10px;\ndisplay: grid;",
position: "",
})
})
test("it should generate a valid css rule: column 1", () => {
expect(generate_css({ position: { column: ["", ""] } })).toEqual({
layout: "",
position: "",
})
})
test("it should generate a valid css rule: column 2", () => {
expect(generate_css({ position: { column: ["1", ""] } })).toEqual({
position: "grid-column-start: 1;",
layout: "",
})
})
test("it should generate a valid css rule: column 3", () => {
expect(generate_css({ position: { column: ["", "1"] } })).toEqual({
position: "grid-column-end: 1;",
layout: "",
})
})
test("it should generate a valid css rule: column 4", () => {
expect(generate_css({ position: { column: ["1", "1"] } })).toEqual({
position: "grid-column-start: 1;\ngrid-column-end: 1;",
layout: "",
})
})
test("it should generate a valid css rule: row 1", () => {
expect(generate_css({ position: { row: ["", ""] } })).toEqual({
layout: "",
position: "",
})
})
test("it should generate a valid css rule: row 2", () => {
expect(generate_css({ position: { row: ["1", ""] } })).toEqual({
position: "grid-row-start: 1;",
layout: "",
})
})
test("it should generate a valid css rule: row 3", () => {
expect(generate_css({ position: { row: ["", "1"] } })).toEqual({
position: "grid-row-end: 1;",
layout: "",
})
})
test("it should generate a valid css rule: row 4", () => {
expect(generate_css({ position: { row: ["1", "1"] } })).toEqual({
position: "grid-row-start: 1;\ngrid-row-end: 1;",
layout: "",
})
})
test("it should generate a valid css rule: padding 1", () => {
expect(
generate_css({ position: { padding: ["1", "1", "1", "1"] } })
).toEqual({
position: "padding: 1px 1px 1px 1px;",
layout: "",
})
})
test("it should generate a valid css rule: padding 2", () => {
expect(generate_css({ position: { padding: ["1", "", "", "1"] } })).toEqual(
{
position: "padding: 1px 0px 0px 1px;",
layout: "",
}
)
})
test("it should generate a valid css rule: margin 1", () => {
expect(
generate_css({ position: { margin: ["1", "1", "1", "1"] } })
).toEqual({
position: "margin: 1px 1px 1px 1px;",
layout: "",
})
})
test("it should generate a valid css rule: margin 2", () => {
expect(generate_css({ position: { margin: ["1", "", "", "1"] } })).toEqual({
position: "margin: 1px 0px 0px 1px;",
layout: "",
})
})
test("it should generate a valid css rule: z-index 1", () => {
expect(generate_css({ position: { zindex: "" } })).toEqual({
position: "",
layout: "",
})
})
test("it should generate a valid css rule: z-index 2", () => {
expect(generate_css({ position: { zindex: "1" } })).toEqual({
position: "z-index: 1;",
layout: "",
})
})
})
describe("generate_screen_css", () => { 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 = [
{
_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 { test("Test generation of normal css styles", () => {
margin: 1px 1px 1px 1px; expect(generate_screen_css([normalComponent])).toBe(".header-123-456 {\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 {
}`
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 {
} const selectedComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: { "font-size": "16px" } } }
.pos-2 {
margin: 1px 1px 1px 1px;
}
.lay-2 {
} test("Test generation of selection css styles", () => {
.pos-3 { expect(generate_screen_css([selectedComponent])).toBe(".header-123-456::selection {\nfont-size: 16px;\n}")
margin: 1px 1px 1px 1px; })
}
.lay-3 {
} const emptyComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: {} } }
.pos-4 {
margin: 1px 1px 1px 1px;
}
.lay-4 {
} test.only("Testing handling of empty component styles", () => {
.pos-5 { expect(generate_screen_css([emptyComponent])).toBe("")
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)
}) })
}) })

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}`)
} }
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 128 128" style="enable-background:new 0 0 128 128;" xml:space="preserve">
<style type="text/css">
.st0{fill:#393C44;}
.st1{fill:#FFFFFF;}
</style>
<g>
<path class="st0" d="M117.33,128H10.67C4.78,128,0,123.22,0,117.33V10.67C0,4.78,4.78,0,10.67,0h106.67
C123.22,0,128,4.78,128,10.67v106.67C128,123.22,123.22,128,117.33,128z"/>
<g>
<path class="st1" d="M75.94,32v27.84c3.14-3.38,7.07-5.07,11.79-5.07c2.8,0,5.37,0.54,7.7,1.63c2.33,1.09,4.32,2.55,5.97,4.39
c1.65,1.84,2.93,4.01,3.87,6.51c0.93,2.5,1.4,5.16,1.4,7.98c0,2.87-0.48,5.57-1.44,8.09c-0.96,2.52-2.3,4.72-4.02,6.58
c-1.72,1.87-3.75,3.34-6.08,4.43C92.8,95.46,90.28,96,87.58,96c-4.91,0-8.79-1.84-11.64-5.52v4.31H62.6V32H75.94z M92.74,75.5
c0-1.21-0.22-2.36-0.66-3.44c-0.44-1.08-1.06-2.03-1.84-2.84c-0.79-0.81-1.7-1.45-2.73-1.93c-1.03-0.48-2.16-0.72-3.39-0.72
c-1.18,0-2.28,0.23-3.32,0.68c-1.03,0.45-1.93,1.09-2.69,1.89c-0.76,0.81-1.38,1.75-1.84,2.84c-0.47,1.09-0.7,2.23-0.7,3.44
s0.22,2.34,0.66,3.4c0.44,1.06,1.06,1.98,1.84,2.76c0.79,0.78,1.71,1.41,2.76,1.89c1.06,0.48,2.17,0.72,3.35,0.72
c1.18,0,2.28-0.23,3.32-0.68c1.03-0.45,1.93-1.08,2.69-1.89c0.76-0.81,1.37-1.74,1.84-2.8C92.51,77.77,92.74,76.66,92.74,75.5z"/>
</g>
<g>
<path class="st1" d="M34.67,32v27.84c3.14-3.38,7.07-5.07,11.79-5.07c2.8,0,5.37,0.54,7.7,1.63c2.33,1.09,4.32,2.55,5.97,4.39
c1.65,1.84,2.93,4.01,3.87,6.51c0.93,2.5,1.4,5.16,1.4,7.98c0,2.87-0.48,5.57-1.44,8.09c-0.96,2.52-2.3,4.72-4.02,6.58
c-1.72,1.87-3.75,3.34-6.08,4.43C51.53,95.46,49.01,96,46.31,96c-4.91,0-8.79-1.84-11.64-5.52v4.31H21.33V32H34.67z M51.47,75.5
c0-1.21-0.22-2.36-0.66-3.44c-0.44-1.08-1.06-2.03-1.84-2.84c-0.79-0.81-1.7-1.45-2.73-1.93c-1.03-0.48-2.16-0.72-3.39-0.72
c-1.18,0-2.28,0.23-3.32,0.68c-1.03,0.45-1.93,1.09-2.69,1.89c-0.76,0.81-1.38,1.75-1.84,2.84c-0.47,1.09-0.7,2.23-0.7,3.44
s0.22,2.34,0.66,3.4c0.44,1.06,1.06,1.98,1.84,2.76c0.79,0.78,1.71,1.41,2.76,1.89c1.06,0.48,2.17,0.72,3.35,0.72
c1.18,0,2.28-0.23,3.32-0.68c1.03-0.45,1.93-1.08,2.69-1.89c0.76-0.81,1.37-1.74,1.84-2.8C51.24,77.77,51.47,76.66,51.47,75.5z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

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,25 +308,6 @@
"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,
@ -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}