Merge branch 'master' of github.com:Budibase/budibase into external-tables

This commit is contained in:
Martin McKeaveney 2021-01-07 13:39:48 +00:00
commit f2abc7d351
25 changed files with 395 additions and 255 deletions

View File

@ -366,20 +366,23 @@ export const getFrontendStore = () => {
await Promise.all(promises)
},
updateStyle: async (type, name, value) => {
let promises = []
const selected = get(selectedComponent)
store.update(state => {
if (!selected._styles) {
selected._styles = {}
}
if (value == null || value === "") {
delete selected._styles[type][name]
} else {
selected._styles[type][name] = value
// save without messing with the store
promises.push(store.actions.preview.saveSelected())
return state
})
await Promise.all(promises)
}
await store.actions.preview.saveSelected()
},
updateCustomStyle: async style => {
const selected = get(selectedComponent)
selected._styles.custom = style
await store.actions.preview.saveSelected()
},
resetStyles: async () => {
const selected = get(selectedComponent)
selected._styles = { normal: {}, hover: {}, active: {} }
await store.actions.preview.saveSelected()
},
updateProp: (name, value) => {
store.update(state => {

View File

@ -1,6 +1,6 @@
<script>
import { onMount } from "svelte"
import { store, currentAsset } from "builderStore"
import { store, currentAsset, selectedComponent } from "builderStore"
import iframeTemplate from "./iframeTemplate"
import { Screen } from "builderStore/store/screenTemplates/utils/Screen"
import { FrontendTypes } from "../../../constants"
@ -33,6 +33,7 @@
layout,
screen,
selectedComponentId,
previewType: $store.currentFrontEndType,
}
// Saving pages and screens to the DB causes them to have _revs.
@ -54,17 +55,18 @@
// Refresh the preview when required
$: refreshContent(strippedJson)
// Initialise the app when mounted
onMount(() => {
// Initialise the app when mounted
iframe.contentWindow.addEventListener(
"bb-ready",
() => {
refreshContent(strippedJson)
},
{
once: true,
}
() => refreshContent(strippedJson),
{ once: true }
)
// Add listener to select components
iframe.contentWindow.addEventListener("bb-select-component", data => {
store.actions.components.select({ _id: data.detail })
})
})
</script>

View File

@ -10,6 +10,9 @@
*, *:before, *:after {
box-sizing: border-box;
}
* {
pointer-events: none;
}
</style>
<script src='/assets/budibase-client.js'></script>
<script>
@ -19,7 +22,7 @@
}
// Extract data from message
const { selectedComponentId, layout, screen } = JSON.parse(event.data)
const { selectedComponentId, layout, screen, previewType } = JSON.parse(event.data)
// Set some flags so the app knows we're in the builder
window["##BUDIBASE_IN_BUILDER##"] = true
@ -27,6 +30,7 @@
window["##BUDIBASE_PREVIEW_SCREEN##"] = screen
window["##BUDIBASE_SELECTED_COMPONENT_ID##"] = selectedComponentId
window["##BUDIBASE_PREVIEW_ID##"] = Math.random()
window["##BUDIBASE_PREVIEW_TYPE##"] = previewType
// Initialise app
if (window.loadBudibase) {
@ -34,15 +38,6 @@
}
}
// Ignore clicks
["click", "mousedown"].forEach(type => {
document.addEventListener(type, function(e) {
e.preventDefault()
e.stopPropagation()
return false
}, true)
})
window.addEventListener("message", receiveMessage)
window.dispatchEvent(new Event("bb-ready"))
</script>

View File

@ -62,7 +62,7 @@
<TextArea
thin
bind:value
placeholder="Add text, or lick the objects on the left to add them to the
placeholder="Add text, or click the objects on the left to add them to the
textbox." />
<div class="controls">
<a href="https://docs.budibase.com/design/binding">

View File

@ -31,6 +31,8 @@
componentPropDefinition.properties[selectedCategory.value]
const onStyleChanged = store.actions.components.updateStyle
const onCustomStyleChanged = store.actions.components.updateCustomStyle
const onResetStyles = store.actions.components.resetStyles
$: isComponentOrScreen =
$store.currentView === "component" ||
@ -93,7 +95,12 @@
<div class="component-props-container">
{#if selectedCategory.value === 'design'}
<DesignView {panelDefinition} {componentInstance} {onStyleChanged} />
<DesignView
{panelDefinition}
{componentInstance}
{onStyleChanged}
{onCustomStyleChanged}
{onResetStyles} />
{:else if selectedCategory.value === 'settings'}
<SettingsView
{componentInstance}

View File

@ -1,82 +0,0 @@
<script>
import { store, allScreens } from "builderStore"
import { FrontendTypes } from "constants"
import ComponentPropertiesPanel from "./ComponentPropertiesPanel.svelte"
import ComponentSelectionList from "./ComponentSelectionList.svelte"
const PROPERTIES_TAB = "properties"
const COMPONENT_SELECTION_TAB = "components"
let selected = PROPERTIES_TAB
const isSelected = tab => selected === tab
const selectTab = tab => (selected = tab)
const toggleTab = () =>
(selected =
selected === PROPERTIES_TAB ? COMPONENT_SELECTION_TAB : PROPERTIES_TAB)
</script>
<div class="root">
{#if $store.currentFrontEndType === FrontendTypes.LAYOUT || $allScreens.length}
<div class="switcher">
<button
class:selected={selected === COMPONENT_SELECTION_TAB}
on:click={() => selectTab(COMPONENT_SELECTION_TAB)}>
Add
</button>
<button
class:selected={selected === PROPERTIES_TAB}
on:click={() => selectTab(PROPERTIES_TAB)}>
Edit
</button>
</div>
<div class="panel">
{#if selected === PROPERTIES_TAB}
<ComponentPropertiesPanel {toggleTab} />
{/if}
{#if selected === COMPONENT_SELECTION_TAB}
<ComponentSelectionList {toggleTab} />
{/if}
</div>
{/if}
</div>
<style>
.root {
height: 100%;
display: flex;
flex-direction: column;
padding: 20px 5px 20px 10px;
border-left: solid 1px var(--grey-2);
}
.switcher {
display: flex;
margin: 0px 20px 20px 0px;
}
.switcher > button {
display: inline-block;
border: none;
margin: 0;
padding: 0;
cursor: pointer;
font-size: 18px;
font-weight: 600;
color: var(--grey-5);
margin-right: 20px;
}
.switcher > .selected {
color: var(--ink);
}
.panel {
height: 100%;
}
</style>

View File

@ -1,12 +1,13 @@
<script>
import { onMount } from "svelte"
import { TextArea, DetailSummary, Button } from "@budibase/bbui"
import PropertyGroup from "./PropertyGroup.svelte"
import FlatButtonGroup from "./FlatButtonGroup.svelte"
export let panelDefinition = {}
export let componentInstance = {}
export let componentDefinition = {}
export let onStyleChanged = () => {}
export let onCustomStyleChanged = () => {}
export let onResetStyles = () => {}
let selectedCategory = "normal"
let propGroup = null
@ -39,11 +40,23 @@
properties={panelDefinition[groupName]}
styleCategory={selectedCategory}
{onStyleChanged}
{componentDefinition}
{componentInstance}
open={currentGroup === groupName}
on:open={() => (currentGroup = groupName)} />
{/each}
<DetailSummary
name={`Custom Styles${componentInstance._styles.custom ? ' *' : ''}`}
on:open={() => (currentGroup = 'custom')}
show={currentGroup === 'custom'}
thin>
<div class="custom-styles">
<TextArea
value={componentInstance._styles.custom}
on:change={event => onCustomStyleChanged(event.target.value)}
placeholder="Enter some CSS..." />
</div>
</DetailSummary>
<Button secondary wide on:click={onResetStyles}>Reset Styles</Button>
{:else}
<div class="no-design">
This component doesn't have any design properties.
@ -85,4 +98,10 @@
font-size: var(--font-size-xs);
color: var(--grey-5);
}
.custom-styles :global(textarea) {
font-family: monospace;
min-height: 120px;
font-size: var(--font-size-xs);
}
</style>

View File

@ -98,7 +98,7 @@
$: isOptionsObject = options.every(o => typeof o === "object")
$: selectedOption = isOptionsObject
? options.find(o => o.value === value)
? options.find(o => o.value === value || (o.value === "" && value == null))
: {}
$: if (open && selectMenu) {

View File

@ -6,11 +6,9 @@
import {
readableToRuntimeBinding,
runtimeToReadableBinding,
CAPTURE_VAR_INSIDE_MUSTACHE,
} from "builderStore/replaceBindings"
import { DropdownMenu } from "@budibase/bbui"
import BindingDropdown from "components/userInterface/BindingDropdown.svelte"
import { onMount } from "svelte"
export let label = ""
export let bindable = true
@ -70,8 +68,8 @@
let temp = runtimeToReadableBinding(bindableProperties, value)
return value == null && props.defaultValue !== undefined
? props.defaultValue
return value == null && props.initialValue !== undefined
? props.initialValue
: temp
}

View File

@ -10,21 +10,36 @@
export let onStyleChanged = () => {}
export let open = false
const hasPropChanged = (style, prop) => {
// TODO: replace color picker with one that works better.
// Currently it cannot support null values, so this is a hack which
// prevents the color fields from always being marked as changed
if (!["color", "background", "border-color"].includes(prop.key)) {
if (prop.initialValue !== undefined) {
return style[prop.key] !== prop.initialValue
}
}
return style[prop.key] != null && style[prop.key] !== ""
}
$: style = componentInstance["_styles"][styleCategory] || {}
$: changed = properties.some(prop => hasPropChanged(style, prop))
</script>
<DetailSummary {name} on:open show={open} thin>
<DetailSummary name={`${name}${changed ? ' *' : ''}`} on:open show={open} thin>
{#if open}
<div>
{#each properties as props}
{#each properties as prop}
<PropertyControl
label={props.label}
control={props.control}
key={props.key}
value={style[props.key]}
label={`${prop.label}${hasPropChanged(style, prop) ? ' *' : ''}`}
control={prop.control}
key={prop.key}
value={style[prop.key]}
onChange={(key, value) => onStyleChanged(styleCategory, key, value)}
props={{ ...excludeProps(props, ['control', 'label']) }} />
props={{ ...excludeProps(prop, ['control', 'label']) }} />
{/each}
</div>
{/if}
</DetailSummary>
<style>

View File

@ -41,7 +41,7 @@
{ key: "layoutId", label: "Layout", control: LayoutSelect },
]
const layoutDefinition = [{ key: "title", label: "Title", control: Input }]
const layoutDefinition = []
const canRenderControl = (key, dependsOn) => {
let test = !isEmpty(componentInstance[dependsOn])
@ -119,7 +119,7 @@
{/if}
{/if}
{#if panelDefinition && panelDefinition.length > 0}
{#if !isLayout && panelDefinition && panelDefinition.length > 0}
{#each panelDefinition as definition}
{#if canRenderControl(definition.key, definition.dependsOn)}
<PropertyControl

View File

@ -24,7 +24,7 @@ export const createProps = (componentDefinition, derivedFromProps) => {
const props = {
_id: uuid(),
_component: componentDefinition._component,
_styles: { normal: {}, hover: {}, active: {}, selected: {} },
_styles: { normal: {}, hover: {}, active: {} },
}
const errors = []
@ -75,7 +75,7 @@ export const makePropsSafe = (componentDefinition, props) => {
}
if (!props._styles) {
props._styles = { normal: {}, hover: {}, active: {}, selected: {} }
props._styles = { normal: {}, hover: {}, active: {} }
}
return props

View File

@ -2,18 +2,16 @@ import Input from "./PropertyPanelControls/Input.svelte"
import OptionSelect from "./OptionSelect.svelte"
import FlatButtonGroup from "./FlatButtonGroup.svelte"
import Colorpicker from "@budibase/colorpicker"
/*
TODO: Allow for default values for all properties
*/
export const layout = [
{
label: "Display",
key: "display",
control: OptionSelect,
initialValue: "",
options: [
{ label: "N/A ", value: "N/A" },
{ label: "Choose option", value: "" },
{ label: "Block", value: "block" },
{ label: "Inline Block", value: "inline-block" },
{ label: "Flex", value: "flex" },
{ label: "Inline Flex", value: "inline-flex" },
],
@ -31,15 +29,15 @@ export const layout = [
padding: "0px 5px",
value: "columnReverse",
},
{ icon: "ri-close-line", value: "" },
],
},
{
label: "Justify",
key: "justify-content",
control: OptionSelect,
initialValue: "Flex Start",
options: [
{ label: "", value: "" },
{ label: "Choose option", value: "" },
{ label: "Flex Start", value: "flex-start" },
{ label: "Flex End", value: "flex-end" },
{ label: "Center", value: "center" },
@ -52,8 +50,8 @@ export const layout = [
label: "Align",
key: "align-items",
control: OptionSelect,
initialValue: "Flex Start",
options: [
{ label: "Choose option", value: "" },
{ label: "Flex Start", value: "flex-start" },
{ label: "Flex End", value: "flex-end" },
{ label: "Center", value: "center" },
@ -66,8 +64,9 @@ export const layout = [
key: "flex-wrap",
control: OptionSelect,
options: [
{ label: "wrap", value: "wrap" },
{ label: "no wrap", value: "noWrap" },
{ label: "Choose option", value: "" },
{ label: "Wrap", value: "wrap" },
{ label: "No wrap", value: "nowrap" },
],
},
{
@ -75,6 +74,7 @@ export const layout = [
key: "gap",
control: OptionSelect,
options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" },
{ label: "4px", value: "4px" },
{ label: "8px", value: "8px" },
@ -93,6 +93,7 @@ export const margin = [
key: "margin",
control: OptionSelect,
options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" },
{ label: "4px", value: "4px" },
{ label: "8px", value: "8px" },
@ -112,6 +113,7 @@ export const margin = [
key: "margin-top",
control: OptionSelect,
options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" },
{ label: "4px", value: "4px" },
{ label: "8px", value: "8px" },
@ -131,6 +133,7 @@ export const margin = [
key: "margin-right",
control: OptionSelect,
options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" },
{ label: "4px", value: "4px" },
{ label: "8px", value: "8px" },
@ -150,6 +153,7 @@ export const margin = [
key: "margin-bottom",
control: OptionSelect,
options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" },
{ label: "4px", value: "4px" },
{ label: "8px", value: "8px" },
@ -169,6 +173,7 @@ export const margin = [
key: "margin-left",
control: OptionSelect,
options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" },
{ label: "4px", value: "4px" },
{ label: "8px", value: "8px" },
@ -191,6 +196,7 @@ export const padding = [
key: "padding",
control: OptionSelect,
options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" },
{ label: "4px", value: "4px" },
{ label: "8px", value: "8px" },
@ -208,6 +214,7 @@ export const padding = [
key: "padding-top",
control: OptionSelect,
options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" },
{ label: "4px", value: "4px" },
{ label: "8px", value: "8px" },
@ -225,6 +232,7 @@ export const padding = [
key: "padding-right",
control: OptionSelect,
options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" },
{ label: "4px", value: "4px" },
{ label: "8px", value: "8px" },
@ -242,6 +250,7 @@ export const padding = [
key: "padding-bottom",
control: OptionSelect,
options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" },
{ label: "4px", value: "4px" },
{ label: "8px", value: "8px" },
@ -259,6 +268,7 @@ export const padding = [
key: "padding-left",
control: OptionSelect,
options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0px" },
{ label: "4px", value: "4px" },
{ label: "8px", value: "8px" },
@ -278,8 +288,8 @@ export const size = [
label: "Flex",
key: "flex",
control: OptionSelect,
defaultValue: "0 1 auto",
options: [
{ label: "Choose option", value: "" },
{ label: "Shrink", value: "0 1 auto" },
{ label: "Grow", value: "1 1 auto" },
],
@ -333,9 +343,8 @@ export const position = [
label: "Position",
key: "position",
control: OptionSelect,
initialValue: "None",
options: [
{ label: "None", value: "none" },
{ label: "Choose option", value: "" },
{ label: "Static", value: "static" },
{ label: "Relative", value: "relative" },
{ label: "Fixed", value: "fixed" },
@ -375,7 +384,18 @@ export const position = [
label: "Z-index",
key: "z-index",
control: OptionSelect,
options: ["-9999", "-3", "-2", "-1", "0", "1", "2", "3", "9999"],
options: [
{ label: "Choose option", value: "" },
{ label: "-9999", value: "-9999" },
{ label: "-3", value: "-3" },
{ label: "-2", value: "-2" },
{ label: "-1", value: "-1" },
{ label: "0", value: "0" },
{ label: "1", value: "1" },
{ label: "2", value: "2" },
{ label: "3", value: "3" },
{ label: "9999", value: "9999" },
],
},
]
@ -384,22 +404,22 @@ export const typography = [
label: "Font",
key: "font-family",
control: OptionSelect,
defaultValue: "Arial",
options: [
"Arial",
"Arial Black",
"Cursive",
"Courier",
"Comic Sans MS",
"Helvetica",
"Helvetica Neue",
"Impact",
"Inter",
"Lucida Sans Unicode",
"Roboto",
"Roboto Mono",
"Times New Roman",
"Verdana",
{ label: "Choose option", value: "" },
{ label: "Arial", value: "Arial" },
{ label: "Arial Black", value: "Arial Black" },
{ label: "Cursive", value: "Cursive" },
{ label: "Courier", value: "Courier" },
{ label: "Comic Sans MS", value: "Comic Sans MS" },
{ label: "Helvetica", value: "Helvetica" },
{ label: "Helvetica Neue", value: "Helvetica Neue" },
{ label: "Impact", value: "Impact" },
{ label: "Inter", value: "Inter" },
{ label: "Lucida Sans Unicode", value: "Lucida Sans Unicode" },
{ label: "Roboto", value: "Roboto" },
{ label: "Roboto Mono", value: "Roboto Mono" },
{ label: "Times New Roman", value: "Times New Roman" },
{ label: "Verdana", value: "Verdana" },
],
styleBindingProperty: "font-family",
},
@ -407,25 +427,36 @@ export const typography = [
label: "Weight",
key: "font-weight",
control: OptionSelect,
options: ["200", "300", "400", "500", "600", "700", "800", "900"],
options: [
{ label: "Choose option", value: "" },
{ label: "200", value: "200" },
{ label: "300", value: "300" },
{ label: "400", value: "400" },
{ label: "500", value: "500" },
{ label: "600", value: "600" },
{ label: "700", value: "700" },
{ label: "800", value: "800" },
{ label: "900", value: "900" },
],
},
{
label: "size",
key: "font-size",
control: OptionSelect,
options: [
"8px",
"10px",
"12px",
"14px",
"16px",
"18px",
"20px",
"24px",
"32px",
"48px",
"60px",
"72px",
{ label: "Choose option", value: "" },
{ label: "8px", value: "8px" },
{ label: "10px", value: "10px" },
{ label: "12px", value: "12px" },
{ label: "14px", value: "14px" },
{ label: "16px", value: "16px" },
{ label: "18px", value: "18px" },
{ label: "20px", value: "20px" },
{ label: "24px", value: "24px" },
{ label: "32px", value: "32px" },
{ label: "48px", value: "48px" },
{ label: "60px", value: "60px" },
{ label: "72px", value: "72px" },
],
textAlign: "center",
},
@ -433,13 +464,21 @@ export const typography = [
label: "Line H",
key: "line-height",
control: OptionSelect,
options: ["1", "1.25", "1.5", "1.75", "2", "4"],
options: [
{ label: "Choose option", value: "" },
{ label: "1", value: "1" },
{ label: "1.25", value: "1.25" },
{ label: "1.5", value: "1.5" },
{ label: "1.75", value: "1.75" },
{ label: "2", value: "2" },
{ label: "4", value: "4" },
],
},
{
label: "Color",
key: "color",
control: Colorpicker,
defaultValue: "#000",
initialValue: "#000",
},
{
label: "align",
@ -450,6 +489,7 @@ export const typography = [
{ icon: "ri-align-center", padding: "0px 5px", value: "center" },
{ icon: "ri-align-right", padding: "0px 5px", value: "right" },
{ icon: "ri-align-justify", padding: "0px 5px", value: "justify" },
{ icon: "ri-close-line", value: "" },
],
},
{
@ -460,16 +500,15 @@ export const typography = [
{ text: "BB", value: "uppercase" },
{ text: "Bb", value: "capitalize" },
{ text: "bb", value: "lowercase" },
{ text: "&times;", value: "none" },
{ icon: "ri-close-line", value: "" },
],
},
{
label: "Decoration",
key: "text-decoration-line",
control: OptionSelect,
defaultValue: "None",
options: [
{ label: "None", value: "none" },
{ label: "Choose option", value: "" },
{ label: "Underline", value: "underline" },
{ label: "Overline", value: "overline" },
{ label: "Line-through", value: "line-through" },
@ -483,15 +522,15 @@ export const background = [
label: "Color",
key: "background",
control: Colorpicker,
defaultValue: "#000",
initialValue: "#000",
},
{
label: "Gradient",
key: "background-image",
control: OptionSelect,
defaultValue: "",
options: [
{ label: "Select option", value: "" },
{ label: "Choose option", value: "" },
{ label: "None", value: "none" },
{
label: "Warm Flame",
value: "linear-gradient(45deg, #ff9a9e 0%, #fad0c4 99%, #fad0c4 100%);",
@ -567,7 +606,7 @@ export const background = [
label: "Image",
key: "background",
control: Input,
placeholder: "url",
placeholder: "URL",
},
]
@ -576,8 +615,8 @@ export const border = [
label: "Radius",
key: "border-radius",
control: OptionSelect,
defaultValue: "None",
options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0" },
{ label: "X Small", value: "0.125rem" },
{ label: "Small", value: "0.25rem" },
@ -592,8 +631,8 @@ export const border = [
label: "Width",
key: "border-width",
control: OptionSelect,
defaultValue: "None",
options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0" },
{ label: "X Small", value: "0.5px" },
{ label: "Small", value: "1px" },
@ -606,24 +645,24 @@ export const border = [
label: "Color",
key: "border-color",
control: Colorpicker,
defaultValue: "#000",
initialValue: "#000",
},
{
label: "Style",
key: "border-style",
control: OptionSelect,
defaultValue: "None",
options: [
"None",
"Hidden",
"Dotted",
"Dashed",
"Solid",
"Double",
"Groove",
"Ridge",
"Inset",
"Outset",
{ label: "Choose option", value: "" },
{ label: "None", value: "none" },
{ label: "Hidden", value: "hidden" },
{ label: "Dotted", value: "dotted" },
{ label: "Dashed", value: "dashed" },
{ label: "Solid", value: "solid" },
{ label: "Double", value: "double" },
{ label: "Groove", value: "groove" },
{ label: "Ridge", value: "ridge" },
{ label: "Inset", value: "inset" },
{ label: "Outset", value: "outset" },
],
},
]
@ -634,14 +673,22 @@ export const effects = [
key: "opacity",
control: OptionSelect,
textAlign: "center",
options: ["0", "0.2", "0.4", "0.6", "0.8", "1"],
options: [
{ label: "Choose option", value: "" },
{ label: "0", value: "0" },
{ label: "0.2", value: "0.2" },
{ label: "0.4", value: "0.4" },
{ label: "0.6", value: "0.6" },
{ label: "0.8", value: "0.8" },
{ label: "1", value: "1" },
],
},
{
label: "Rotate",
key: "transform",
control: OptionSelect,
defaultValue: "0",
options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "0" },
{ label: "45 deg", value: "rotate(45deg)" },
{ label: "90 deg", value: "rotate(90deg)" },
@ -657,8 +704,8 @@ export const effects = [
label: "Shadow",
key: "box-shadow",
control: OptionSelect,
defaultValue: "None",
options: [
{ label: "Choose option", value: "" },
{ label: "None", value: "none" },
{ label: "X Small", value: "0 1px 2px 0 rgba(0, 0, 0, 0.05)" },
{
@ -691,19 +738,20 @@ export const transitions = [
key: "transition-property",
control: OptionSelect,
options: [
"None",
"All",
"Background Color",
"Color",
"Font Size",
"Font Weight",
"Height",
"Margin",
"Opacity",
"Padding",
"Rotate",
"Shadow",
"Width",
{ label: "Choose option", value: "" },
{ label: "None", value: "none" },
{ label: "All", value: "all" },
{ label: "Background Color", value: "background color" },
{ label: "Color", value: "color" },
{ label: "Font Size", value: "font size" },
{ label: "Font Weight", value: "font weight" },
{ label: "Height", value: "height" },
{ label: "Margin", value: "margin" },
{ label: "Opacity", value: "opacity" },
{ label: "Padding", value: "padding" },
{ label: "Rotate", value: "rotate" },
{ label: "Shadow", value: "shadow" },
{ label: "Width", value: "width" },
],
},
{
@ -712,13 +760,28 @@ export const transitions = [
control: OptionSelect,
textAlign: "center",
placeholder: "sec",
options: ["0.4s", "0.6s", "0.8s", "1s", "2s", "4s"],
options: [
{ label: "Choose option", value: "" },
{ label: "0.4s", value: "0.4s" },
{ label: "0.6s", value: "0.6s" },
{ label: "0.8s", value: "0.8s" },
{ label: "1s", value: "1s" },
{ label: "2s", value: "2s" },
{ label: "4s", value: "4s" },
],
},
{
label: "Ease",
key: "transition-timing-function:",
key: "transition-timing-function",
control: OptionSelect,
options: ["linear", "ease", "ease-in", "ease-out", "ease-in-out"],
options: [
{ label: "Choose option", value: "" },
{ label: "Linear", value: "linear" },
{ label: "Ease", value: "ease" },
{ label: "Ease in", value: "ease-in" },
{ label: "Ease out", value: "ease-out" },
{ label: "Ease in out", value: "ease-in-out" },
],
},
]

View File

@ -1,7 +1,11 @@
<script>
import { store, backendUiStore, currentAsset } from "builderStore"
import {
store,
backendUiStore,
currentAsset,
selectedComponent,
} from "builderStore"
import { onMount } from "svelte"
import { FrontendTypes } from "constants"
import CurrentItemPreview from "components/userInterface/AppPreview"
import ComponentPropertiesPanel from "components/userInterface/ComponentPropertiesPanel.svelte"
import ComponentSelectionList from "components/userInterface/ComponentSelectionList.svelte"
@ -43,7 +47,7 @@
{/if}
</div>
{#if $store.currentFrontEndType === FrontendTypes.SCREEN || $store.currentFrontEndType === FrontendTypes.LAYOUT}
{#if $selectedComponent != null}
<div class="components-pane">
<ComponentPropertiesPanel />
</div>

View File

@ -9,6 +9,7 @@
setContext("sdk", SDK)
setContext("component", writable({}))
setContext("data", createDataStore())
setContext("screenslot", false)
let loaded = false

View File

@ -8,8 +8,9 @@
export let definition = {}
// Get local data binding context
// Get contexts
const dataContext = getContext("data")
const screenslotContext = getContext("screenslot")
// Create component context
const componentStore = writable({})
@ -20,10 +21,15 @@
$: children = definition._children
$: id = definition._id
$: enrichedProps = enrichProps(definition, $dataContext, $bindingStore)
$: selected = id === $builderStore.selectedComponentId
$: styles = definition._styles
// Allow component selection in the builder preview if we're previewing a
// layout, or we're preview a screen and we're inside the screenslot
$: allowSelection =
$builderStore.previewType === "layout" || screenslotContext
// Update component context
$: componentStore.set({ id, styles: { ...definition._styles, selected } })
$: componentStore.set({ id, styles: { ...styles, id, allowSelection } })
// Gets the component constructor for the specified component
const getComponentConstructor = component => {

View File

@ -1,5 +1,5 @@
<script>
import { getContext } from "svelte"
import { getContext, setContext } from "svelte"
import Router from "svelte-spa-router"
import { routeStore } from "../store"
import Screen from "./Screen.svelte"
@ -7,6 +7,9 @@
const { styleable } = getContext("sdk")
const component = getContext("component")
// Set context flag so components know that we're now inside the screenslot
setContext("screenslot", true)
// Only wrap this as an array to take advantage of svelte keying,
// to ensure the svelte-spa-router is fully remounted when route config
// changes

View File

@ -11,6 +11,7 @@ const loadBudibase = () => {
screen: window["##BUDIBASE_PREVIEW_SCREEN##"],
selectedComponentId: window["##BUDIBASE_SELECTED_COMPONENT_ID##"],
previewId: window["##BUDIBASE_PREVIEW_ID##"],
previewType: window["##BUDIBASE_PREVIEW_TYPE##"],
})
// Create app if one hasn't been created yet

View File

@ -1,8 +1,8 @@
import * as API from "./api"
import { authStore, routeStore, screenStore, bindingStore } from "./store"
import { styleable } from "./utils/styleable"
import { linkable } from "./utils/linkable"
import { getAppId } from "./utils/getAppId"
import { link as linkable } from "svelte-spa-router"
import DataProvider from "./components/DataProvider.svelte"
export default {

View File

@ -7,8 +7,20 @@ const createBuilderStore = () => {
screen: null,
selectedComponentId: null,
previewId: null,
previewType: null,
}
const store = writable(initialState)
const actions = {
selectComponent: id => {
window.dispatchEvent(
new CustomEvent("bb-select-component", { detail: id })
)
},
}
return {
...store,
actions,
}
return writable(initialState)
}
export const builderStore = createBuilderStore()

View File

@ -0,0 +1,10 @@
import { get } from "svelte/store"
import { link } from "svelte-spa-router"
import { builderStore } from "../store"
export const linkable = (node, href) => {
if (get(builderStore).inBuilder) {
return
}
link(node, href)
}

View File

@ -1,47 +1,111 @@
import { get } from "svelte/store"
import { builderStore } from "../store"
const selectedComponentWidth = 2
const selectedComponentColor = "#4285f4"
/**
* Helper to build a CSS string from a style object
* Helper to build a CSS string from a style object.
*/
const buildStyleString = (styles, selected) => {
const buildStyleString = (styleObject, customStyles) => {
let str = ""
if (selected) {
styles.border = "2px solid #0055ff !important"
}
Object.entries(styles).forEach(([style, value]) => {
Object.entries(styleObject).forEach(([style, value]) => {
if (style && value != null) {
str += `${style}: ${value}; `
}
})
return str + (customStyles || "")
}
/**
* Applies styles to enrich the builder preview.
* Applies styles to highlight the selected component, and allows pointer
* events for any selectable components (overriding the blanket ban on pointer
* events in the iframe HTML).
*/
const addBuilderPreviewStyles = (styleString, componentId, selectable) => {
let str = styleString
// Apply extra styles if we're in the builder preview
const state = get(builderStore)
if (state.inBuilder) {
// Allow pointer events and always enable cursor
if (selectable) {
str += ";pointer-events: all !important; cursor: pointer !important;"
}
// Highlighted selected element
if (componentId === state.selectedComponentId) {
str += `;box-shadow: 0 0 0 ${selectedComponentWidth}px ${selectedComponentColor} inset !important;`
}
}
return str
}
/**
* Svelte action to apply correct component styles.
* This also applies handlers for selecting components from the builder preview.
*/
export const styleable = (node, styles = {}) => {
let applyNormalStyles
let applyHoverStyles
let selectComponent
// Kill JS even bubbling
const blockEvent = event => {
event.preventDefault()
event.stopPropagation()
return false
}
// Creates event listeners and applies initial styles
const setupStyles = newStyles => {
const selected = newStyles.selected
const normalStyles = newStyles.normal || {}
const componentId = newStyles.id
const selectable = newStyles.allowSelection
const customStyles = newStyles.custom
const normalStyles = newStyles.normal
const hoverStyles = {
...normalStyles,
...newStyles.hover,
}
applyNormalStyles = () => {
node.style = buildStyleString(normalStyles, selected)
// Applies a style string to a DOM node, enriching it for the builder
// preview
const applyStyles = styleString => {
node.style = addBuilderPreviewStyles(styleString, componentId, selectable)
}
// Applies the "normal" style definition
applyNormalStyles = () => {
applyStyles(buildStyleString(normalStyles, customStyles))
}
// Applies any "hover" styles as well as the base "normal" styles
applyHoverStyles = () => {
node.style = buildStyleString(hoverStyles, selected)
applyStyles(buildStyleString(hoverStyles, customStyles))
}
// Handler to select a component in the builder when clicking it in the
// builder preview
selectComponent = event => {
builderStore.actions.selectComponent(newStyles.id)
return blockEvent(event)
}
// Add listeners to toggle hover styles
node.addEventListener("mouseover", applyHoverStyles)
node.addEventListener("mouseout", applyNormalStyles)
// Add builder preview click listener
if (get(builderStore).inBuilder) {
node.addEventListener("click", selectComponent, false)
// Kill other interaction events
node.addEventListener("mousedown", blockEvent)
node.addEventListener("mouseup", blockEvent)
}
// Apply initial normal styles
applyNormalStyles()
}
@ -50,6 +114,13 @@ export const styleable = (node, styles = {}) => {
const removeListeners = () => {
node.removeEventListener("mouseover", applyHoverStyles)
node.removeEventListener("mouseout", applyNormalStyles)
// Remove builder preview click listener
if (get(builderStore).inBuilder) {
node.removeEventListener("click", selectComponent)
node.removeEventListener("mousedown", blockEvent)
node.removeEventListener("mouseup", blockEvent)
}
}
// Apply initial styles

View File

@ -8,8 +8,8 @@ exports.save = async function(ctx) {
if (!layout.props) {
layout = {
...layout,
...EMPTY_LAYOUT,
...layout,
}
}
@ -34,7 +34,7 @@ exports.destroy = async function(ctx) {
)
).rows.map(element => element.doc.layoutId)
if (layoutsUsedByScreens.includes(layoutId)) {
ctx.throw(400, "Cannot delete a base layout")
ctx.throw(400, "Cannot delete a layout that's being used by a screen")
}
await db.remove(layoutId, layoutRev)

View File

@ -16,7 +16,17 @@ const EMPTY_LAYOUT = {
_id: "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967",
_component: "##builtin/screenslot",
_styles: {
normal: {},
normal: {
flex: "1 1 auto",
display: "flex",
"flex-direction": "column",
"justify-content": "flex-start",
"align-items": "stretch",
"max-width": "100%",
"margin-left": "20px",
"margin-right": "20px",
width: "1400px",
},
hover: {},
active: {},
selected: {},
@ -28,7 +38,16 @@ const EMPTY_LAYOUT = {
_styles: {
active: {},
hover: {},
normal: {},
normal: {
display: "flex",
"flex-direction": "column",
"align-items": "center",
"justify-content": "flex-start",
"margin-right": "auto",
"margin-left": "auto",
"min-height": "100%",
"background-image": "#f5f5f5",
},
selected: {},
},
className: "",

View File

@ -1,11 +1,4 @@
<script>
import { getContext } from "svelte"
const component = getContext("component")
const { styleable } = getContext("sdk")
</script>
<div use:styleable={$component.styles}>
<div>
<h1>Screen Slot</h1>
<span>
The screens that you create will be displayed inside this box.