diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 7bbd474a92..1f1fb035a4 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -620,6 +620,9 @@ export const getFrontendStore = () => { if (!name || !component) { return } + if (component[name] === value) { + return + } component[name] = value store.update(state => { state.selectedComponentId = component._id diff --git a/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte b/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte index 4001db44b6..8e3e2dc21d 100644 --- a/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte +++ b/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte @@ -32,15 +32,13 @@ .component("@budibase/standard-components/screenslot") .instanceName("Content Placeholder") .json() - + // Messages that can be sent from the iframe preview to the builder // Budibase events are and initalisation events const MessageTypes = { - IFRAME_LOADED: "iframe-loaded", READY: "ready", ERROR: "error", BUDIBASE: "type", - KEYDOWN: "keydown" } // Construct iframe template @@ -69,7 +67,7 @@ theme: $store.theme, customTheme: $store.customTheme, previewDevice: $store.previewDevice, - messagePassing: $store.clientFeatures.messagePassing + messagePassing: $store.clientFeatures.messagePassing, } // Saving pages and screens to the DB causes them to have _revs. @@ -111,7 +109,6 @@ loading = false error = event.error || "An unknown error occurred" }, - [MessageTypes.KEYDOWN]: handleKeydownEvent } const messageHandler = handlers[message.data.type] || handleBudibaseEvent @@ -122,16 +119,25 @@ window.addEventListener("message", receiveMessage) if (!$store.clientFeatures.messagePassing) { // Legacy - remove in later versions of BB - iframe.contentWindow.addEventListener("ready", () => { - receiveMessage({ data: { type: MessageTypes.READY }}) - }, { once: true }) - iframe.contentWindow.addEventListener("error", event => { - receiveMessage({ data: { type: MessageTypes.ERROR, error: event.detail }}) - }, { once: true }) + iframe.contentWindow.addEventListener( + "ready", + () => { + receiveMessage({ data: { type: MessageTypes.READY } }) + }, + { once: true } + ) + iframe.contentWindow.addEventListener( + "error", + event => { + receiveMessage({ + data: { type: MessageTypes.ERROR, error: event.detail }, + }) + }, + { once: true } + ) // Add listener for events sent by client library in preview iframe.contentWindow.addEventListener("bb-event", handleBudibaseEvent) - iframe.contentWindow.addEventListener("keydown", handleKeydownEvent) - } + } }) // Remove all iframe event listeners on component destroy @@ -140,14 +146,20 @@ window.removeEventListener("message", receiveMessage) if (!$store.clientFeatures.messagePassing) { // Legacy - remove in later versions of BB - iframe.contentWindow.removeEventListener("bb-event", handleBudibaseEvent) - iframe.contentWindow.removeEventListener("keydown", handleKeydownEvent) + iframe.contentWindow.removeEventListener( + "bb-event", + handleBudibaseEvent + ) } } }) const handleBudibaseEvent = event => { const { type, data } = event.data || event.detail + if (!type) { + return + } + if (type === "select-component" && data.id) { store.actions.components.select({ _id: data.id }) } else if (type === "update-prop") { @@ -183,19 +195,6 @@ } } - const handleKeydownEvent = event => { - const { key } = event.data || event - if ( - (key === "Delete" || key === "Backspace") && - selectedComponentId && - ["input", "textarea"].indexOf( - iframe.contentWindow.document.activeElement?.tagName.toLowerCase() - ) === -1 - ) { - confirmDeleteComponent(selectedComponentId) - } - } - const confirmDeleteComponent = componentId => { idToDelete = componentId confirmDeleteDialog.show() diff --git a/packages/builder/src/components/design/AppPreview/iframeTemplate.js b/packages/builder/src/components/design/AppPreview/iframeTemplate.js index 372eac24f7..e8c563db9f 100644 --- a/packages/builder/src/components/design/AppPreview/iframeTemplate.js +++ b/packages/builder/src/components/design/AppPreview/iframeTemplate.js @@ -84,7 +84,6 @@ export default ` if (window.loadBudibase) { window.loadBudibase() document.documentElement.classList.add("loaded") - window.parent.postMessage({ type: "iframe-loaded" }) } else { throw "The client library couldn't be loaded" } @@ -94,10 +93,6 @@ export default ` } window.addEventListener("message", receiveMessage) - window.addEventListener("keydown", evt => { - window.parent.postMessage({ type: "keydown", key: event.key }) - }) - window.parent.postMessage({ type: "ready" }) diff --git a/packages/builder/src/components/design/NavigationPanel/ComponentDropdownMenu.svelte b/packages/builder/src/components/design/NavigationPanel/ComponentDropdownMenu.svelte index c3f51ee5df..06293e4168 100644 --- a/packages/builder/src/components/design/NavigationPanel/ComponentDropdownMenu.svelte +++ b/packages/builder/src/components/design/NavigationPanel/ComponentDropdownMenu.svelte @@ -13,6 +13,12 @@ $: noChildrenAllowed = !component || !definition?.hasChildren $: noPaste = !$store.componentToPaste + // "editable" has been repurposed for inline text editing. + // It remains here for legacy compatibility. + // Future components should define "static": true for indicate they should + // not show a context menu. + $: showMenu = definition?.editable !== false && definition?.static !== true + const moveUpComponent = () => { const asset = get(currentAsset) const parent = findComponentParent(asset.props, component._id) @@ -69,7 +75,7 @@ } -{#if definition?.editable !== false} +{#if showMenu}
diff --git a/packages/builder/src/pages/builder/app/[application]/data/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/index.svelte index 5e5c1e2957..de9a392f9c 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/index.svelte @@ -7,7 +7,7 @@ let modal $: setupComplete = - $datasources.list.find(x => (x._id = "bb_internal")).entities.length > 1 || + $datasources.list.find(x => (x._id = "bb_internal"))?.entities.length > 1 || $datasources.list.length > 1 onMount(() => { diff --git a/packages/client/manifest.json b/packages/client/manifest.json index da1d87c5ab..b9252d076b 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -240,13 +240,15 @@ "name": "Screenslot", "icon": "WebPage", "description": "Contains your app screens", - "editable": false + "static": true }, "button": { "name": "Button", "description": "A basic html button that is ready for styling", "icon": "Button", + "editable": true, "illegalChildren": ["section"], + "showSettingsBar": true, "settings": [ { "type": "text", @@ -255,6 +257,7 @@ }, { "type": "select", + "showInBar": true, "label": "Variant", "key": "type", "options": [ @@ -283,6 +286,7 @@ { "type": "select", "label": "Size", + "showInBar": true, "key": "size", "options": [ { @@ -307,11 +311,18 @@ { "type": "boolean", "label": "Quiet", - "key": "quiet" + "key": "quiet", + "showInBar": true, + "barIcon": "VisibilityOff", + "barTitle": "Quiet variant", + "barSeparator": false }, { "type": "boolean", "label": "Disabled", + "showInBar": true, + "barIcon": "NoEdit", + "barTitle": "Disable button", "key": "disabled" }, { @@ -590,6 +601,7 @@ "icon": "TextParagraph", "illegalChildren": ["section"], "showSettingsBar": true, + "editable": true, "settings": [ { "type": "text", @@ -696,6 +708,7 @@ "description": "A component for displaying heading text", "illegalChildren": ["section"], "showSettingsBar": true, + "editable": true, "settings": [ { "type": "text", @@ -940,6 +953,7 @@ "description": "A basic link component for internal and external links", "icon": "Link", "showSettingsBar": true, + "editable": true, "illegalChildren": ["section"], "settings": [ { @@ -1831,6 +1845,7 @@ "icon": "Text", "illegalChildren": ["section"], "styles": ["size"], + "editable": true, "settings": [ { "type": "field/string", @@ -1869,6 +1884,7 @@ "name": "Number Field", "icon": "123", "styles": ["size"], + "editable": true, "illegalChildren": ["section"], "settings": [ { @@ -1908,6 +1924,7 @@ "name": "Password Field", "icon": "LockClosed", "styles": ["size"], + "editable": true, "illegalChildren": ["section"], "settings": [ { @@ -1947,6 +1964,7 @@ "name": "Options Picker", "icon": "ViewList", "styles": ["size"], + "editable": true, "illegalChildren": ["section"], "settings": [ { @@ -2070,6 +2088,7 @@ "name": "Multi-select Picker", "icon": "ViewList", "styles": ["size"], + "editable": true, "illegalChildren": ["section"], "settings": [ { @@ -2171,6 +2190,7 @@ "booleanfield": { "name": "Checkbox", "icon": "Checkmark", + "editable": true, "illegalChildren": ["section"], "settings": [ { @@ -2234,6 +2254,7 @@ "name": "Rich Text", "icon": "TextParagraph", "styles": ["size"], + "editable": true, "illegalChildren": ["section"], "settings": [ { @@ -2274,6 +2295,7 @@ "name": "Date Picker", "icon": "DateInput", "styles": ["size"], + "editable": true, "illegalChildren": ["section"], "settings": [ { @@ -2319,6 +2341,7 @@ "name": "Attachment", "icon": "Attach", "styles": ["size"], + "editable": true, "illegalChildren": ["section"], "settings": [ { @@ -2348,6 +2371,7 @@ "name": "Relationship Picker", "icon": "TaskList", "styles": ["size"], + "editable": true, "illegalChildren": ["section"], "settings": [ { diff --git a/packages/client/src/components/ClientApp.svelte b/packages/client/src/components/ClientApp.svelte index cc16570992..119a26800f 100644 --- a/packages/client/src/components/ClientApp.svelte +++ b/packages/client/src/components/ClientApp.svelte @@ -1,5 +1,5 @@ -{#key renderKey} - {#if constructor && settings && (visible || inSelectedPath)} - - -
- - {#if children.length} - {#each children as child (child._id)} - - {/each} - {:else if emptyState} - - {:else if isBlock} - - {/if} - -
- {/if} -{/key} +{#if constructor && cachedSettings && (visible || inSelectedPath)} + + +
+ + {#if children.length} + {#each children as child (child._id)} + + {/each} + {:else if emptyState} + + {:else if isBlock} + + {/if} + +
+{/if} diff --git a/packages/client/src/components/CustomThemeWrapper.svelte b/packages/client/src/components/CustomThemeWrapper.svelte index 9b466bb7d7..c656b09306 100644 --- a/packages/client/src/components/CustomThemeWrapper.svelte +++ b/packages/client/src/components/CustomThemeWrapper.svelte @@ -79,4 +79,9 @@ scrollbar-color: var(--spectrum-global-color-gray-400) var(--spectrum-alias-background-color-default); } + + /* Remove border when editing contenteditable components */ + :global(*[contenteditable="true"]:focus) { + outline: none; + } diff --git a/packages/client/src/components/app/Button.svelte b/packages/client/src/components/app/Button.svelte index ed5e2e50a6..f00b114b2b 100644 --- a/packages/client/src/components/app/Button.svelte +++ b/packages/client/src/components/app/Button.svelte @@ -2,7 +2,7 @@ import { getContext } from "svelte" import "@spectrum-css/button/dist/index-vars.css" - const { styleable } = getContext("sdk") + const { styleable, builderStore } = getContext("sdk") const component = getContext("component") export let disabled = false @@ -11,16 +11,35 @@ export let size = "M" export let type = "primary" export let quiet = false + + let node + + $: $component.editing && node?.focus() + $: componentText = getComponentText(text, $builderStore, $component) + + const getComponentText = (text, builderState, componentState) => { + if (!builderState.inBuilder || componentState.editing) { + return text || " " + } + return text || componentState.name || "Placeholder text" + } + + const updateText = e => { + builderStore.actions.updateProp("text", e.target.textContent) + }