Merge pull request #12939 from Budibase/labday/small-ux-improvement-mike-d

A small UX improvement to the automation scripting system
This commit is contained in:
Michael Drury 2024-02-02 17:21:11 +00:00 committed by GitHub
commit 10159e1852
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 117 additions and 95 deletions

View File

@ -15,7 +15,6 @@
Icon, Icon,
Checkbox, Checkbox,
DatePicker, DatePicker,
Detail,
} from "@budibase/bbui" } from "@budibase/bbui"
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte" import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
import { automationStore, selectedAutomation } from "builderStore" import { automationStore, selectedAutomation } from "builderStore"
@ -33,6 +32,8 @@
import Editor from "components/integration/QueryEditor.svelte" import Editor from "components/integration/QueryEditor.svelte"
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte" import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte" import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte"
import BindingPicker from "components/common/bindings/BindingPicker.svelte"
import { BindingHelpers } from "components/common/bindings/utils"
import { import {
bindingsToCompletions, bindingsToCompletions,
hbAutocomplete, hbAutocomplete,
@ -56,7 +57,7 @@
let drawer let drawer
let fillWidth = true let fillWidth = true
let inputData let inputData
let codeBindingOpen = false let insertAtPos, getCaretPosition
$: filters = lookForFilters(schemaProperties) || [] $: filters = lookForFilters(schemaProperties) || []
$: tempFilters = filters $: tempFilters = filters
$: stepId = block.stepId $: stepId = block.stepId
@ -75,6 +76,10 @@
$: isUpdateRow = stepId === ActionStepID.UPDATE_ROW $: isUpdateRow = stepId === ActionStepID.UPDATE_ROW
$: codeMode = $: codeMode =
stepId === "EXECUTE_BASH" ? EditorModes.Handlebars : EditorModes.JS stepId === "EXECUTE_BASH" ? EditorModes.Handlebars : EditorModes.JS
$: bindingsHelpers = new BindingHelpers(getCaretPosition, insertAtPos, {
disableWrapping: true,
})
$: editingJs = codeMode === EditorModes.JS
$: stepCompletions = $: stepCompletions =
codeMode === EditorModes.Handlebars codeMode === EditorModes.Handlebars
@ -539,18 +544,8 @@
/> />
{:else if value.customType === "code"} {:else if value.customType === "code"}
<CodeEditorModal> <CodeEditorModal>
{#if codeMode == EditorModes.JS} <div class:js-editor={editingJs}>
<ActionButton <div class:js-code={editingJs} style="width: 100%">
on:click={() => (codeBindingOpen = !codeBindingOpen)}
quiet
icon={codeBindingOpen ? "ChevronDown" : "ChevronRight"}
>
<Detail size="S">Bindings</Detail>
</ActionButton>
{#if codeBindingOpen}
<pre>{JSON.stringify(bindings, null, 2)}</pre>
{/if}
{/if}
<CodeEditor <CodeEditor
value={inputData[key]} value={inputData[key]}
on:change={e => { on:change={e => {
@ -560,11 +555,13 @@
}} }}
completions={stepCompletions} completions={stepCompletions}
mode={codeMode} mode={codeMode}
autocompleteEnabled={codeMode != EditorModes.JS} autocompleteEnabled={codeMode !== EditorModes.JS}
bind:getCaretPosition
bind:insertAtPos
height={500} height={500}
/> />
<div class="messaging"> <div class="messaging">
{#if codeMode == EditorModes.Handlebars} {#if codeMode === EditorModes.Handlebars}
<Icon name="FlashOn" /> <Icon name="FlashOn" />
<div class="messaging-wrap"> <div class="messaging-wrap">
<div> <div>
@ -575,6 +572,26 @@
</div> </div>
{/if} {/if}
</div> </div>
</div>
{#if editingJs}
<div class="js-binding-picker">
<BindingPicker
{bindings}
allowHelpers={false}
addBinding={binding =>
bindingsHelpers.onSelectBinding(
inputData[key],
binding,
{
js: true,
dontDecode: true,
}
)}
mode="javascript"
/>
</div>
{/if}
</div>
</CodeEditorModal> </CodeEditorModal>
{:else if value.customType === "loopOption"} {:else if value.customType === "loopOption"}
<Select <Select
@ -658,4 +675,20 @@
.test :global(.drawer) { .test :global(.drawer) {
width: 10000px !important; width: 10000px !important;
} }
.js-editor {
display: flex;
flex-direction: row;
flex-grow: 1;
width: 100%;
}
.js-code {
flex: 7;
}
.js-binding-picker {
flex: 3;
margin-top: calc((var(--spacing-xl) * -1) + 1px);
}
</style> </style>

View File

@ -54,6 +54,7 @@
export let placeholder = null export let placeholder = null
export let autocompleteEnabled = true export let autocompleteEnabled = true
export let autofocus = false export let autofocus = false
export let jsBindingWrapping = true
// Export a function to expose caret position // Export a function to expose caret position
export const getCaretPosition = () => { export const getCaretPosition = () => {
@ -187,7 +188,7 @@
) )
complete.push( complete.push(
EditorView.inputHandler.of((view, from, to, insert) => { EditorView.inputHandler.of((view, from, to, insert) => {
if (insert === "$") { if (jsBindingWrapping && insert === "$") {
let { text } = view.state.doc.lineAt(from) let { text } = view.state.doc.lineAt(from)
const left = from ? text.substring(0, from) : "" const left = from ? text.substring(0, from) : ""

View File

@ -286,13 +286,14 @@ export const hbInsert = (value, from, to, text) => {
return parsedInsert return parsedInsert
} }
export function jsInsert(value, from, to, text, { helper } = {}) { export function jsInsert(value, from, to, text, { helper, disableWrapping }) {
let parsedInsert = "" let parsedInsert = ""
const left = from ? value.substring(0, from) : "" const left = from ? value.substring(0, from) : ""
const right = to ? value.substring(to) : "" const right = to ? value.substring(to) : ""
if (disableWrapping) {
if (helper) { parsedInsert = text
} else if (helper) {
parsedInsert = `helpers.${text}()` parsedInsert = `helpers.${text}()`
} else if (!left.includes('$("') || !right.includes('")')) { } else if (!left.includes('$("') || !right.includes('")')) {
parsedInsert = `$("${text}")` parsedInsert = `$("${text}")`

View File

@ -29,10 +29,9 @@
hbAutocomplete, hbAutocomplete,
EditorModes, EditorModes,
bindingsToCompletions, bindingsToCompletions,
hbInsert,
jsInsert,
} from "../CodeEditor" } from "../CodeEditor"
import BindingPicker from "./BindingPicker.svelte" import BindingPicker from "./BindingPicker.svelte"
import { BindingHelpers } from "./utils"
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -60,8 +59,10 @@
let targetMode = null let targetMode = null
$: usingJS = mode === "JavaScript" $: usingJS = mode === "JavaScript"
$: editorMode = mode == "JavaScript" ? EditorModes.JS : EditorModes.Handlebars $: editorMode =
mode === "JavaScript" ? EditorModes.JS : EditorModes.Handlebars
$: bindingCompletions = bindingsToCompletions(bindings, editorMode) $: bindingCompletions = bindingsToCompletions(bindings, editorMode)
$: bindingHelpers = new BindingHelpers(getCaretPosition, insertAtPos)
const updateValue = val => { const updateValue = val => {
valid = isValid(readableToRuntimeBinding(bindings, val)) valid = isValid(readableToRuntimeBinding(bindings, val))
@ -70,31 +71,13 @@
} }
} }
// Adds a JS/HBS helper to the expression
const onSelectHelper = (helper, js) => { const onSelectHelper = (helper, js) => {
const pos = getCaretPosition() bindingHelpers.onSelectHelper(js ? jsValue : hbsValue, helper, { js })
const { start, end } = pos
if (js) {
let js = decodeJSBinding(jsValue)
const insertVal = jsInsert(js, start, end, helper.text, { helper: true })
insertAtPos({ start, end, value: insertVal })
} else {
const insertVal = hbInsert(hbsValue, start, end, helper.text)
insertAtPos({ start, end, value: insertVal })
}
} }
// Adds a data binding to the expression
const onSelectBinding = (binding, { forceJS } = {}) => { const onSelectBinding = (binding, { forceJS } = {}) => {
const { start, end } = getCaretPosition() const js = usingJS || forceJS
if (usingJS || forceJS) { bindingHelpers.onSelectBinding(js ? jsValue : hbsValue, binding, { js })
let js = decodeJSBinding(jsValue)
const insertVal = jsInsert(js, start, end, binding.readableBinding)
insertAtPos({ start, end, value: insertVal })
} else {
const insertVal = hbInsert(hbsValue, start, end, binding.readableBinding)
insertAtPos({ start, end, value: insertVal })
}
} }
const onChangeMode = e => { const onChangeMode = e => {

View File

@ -9,6 +9,7 @@
export let bindings export let bindings
export let mode export let mode
export let allowHelpers export let allowHelpers
export let noPaddingTop = false
let search = "" let search = ""
let popover let popover

View File

@ -1,38 +1,41 @@
export function addHBSBinding(value, caretPos, binding) { import { decodeJSBinding } from "@budibase/string-templates"
binding = typeof binding === "string" ? binding : binding.path import { hbInsert, jsInsert } from "components/common/CodeEditor"
value = value == null ? "" : value
const left = caretPos?.start ? value.substring(0, caretPos.start) : "" export class BindingHelpers {
const right = caretPos?.end ? value.substring(caretPos.end) : "" constructor(getCaretPosition, insertAtPos, { disableWrapping } = {}) {
if (!left.includes("{{") || !right.includes("}}")) { this.getCaretPosition = getCaretPosition
binding = `{{ ${binding} }}` this.insertAtPos = insertAtPos
} this.disableWrapping = disableWrapping
if (caretPos.start) {
value =
value.substring(0, caretPos.start) +
binding +
value.substring(caretPos.end, value.length)
} else {
value += binding
}
return value
} }
export function addJSBinding(value, caretPos, binding, { helper } = {}) { // Adds a JS/HBS helper to the expression
binding = typeof binding === "string" ? binding : binding.path onSelectHelper(value, helper, { js, dontDecode }) {
value = value == null ? "" : value const pos = this.getCaretPosition()
if (!helper) { const { start, end } = pos
binding = `$("${binding}")` if (js) {
const jsVal = dontDecode ? value : decodeJSBinding(value)
const insertVal = jsInsert(jsVal, start, end, helper.text, {
helper: true,
})
this.insertAtPos({ start, end, value: insertVal })
} else { } else {
binding = `helpers.${binding}()` const insertVal = hbInsert(value, start, end, helper.text)
this.insertAtPos({ start, end, value: insertVal })
} }
if (caretPos.start) { }
value =
value.substring(0, caretPos.start) + // Adds a data binding to the expression
binding + onSelectBinding(value, binding, { js, dontDecode }) {
value.substring(caretPos.end, value.length) const { start, end } = this.getCaretPosition()
if (js) {
const jsVal = dontDecode ? value : decodeJSBinding(value)
const insertVal = jsInsert(jsVal, start, end, binding.readableBinding, {
disableWrapping: this.disableWrapping,
})
this.insertAtPos({ start, end, value: insertVal })
} else { } else {
value += binding const insertVal = hbInsert(value, start, end, binding.readableBinding)
this.insertAtPos({ start, end, value: insertVal })
}
} }
return value
} }