Enable selecting components from the builder preview and apply any custom styles
This commit is contained in:
parent
d25fd8b625
commit
62fecd3a3c
|
@ -9,6 +9,7 @@
|
||||||
setContext("sdk", SDK)
|
setContext("sdk", SDK)
|
||||||
setContext("component", writable({}))
|
setContext("component", writable({}))
|
||||||
setContext("data", createDataStore())
|
setContext("data", createDataStore())
|
||||||
|
setContext("screenslot", false)
|
||||||
|
|
||||||
let loaded = false
|
let loaded = false
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,9 @@
|
||||||
|
|
||||||
export let definition = {}
|
export let definition = {}
|
||||||
|
|
||||||
// Get local data binding context
|
// Get contexts
|
||||||
const dataContext = getContext("data")
|
const dataContext = getContext("data")
|
||||||
|
const screenslotContext = getContext("screenslot")
|
||||||
|
|
||||||
// Create component context
|
// Create component context
|
||||||
const componentStore = writable({})
|
const componentStore = writable({})
|
||||||
|
@ -20,10 +21,15 @@
|
||||||
$: children = definition._children
|
$: children = definition._children
|
||||||
$: id = definition._id
|
$: id = definition._id
|
||||||
$: enrichedProps = enrichProps(definition, $dataContext, $bindingStore)
|
$: 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
|
// 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
|
// Gets the component constructor for the specified component
|
||||||
const getComponentConstructor = component => {
|
const getComponentConstructor = component => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext, setContext } from "svelte"
|
||||||
import Router from "svelte-spa-router"
|
import Router from "svelte-spa-router"
|
||||||
import { routeStore } from "../store"
|
import { routeStore } from "../store"
|
||||||
import Screen from "./Screen.svelte"
|
import Screen from "./Screen.svelte"
|
||||||
|
@ -7,6 +7,9 @@
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable } = getContext("sdk")
|
||||||
const component = getContext("component")
|
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,
|
// Only wrap this as an array to take advantage of svelte keying,
|
||||||
// to ensure the svelte-spa-router is fully remounted when route config
|
// to ensure the svelte-spa-router is fully remounted when route config
|
||||||
// changes
|
// changes
|
||||||
|
|
|
@ -1,49 +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 = (styleObject, customStyles, selected) => {
|
const buildStyleString = (styleObject, customStyles) => {
|
||||||
let str = ""
|
let str = ""
|
||||||
Object.entries(styleObject).forEach(([style, value]) => {
|
Object.entries(styleObject).forEach(([style, value]) => {
|
||||||
if (style && value != null) {
|
if (style && value != null) {
|
||||||
str += `${style}: ${value}; `
|
str += `${style}: ${value}; `
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
str += customStyles || ""
|
return str + (customStyles || "")
|
||||||
if (selected) {
|
}
|
||||||
str += ";border: 2px solid #0055ff !important;"
|
|
||||||
|
/**
|
||||||
|
* 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
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Svelte action to apply correct component styles.
|
* Svelte action to apply correct component styles.
|
||||||
|
* This also applies handlers for selecting components from the builder preview.
|
||||||
*/
|
*/
|
||||||
export const styleable = (node, styles = {}) => {
|
export const styleable = (node, styles = {}) => {
|
||||||
let applyNormalStyles
|
let applyNormalStyles
|
||||||
let applyHoverStyles
|
let applyHoverStyles
|
||||||
|
let selectComponent
|
||||||
|
|
||||||
|
// Kill JS even bubbling
|
||||||
|
const blockEvent = event => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Creates event listeners and applies initial styles
|
// Creates event listeners and applies initial styles
|
||||||
const setupStyles = newStyles => {
|
const setupStyles = newStyles => {
|
||||||
const selected = newStyles.selected
|
const componentId = newStyles.id
|
||||||
|
const selectable = newStyles.allowSelection
|
||||||
const customStyles = newStyles.custom
|
const customStyles = newStyles.custom
|
||||||
const normalStyles = newStyles.normal || {}
|
const normalStyles = newStyles.normal
|
||||||
const hoverStyles = {
|
const hoverStyles = {
|
||||||
...normalStyles,
|
...normalStyles,
|
||||||
...newStyles.hover,
|
...newStyles.hover,
|
||||||
}
|
}
|
||||||
|
|
||||||
applyNormalStyles = () => {
|
// Applies a style string to a DOM node, enriching it for the builder
|
||||||
node.style = buildStyleString(normalStyles, customStyles, selected)
|
// 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 = () => {
|
applyHoverStyles = () => {
|
||||||
node.style = buildStyleString(hoverStyles, customStyles, 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
|
// Add listeners to toggle hover styles
|
||||||
node.addEventListener("mouseover", applyHoverStyles)
|
node.addEventListener("mouseover", applyHoverStyles)
|
||||||
node.addEventListener("mouseout", applyNormalStyles)
|
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
|
// Apply initial normal styles
|
||||||
applyNormalStyles()
|
applyNormalStyles()
|
||||||
}
|
}
|
||||||
|
@ -52,6 +114,13 @@ export const styleable = (node, styles = {}) => {
|
||||||
const removeListeners = () => {
|
const removeListeners = () => {
|
||||||
node.removeEventListener("mouseover", applyHoverStyles)
|
node.removeEventListener("mouseover", applyHoverStyles)
|
||||||
node.removeEventListener("mouseout", applyNormalStyles)
|
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
|
// Apply initial styles
|
||||||
|
|
Loading…
Reference in New Issue