From 8a31cc2ff743042f27dcc3faa02d0ed2cd29fec0 Mon Sep 17 00:00:00 2001 From: Dean <deanhannigan@gmail.com> Date: Tue, 13 Aug 2024 11:07:00 +0100 Subject: [PATCH] Bug fixes for bindings panel and code editor --- .../SetupPanel/RowSelectorTypes.svelte | 17 ++- .../common/CodeEditor/CodeEditor.svelte | 109 +++++++++++------- .../common/bindings/BindingPanel.svelte | 13 +++ .../common/bindings/DrawerBindableSlot.svelte | 12 +- 4 files changed, 96 insertions(+), 55 deletions(-) diff --git a/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte b/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte index ee9fd12c0c..7717054a92 100644 --- a/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte +++ b/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte @@ -13,6 +13,10 @@ import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte" import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte" import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte" + import { + readableToRuntimeBinding, + runtimeToReadableBinding, + } from "dataBinding" export let onChange export let field @@ -30,6 +34,8 @@ return clone }) + $: readableValue = runtimeToReadableBinding(parsedBindings, fieldData) + let attachmentTypes = [ FieldType.ATTACHMENTS, FieldType.ATTACHMENT_SINGLE, @@ -132,11 +138,12 @@ /> {:else if schema.type === "longform"} <TextArea - value={fieldData} + value={readableValue} + bindings={parsedBindings} on:change={e => onChange({ row: { - [field]: e.detail, + [field]: readableToRuntimeBinding(parsedBindings, e.detail), }, })} /> @@ -144,11 +151,11 @@ <span> <div class="field-wrap json-field"> <CodeEditor - value={fieldData} - on:change={e => { + value={readableValue} + on:blur={e => { onChange({ row: { - [field]: e.detail, + [field]: readableToRuntimeBinding(parsedBindings, e.detail), }, }) }} diff --git a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte index 0102d8f7a9..c3e4835f96 100644 --- a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte +++ b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte @@ -1,6 +1,6 @@ <script> import { Label } from "@budibase/bbui" - import { onMount, createEventDispatcher } from "svelte" + import { onMount, createEventDispatcher, onDestroy } from "svelte" import { FIND_ANY_HBS_REGEX } from "@budibase/string-templates" import { @@ -58,6 +58,64 @@ const dispatch = createEventDispatcher() + let textarea + let editor + let mounted = false + let isEditorInitialised = false + let queuedRefresh = false + + // Theming! + let currentTheme = $themeStore?.theme + let isDark = !currentTheme.includes("light") + let themeConfig = new Compartment() + + $: { + if (autofocus && isEditorInitialised) { + editor.focus() + } + } + + // Init when all elements are ready + $: if (mounted && !isEditorInitialised) { + isEditorInitialised = true + initEditor() + } + + // Theme change + $: if (mounted && isEditorInitialised && $themeStore?.theme) { + if (currentTheme != $themeStore?.theme) { + currentTheme = $themeStore?.theme + isDark = !currentTheme.includes("light") + + // Issue theme compartment update + editor.dispatch({ + effects: themeConfig.reconfigure([...(isDark ? [oneDark] : [])]), + }) + } + } + + // Wait to try and gracefully replace + $: refresh(value, isEditorInitialised, mounted) + + /** + * Will refresh the editor contents only after + * it has been fully initialised + * @param value {string} the editor value + */ + const refresh = (value, initialised, mounted) => { + if (!initialised || !mounted) { + queuedRefresh = true + return + } + + if (editor.state.doc.toString() !== value || queuedRefresh) { + editor.dispatch({ + changes: { from: 0, to: editor.state.doc.length, insert: value }, + }) + queuedRefresh = false + } + } + // Export a function to expose caret position export const getCaretPosition = () => { const selection_range = editor.state.selection.ranges[0] @@ -132,11 +190,6 @@ } ) - // Theming! - let currentTheme = $themeStore?.theme - let isDark = !currentTheme.includes("light") - let themeConfig = new Compartment() - const indentWithTabCustom = { key: "Tab", run: view => { @@ -253,6 +306,11 @@ lineNumbers(), foldGutter(), keymap.of(buildKeymap()), + EditorView.domEventHandlers({ + blur: () => { + dispatch("blur", editor.state.doc.toString()) + }, + }), EditorView.updateListener.of(v => { const docStr = v.state.doc?.toString() if (docStr === value) { @@ -266,11 +324,6 @@ return complete } - let textarea - let editor - let mounted = false - let isEditorInitialised = false - const initEditor = () => { const baseExtensions = buildBaseExtensions() @@ -281,37 +334,13 @@ }) } - $: { - if (autofocus && isEditorInitialised) { - editor.focus() - } - } - - // Init when all elements are ready - $: if (mounted && !isEditorInitialised) { - isEditorInitialised = true - initEditor() - } - - // Theme change - $: if (mounted && isEditorInitialised && $themeStore?.theme) { - if (currentTheme != $themeStore?.theme) { - currentTheme = $themeStore?.theme - isDark = !currentTheme.includes("light") - - // Issue theme compartment update - editor.dispatch({ - effects: themeConfig.reconfigure([...(isDark ? [oneDark] : [])]), - }) - } - } - onMount(async () => { mounted = true - return () => { - if (editor) { - editor.destroy() - } + }) + + onDestroy(() => { + if (editor) { + editor.destroy() } }) </script> diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index d8edf0cbb1..2a656dec6f 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -67,6 +67,10 @@ let targetMode = null let expressionResult let evaluating = false + let mounted = false + + // Value updates must be reprocessed to avoid timing issues + $: processUpdate(value, mounted) $: useSnippets = allowSnippets && !$licensing.isFreePlan $: editorModeOptions = getModeOptions(allowHBS, allowJS) @@ -94,6 +98,13 @@ } } + const processUpdate = value => { + if (!mounted) return + let isJS = value?.startsWith?.("{{ js ") + jsValue = isJS ? value : null + hbsValue = isJS ? null : value + } + const getHBSCompletions = bindingCompletions => { return [ hbAutocomplete([ @@ -266,6 +277,8 @@ // Set the initial side panel sidePanel = sidePanelOptions[0] + + mounted = true }) </script> diff --git a/packages/builder/src/components/common/bindings/DrawerBindableSlot.svelte b/packages/builder/src/components/common/bindings/DrawerBindableSlot.svelte index 81c3bca3d3..e41a740229 100644 --- a/packages/builder/src/components/common/bindings/DrawerBindableSlot.svelte +++ b/packages/builder/src/components/common/bindings/DrawerBindableSlot.svelte @@ -20,7 +20,6 @@ export let allowJS = true export let allowHelpers = true export let updateOnChange = true - export let drawerLeft export let type export let schema @@ -170,14 +169,7 @@ <Icon disabled={isJS} size="S" name="Close" /> </div> {:else} - <slot - {label} - {disabled} - readonly={isJS} - value={isJS ? "(JavaScript function)" : readableValue} - {placeholder} - {updateOnChange} - /> + <slot /> {/if} {#if !disabled && type !== "formula" && !disabled && !attachmentTypes.includes(type)} <div @@ -195,7 +187,7 @@ on:drawerShow bind:this={bindingDrawer} title={title ?? placeholder ?? "Bindings"} - left={drawerLeft} + forceModal={true} > <Button cta slot="buttons" on:click={saveBinding}>Save</Button> <svelte:component