diff --git a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte index 88661467be..90188d3094 100644 --- a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte +++ b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte @@ -40,20 +40,20 @@ indentMore, indentLess, } from "@codemirror/commands" - import { Compartment } from "@codemirror/state" + import { Compartment, EditorState } from "@codemirror/state" import { javascript } from "@codemirror/lang-javascript" import { EditorModes } from "./" import { themeStore } from "stores/portal" export let label export let completions = [] - export let resize = "none" export let mode = EditorModes.Handlebars export let value = "" export let placeholder = null export let autocompleteEnabled = true export let autofocus = false export let jsBindingWrapping = true + export let readonly = false // Export a function to expose caret position export const getCaretPosition = () => { @@ -143,32 +143,21 @@ const buildBaseExtensions = () => { return [ ...(mode.name === "handlebars" ? [plugin] : []), - history(), drawSelection(), dropCursor(), bracketMatching(), closeBrackets(), - highlightActiveLine(), syntaxHighlighting(oneDarkHighlightStyle, { fallback: true }), - highlightActiveLineGutter(), highlightSpecialChars(), - lineNumbers(), - foldGutter(), EditorView.lineWrapping, - EditorView.updateListener.of(v => { - const docStr = v.state.doc?.toString() - if (docStr === value) { - return - } - dispatch("change", docStr) - }), - keymap.of(buildKeymap()), themeConfig.of([...(isDark ? [oneDark] : [])]), ] } + // None of this is reactive, but it never has been, so we just assume most + // config flags aren't changed at runtime const buildExtensions = base => { - const complete = [...base] + let complete = [...base] if (autocompleteEnabled) { complete.push( @@ -210,12 +199,36 @@ if (mode.name === "javascript") { complete.push(javascript()) - complete.push(highlightWhitespace()) + if (!readonly) { + complete.push(highlightWhitespace()) + } } if (placeholder) { complete.push(placeholderFn(placeholder)) } + + if (readonly) { + complete.push(EditorState.readOnly.of(true)) + } else { + complete = [ + ...complete, + history(), + highlightActiveLine(), + highlightActiveLineGutter(), + lineNumbers(), + foldGutter(), + keymap.of(buildKeymap()), + EditorView.updateListener.of(v => { + const docStr = v.state.doc?.toString() + if (docStr === value) { + return + } + dispatch("change", docStr) + }), + ] + } + return complete } @@ -301,7 +314,6 @@ /* Active line */ .code-editor :global(.cm-line) { - height: 16px; padding: 0 var(--spacing-s); color: var(--spectrum-alias-text-color); } @@ -319,6 +331,9 @@ background: var(--spectrum-global-color-gray-100) !important; z-index: -2; } + .code-editor :global(.cm-highlightSpace:before) { + color: var(--spectrum-global-color-gray-500); + } /* Code selection */ .code-editor :global(.cm-selectionBackground) { diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index b40ce1aa6a..8951ba1207 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -25,6 +25,7 @@ } from "../CodeEditor" import BindingSidePanel from "./BindingSidePanel.svelte" import EvaluationSidePanel from "./EvaluationSidePanel.svelte" + import SnippetSidePanel from "./SnippetSidePanel.svelte" import { BindingHelpers } from "./utils" import formatHighlight from "json-format-highlight" import { capitalise } from "helpers" @@ -38,6 +39,7 @@ export let valid export let allowJS = false export let allowHelpers = true + export let allowSnippets = true export let context = null export let autofocusEditor = false @@ -49,6 +51,7 @@ const SidePanels = { Bindings: "FlashOn", Evaluation: "Play", + Snippets: "Code", } let initialValueJS = value?.startsWith?.("{{ js ") @@ -64,10 +67,8 @@ let evaluating = false $: drawerContext?.modal.subscribe(val => (drawerIsModal = val)) - $: editorTabs = allowJS ? [Modes.Text, Modes.JavaScript] : [Modes.Text] - $: sideTabs = context - ? [SidePanels.Evaluation, SidePanels.Bindings] - : [SidePanels.Bindings] + $: editorModeOptions = allowJS ? [Modes.Text, Modes.JavaScript] : [Modes.Text] + $: sidePanelOptions = getSidePanelOptions(context, allowSnippets, mode) $: enrichedBindings = enrichBindings(bindings, context) $: usingJS = mode === Modes.JavaScript $: editorMode = @@ -77,6 +78,22 @@ $: runtimeExpression = readableToRuntimeBinding(enrichedBindings, value) $: requestUpdateEvaluation(runtimeExpression, context) $: bindingHelpers = new BindingHelpers(getCaretPosition, insertAtPos) + $: { + if (!sidePanelOptions.includes(sidePanel)) { + sidePanel = SidePanels.Bindings + } + } + + const getSidePanelOptions = (context, allowSnippets, mode) => { + let options = [SidePanels.Bindings] + if (context) { + options.unshift(SidePanels.Evaluation) + } + if (allowSnippets && mode === Modes.JavaScript) { + options.push(SidePanels.Snippets) + } + return options + } const debouncedUpdateEvaluation = Utils.debounce((expression, context) => { expressionResult = processStringSync(expression || "", context) @@ -135,11 +152,22 @@ bindingHelpers.onSelectBinding(js ? jsValue : hbsValue, binding, { js }) } + const onSelectSnippet = snippet => { + bindingHelpers.onSelectSnippet(jsValue, snippet) + } + const changeMode = newMode => { if (targetMode || newMode === mode) { return } - if (editorValue) { + + // Get the raw editor value to see if we are abandoning changes + let rawValue = editorValue + if (mode === Modes.JavaScript) { + rawValue = decodeJSBinding(rawValue) + } + + if (rawValue?.length) { targetMode = newMode } else { mode = newMode @@ -178,26 +206,26 @@
- {#each editorTabs as tab} + {#each editorModeOptions as editorMode} changeMode(tab)} + selected={mode === editorMode} + on:click={() => changeMode(editorMode)} > - {capitalise(tab)} + {capitalise(editorMode)} {/each}
- {#each sideTabs as tab} + {#each sidePanelOptions as panel} changeSidePanel(tab)} + selected={sidePanel === panel} + on:click={() => changeSidePanel(panel)} > - + {/each} {#if drawerContext && get(drawerContext.resizable)} @@ -287,6 +315,8 @@ {evaluating} expression={editorValue} /> + {:else if sidePanel === SidePanels.Snippets} + {/if}
diff --git a/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte b/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte index 6a62e7a318..0539c4a8f5 100644 --- a/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte +++ b/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte @@ -1,5 +1,7 @@