Allow in-preview editing of paragraphs and headings

This commit is contained in:
Andrew Kingston 2021-10-28 12:43:31 +01:00
parent 7a6cf5d534
commit 7c16a1423b
8 changed files with 88 additions and 33 deletions

View File

@ -589,6 +589,7 @@
"icon": "TextParagraph",
"illegalChildren": ["section"],
"showSettingsBar": true,
"editable": true,
"settings": [
{
"type": "text",
@ -695,6 +696,7 @@
"description": "A component for displaying heading text",
"illegalChildren": ["section"],
"showSettingsBar": true,
"editable": true,
"settings": [
{
"type": "text",

View File

@ -60,21 +60,32 @@
$: instanceKey = JSON.stringify(rawProps)
$: updateComponentProps(rawProps, instanceKey, $context)
$: selected =
$builderStore.inBuilder &&
$builderStore.selectedComponentId === instance._id
$builderStore.inBuilder && $builderStore.selectedComponentId === id
$: inSelectedPath = $builderStore.selectedComponentPath?.includes(id)
$: evaluateConditions(enrichedSettings?._conditions)
$: componentSettings = { ...enrichedSettings, ...conditionalSettings }
$: renderKey = `${propsHash}-${emptyState}`
$: editable = definition.editable
$: editing = editable && selected && $builderStore.editMode
$: draggable = interactive && !isLayout && !isScreen && !editing
$: droppable = interactive && !isLayout && !isScreen
// Update component context
$: componentStore.set({
id,
children: children.length,
styles: { ...instance._styles, id, empty: emptyState, interactive },
styles: {
...instance._styles,
id,
empty: emptyState,
interactive,
draggable,
editable,
},
empty: emptyState,
selected,
name,
editing,
})
const getRawProps = instance => {
@ -171,10 +182,6 @@
conditionalSettings = result.settingUpdates
visible = nextVisible
}
// Drag and drop helper tags
$: draggable = interactive && !isLayout && !isScreen
$: droppable = interactive && !isLayout && !isScreen
</script>
{#key renderKey}
@ -187,6 +194,7 @@
class:droppable
class:empty
class:interactive
class:editing
data-id={id}
data-name={name}
>
@ -213,4 +221,7 @@
.draggable :global(*:hover) {
cursor: grab;
}
.editing :global(*:hover) {
cursor: auto;
}
</style>

View File

@ -36,21 +36,34 @@
},
}
}
// Convert contenteditable HTML to text and save
const updateText = e => {
const html = e.target.innerHTML
const sanitized = html
.replace(/<\/div><div>/gi, "\n")
.replace(/<div>/gi, "")
.replace(/<\/div>/gi, "")
.replace(/<br>/gi, "")
builderStore.actions.updateProp("text", sanitized)
}
</script>
<h1
<div
contenteditable={$component.editing}
use:styleable={styles}
class:placeholder
class:bold
class:italic
class:underline
class="spectrum-Heading {sizeClass} {alignClass}"
on:blur={$component.editing ? updateText : null}
>
{componentText}
</h1>
</div>
<style>
h1 {
div {
white-space: pre-wrap;
font-weight: 600;
}

View File

@ -35,21 +35,34 @@
},
}
}
// Convert contenteditable HTML to text and save
const updateText = e => {
const html = e.target.innerHTML
const sanitized = html
.replace(/<\/div><div>/gi, "\n")
.replace(/<div>/gi, "")
.replace(/<\/div>/gi, "")
.replace(/<br>/gi, "")
builderStore.actions.updateProp("text", sanitized)
}
</script>
<p
<div
contenteditable={$component.editing}
use:styleable={styles}
class:placeholder
class:bold
class:italic
class:underline
class="spectrum-Body {sizeClass} {alignClass}"
on:blur={$component.editing ? updateText : null}
>
{componentText}
</p>
</div>
<style>
p {
div {
display: inline-block;
white-space: pre-wrap;
margin: 0;

View File

@ -17,10 +17,9 @@
<div
in:fade={{
delay: transition ? 50 : 0,
delay: transition ? 130 : 0,
duration: transition ? 130 : 0,
}}
out:fade={{ duration: transition ? 130 : 0 }}
class="indicator"
class:flipped
class:line

View File

@ -1,11 +1,15 @@
<script>
import { builderStore } from "stores"
import IndicatorSet from "./IndicatorSet.svelte"
$: color = $builderStore.editMode
? "var(--spectrum-global-color-static-green-500)"
: "var(--spectrum-global-color-static-blue-600)"
</script>
<IndicatorSet
componentId={$builderStore.selectedComponentId}
color="var(--spectrum-global-color-static-blue-600)"
{color}
zIndex="910"
transition
/>

View File

@ -1,4 +1,4 @@
import { writable, derived } from "svelte/store"
import { writable, derived, get } from "svelte/store"
import Manifest from "manifest.json"
import { findComponentById, findComponentPathById } from "../utils/components"
import { pingEndUser } from "../api"
@ -18,6 +18,7 @@ const createBuilderStore = () => {
layout: null,
screen: null,
selectedComponentId: null,
editMode: false,
previewId: null,
previewType: null,
selectedPath: [],
@ -54,6 +55,10 @@ const createBuilderStore = () => {
const actions = {
selectComponent: id => {
if (id === get(writableStore).selectedComponentId) {
return
}
writableStore.update(state => ({ ...state, editMode: false }))
dispatchEvent("select-component", { id })
},
updateProp: (prop, value) => {
@ -69,10 +74,7 @@ const createBuilderStore = () => {
pingEndUser()
},
setSelectedPath: path => {
writableStore.update(state => {
state.selectedPath = path
return state
})
writableStore.update(state => ({ ...state, selectedPath: path }))
},
moveComponent: (componentId, destinationComponentId, mode) => {
dispatchEvent("move-component", {
@ -82,10 +84,10 @@ const createBuilderStore = () => {
})
},
setDragging: dragging => {
writableStore.update(state => {
state.isDragging = dragging
return state
})
writableStore.update(state => ({ ...state, isDragging: dragging }))
},
setEditMode: enabled => {
writableStore.update(state => ({ ...state, editMode: enabled }))
},
}
return {

View File

@ -22,12 +22,7 @@ export const styleable = (node, styles = {}) => {
let applyNormalStyles
let applyHoverStyles
let selectComponent
// Allow dragging if required
const parent = node.closest(".component")
if (parent && parent.classList.contains("draggable")) {
node.setAttribute("draggable", true)
}
let editComponent
// Creates event listeners and applies initial styles
const setupStyles = (newStyles = {}) => {
@ -46,6 +41,9 @@ export const styleable = (node, styles = {}) => {
...(newStyles.hover || {}),
}
// Allow dragging if required
node.setAttribute("draggable", !!styles.draggable)
// Applies a style string to a DOM node
const applyStyles = styleString => {
node.style = styleString
@ -72,13 +70,25 @@ export const styleable = (node, styles = {}) => {
return false
}
// Handler to start editing a component (if applicable) when double
// clicking in the builder preview
editComponent = event => {
if (newStyles.interactive && newStyles.editable) {
builderStore.actions.setEditMode(true)
}
event.preventDefault()
event.stopPropagation()
return false
}
// Add listeners to toggle hover styles
node.addEventListener("mouseover", applyHoverStyles)
node.addEventListener("mouseout", applyNormalStyles)
// Add builder preview click listener
// Add builder preview listeners
if (get(builderStore).inBuilder) {
node.addEventListener("click", selectComponent, false)
node.addEventListener("dblclick", editComponent, false)
}
// Apply initial normal styles
@ -90,9 +100,10 @@ export const styleable = (node, styles = {}) => {
node.removeEventListener("mouseover", applyHoverStyles)
node.removeEventListener("mouseout", applyNormalStyles)
// Remove builder preview click listener
// Remove builder preview listeners
if (get(builderStore).inBuilder) {
node.removeEventListener("click", selectComponent)
node.removeEventListener("dblclick", editComponent)
}
}