Merge pull request #972 from Budibase/custom-css
Custom CSS, design panel improvements and allow selecting components from preview
This commit is contained in:
commit
239f27ccee
|
@ -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 => {
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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,21 +30,13 @@
|
|||
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) {
|
||||
loadBudibase()
|
||||
}
|
||||
}
|
||||
|
||||
// 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"))
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -10,20 +10,33 @@
|
|||
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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: "×", 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" },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
setContext("sdk", SDK)
|
||||
setContext("component", writable({}))
|
||||
setContext("data", createDataStore())
|
||||
setContext("screenslot", false)
|
||||
|
||||
let loaded = false
|
||||
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue