Refactoring and some styling updates

This commit is contained in:
Dean 2023-05-29 21:19:44 +01:00
parent 0730c15b14
commit e8c6185add
14 changed files with 341 additions and 211 deletions

View File

@ -56,6 +56,8 @@ export default function positionDropdown(element, opts) {
styles.left = anchorBounds.left + anchorBounds.width - elementBounds.width
} else if (align === "right-outside") {
styles.left = anchorBounds.right + offset
} else if (align === "left-outside") {
styles.left = anchorBounds.left - elementBounds.width - offset
} else {
styles.left = anchorBounds.left
}

View File

@ -21,7 +21,6 @@
}
export function hide() {
console.log("CLOSE")
if (!visible) {
return
}

View File

@ -12,6 +12,7 @@
export let emphasized = false
export let onTop = false
export let size = "M"
export let beforeSwitch = null
let thisSelected = undefined
@ -28,9 +29,14 @@
thisSelected = selected
dispatch("select", thisSelected)
} else if ($tab.title !== thisSelected) {
thisSelected = $tab.title
selected = $tab.title
dispatch("select", thisSelected)
if (typeof beforeSwitch == "function") {
const proceed = beforeSwitch($tab.title)
if (proceed) {
thisSelected = $tab.title
selected = $tab.title
dispatch("select", thisSelected)
}
}
}
if ($tab.title !== thisSelected) {
tab.update(state => {

View File

@ -519,7 +519,7 @@ export const makeStateBinding = key => {
readableBinding: `State.${key}`,
category: "State",
icon: "AutomatedSegment",
display: { name: key }, //no type
display: { name: key },
}
}

View File

@ -69,6 +69,7 @@ const INITIAL_FRONTEND_STATE = {
customTheme: {},
previewDevice: "desktop",
highlightedSettingKey: null,
propertyFocus: null,
builderSidePanel: false,
// URL params
@ -1319,6 +1320,12 @@ export const getFrontendStore = () => {
highlightedSettingKey: key,
}))
},
propertyFocus: key => {
store.update(state => ({
...state,
propertyFocus: key,
}))
},
},
dnd: {
start: component => {

View File

@ -37,7 +37,6 @@
import { TriggerStepID, ActionStepID } from "constants/backend/automations"
import { onMount } from "svelte"
import { cloneDeep } from "lodash/fp"
import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte"
export let block
export let testData
@ -235,18 +234,19 @@
? `steps[${idx - loopBlockCount}].${name}`
: `steps.${idx - loopBlockCount}.${name}`
const runtime = idx === 0 ? `trigger.${name}` : runtimeName
const categoryName =
idx === 0
? "Trigger outputs"
: isLoopBlock
? "Loop Outputs"
: `Step ${idx - loopBlockCount} outputs`
return {
readableBinding: runtime,
runtimeBinding: runtime,
type: value.type,
description: value.description,
icon: bindingIcon,
category:
idx === 0
? "Trigger outputs"
: isLoopBlock
? "Loop Outputs"
: `Step ${idx - loopBlockCount} outputs`,
category: categoryName,
display: {
type: value.type,
name: name,

View File

@ -69,13 +69,15 @@
to: opts.end || editor.state.doc.length,
insert: opts.value,
},
selection: {
anchor: opts.start + opts.value.length,
},
selection: opts.cursor
? {
anchor: opts.start + opts.value.length,
}
: undefined,
})
}
// For handlebars only. Demo
// For handlebars only.
const bindStyle = new MatchDecorator({
regexp: /{{[.\[\]\"#-\w\s\/]*}}/g,
decoration: match => {
@ -117,7 +119,7 @@
...completionKeymap,
indentWithTab,
]
return buildKeymap
return baseMap
}
const buildBaseExtensions = () => {
@ -167,8 +169,6 @@
complete.push(foldGutter())
complete.push(
EditorView.inputHandler.of((view, from, to, insert) => {
console.log({ view, from, to, insert })
if (insert === "$") {
let { text } = view.state.doc.lineAt(from)

View File

@ -1,26 +1,14 @@
import { EditorView } from "@codemirror/view"
// import { insertCompletionText } from "@codemirror/autocomplete"
import { getManifest } from "@budibase/string-templates"
import sanitizeHtml from "sanitize-html"
import { groupBy } from "lodash"
// Really just Javascript and Text
export const EditorModes = {
JS: {
name: "javascript",
json: false,
match: /\$$/,
},
JSON: {
name: "javascript",
json: true,
},
XML: {
name: "xml",
},
SQL: {
name: "sql",
},
Handlebars: {
name: "handlebars",
base: "text/html",
@ -31,7 +19,6 @@ export const EditorModes = {
},
}
// Get a generalised approach to constants in the dataBindings file?
export const SECTIONS = {
HB_HELPER: {
name: "Helper",
@ -108,7 +95,7 @@ export const getDefaultTheme = opts => {
color: "var(--ink)",
},
"& .binding-wrap": {
color: "chartreuse",
color: "var(--spectrum-global-color-blue-700)",
},
},
{ dark }
@ -209,20 +196,15 @@ export const jsAutocomplete = baseCompletions => {
let jsBinding = context.matchBefore(/\$\(\"[\s\w]*/)
let options = baseCompletions || []
if (!jsBinding) {
console.log("leaving")
if (jsBinding) {
return {
from: context.pos,
from: jsBinding.from + 3,
filter: true,
options,
}
}
return {
from: jsBinding.from + 3,
filter: true,
options,
}
return null
}
return coreCompletion
@ -305,30 +287,27 @@ export const insertBinding = (view, from, to, text, mode) => {
}
export const bindingsToCompletions = (bindings, mode) => {
// REFACTOR OUT
const bindingByCategory = groupBy(bindings, "category")
const categoryToIcon = bindings?.reduce((acc, ele) => {
if (ele.icon) {
acc[ele.category] = acc[ele.category] || ele.icon
}
return acc
}, {})
const categoryMeta = bindings?.reduce((acc, ele) => {
acc[ele.category] = acc[ele.category] || {}
// REFACTOR OUT
const categoryToRank = bindings?.reduce((acc, ele) => {
if (ele.icon) {
acc[ele.category] = acc[ele.category] || ele.display.rank
acc[ele.category]["icon"] = acc[ele.category]["icon"] || ele.icon
}
if (ele.display?.rank) {
acc[ele.category]["rank"] = acc[ele.category]["rank"] || ele.display.rank
}
return acc
}, {})
const completions = Object.keys(bindingByCategory).reduce((comps, catKey) => {
// REFACTOR OUT
const { icon, rank } = categoryMeta[catKey] || {}
const bindindSectionHeader = buildSectionHeader(
bindingByCategory.type,
catKey,
categoryToIcon[catKey] || "",
categoryToRank[catKey] || 1
icon || "",
typeof rank == "number" ? rank : 1
)
return [

View File

@ -1,17 +1,13 @@
<script>
import groupBy from "lodash/fp/groupBy"
import {
Search,
Input,
DrawerContent,
Tabs,
Tab,
Body,
Layout,
Button,
ActionButton,
Heading,
Icon,
Popover,
} from "@budibase/bbui"
import { createEventDispatcher, onMount } from "svelte"
import {
@ -23,7 +19,7 @@
readableToRuntimeBinding,
runtimeToReadableBinding,
} from "builderStore/dataBinding"
import { handlebarsCompletions } from "constants/completions"
import { store } from "builderStore"
import { convertToJS } from "@budibase/string-templates"
import { admin } from "stores/portal"
import CodeEditor from "../CodeEditor/CodeEditor.svelte"
@ -37,7 +33,6 @@
jsInsert,
} from "../CodeEditor"
import { getContext } from "svelte"
import BindingPicker from "./BindingPicker.svelte"
const dispatch = createEventDispatcher()
@ -55,57 +50,19 @@
const drawerActions = getContext("drawer-actions")
const bindingDrawerActions = getContext("binding-drawer-actions")
let helpers = handlebarsCompletions()
let getCaretPosition
let insertAtPos
let search = ""
let initialValueJS = typeof value === "string" && value?.startsWith("{{ js ")
let mode = initialValueJS ? "JavaScript" : "Text"
let jsValue = initialValueJS ? value : null
let hbsValue = initialValueJS ? null : value
let sidebar = true
let selectedCategory = null
let popover
let popoverAnchor
let hoverTarget
let targetMode = null
$: usingJS = mode === "JavaScript"
$: searchRgx = new RegExp(search, "ig")
$: categories = Object.entries(groupBy("category", bindings))
$: editorMode = mode == "JavaScript" ? EditorModes.JS : EditorModes.Handlebars
$: bindingCompletions = bindingsToCompletions(bindings, editorMode)
$: bindingIcons = bindings?.reduce((acc, ele) => {
if (ele.icon) {
acc[ele.category] = acc[ele.category] || ele.icon
}
return acc
}, {})
$: categoryIcons = { ...bindingIcons, Helpers: "MagicWand" }
$: filteredCategories = categories
.map(([name, categoryBindings]) => ({
name,
bindings: categoryBindings?.filter(binding => {
return binding.readableBinding.match(searchRgx)
}),
}))
.filter(category => {
return (
category.bindings?.length > 0 &&
(!selectedCategory ? true : selectedCategory === category.name)
)
})
$: filteredHelpers = helpers?.filter(helper => {
return helper.label.match(searchRgx) || helper.description.match(searchRgx)
})
$: categoryNames = getCategoryNames(categories)
const updateValue = val => {
valid = isValid(readableToRuntimeBinding(bindings, val))
if (valid) {
@ -113,14 +70,6 @@
}
}
const getCategoryNames = categories => {
let names = [...categories.map(cat => cat[0])]
if (allowHelpers) {
names.push("Helpers")
}
return names
}
// Adds a JS/HBS helper to the expression
const onSelectHelper = (helper, js) => {
const pos = getCaretPosition()
@ -163,6 +112,18 @@
updateValue(jsValue)
}
const switchMode = () => {
if (targetMode == "Text") {
jsValue = null
updateValue(jsValue)
} else {
hbsValue = null
updateValue(hbsValue)
}
mode = targetMode + ""
targetMode = null
}
const convert = () => {
const runtime = readableToRuntimeBinding(bindings, hbsValue)
const runtimeJs = encodeJSBinding(convertToJS(runtime))
@ -177,84 +138,108 @@
})
</script>
<DrawerContent>
<div class="main">
<Tabs selected={mode} on:select={onChangeMode}>
<Tab title="Text">
<div class="main-content" class:binding-panel={sidebar}>
<div class="editor">
<CodeEditor
value={hbsValue}
on:change={onChangeHBSValue}
bind:getCaretPosition
bind:insertAtPos
completions={[
hbAutocomplete([
...bindingCompletions,
...getHelperCompletions(),
]),
]}
placeholder="Add text, or click the objects on the left to add them to the textbox."
/>
<div class="binding-footer">
<div class="messaging">
{#if !valid}
<p class="syntax-error">
Current Handlebars syntax is invalid, please check the guide
<a href="https://handlebarsjs.com/guide/">here</a>
for more details.
</p>
{/if}
</div>
<div class="actions">
{#if $admin.isDev && allowJS}
<ActionButton secondary>Convert To JS</ActionButton>
{/if}
<ActionButton
secondary
icon={sidebar ? "RailRightClose" : "RailRightOpen"}
on:click={() => {
sidebar = !sidebar
}}
/>
</div>
</div>
</div>
<span class="binding-drawer">
<DrawerContent>
<div class="main">
<Tabs
selected={mode}
on:select={onChangeMode}
beforeSwitch={selectedMode => {
if (selectedMode == mode) {
return true
}
{#if sidebar}
<div class="binding-picker">
<BindingPicker
{bindings}
{allowHelpers}
addHelper={onSelectHelper}
addBinding={onSelectBinding}
mode={editorMode}
/>
</div>
{/if}
</div>
</Tab>
{#if allowJS}
<Tab title="JavaScript">
//Get the current mode value
const currentEditorValue = mode === "JavaScript" ? jsValue : hbsValue
if (currentEditorValue) {
targetMode = selectedMode
return false
}
return true
}}
>
<Tab title="Text">
<div class="main-content" class:binding-panel={sidebar}>
<div class="editor">
<CodeEditor
value={decodeJSBinding(jsValue)}
on:change={onChangeJSValue}
completions={[
jsAutocomplete([
...bindingCompletions,
...getHelperCompletions(),
]),
]}
mode={EditorModes.JS}
bind:getCaretPosition
bind:insertAtPos
/>
<Body size="S">
JavaScript expressions are executed as functions, so ensure that
your expression returns a value.
</Body>
<div class="overlay-wrap">
{#if targetMode}
<div class="mode-overlay">
<div class="prompt-body">
<Heading size="S">
{`Switch to ${targetMode}?`}
</Heading>
<Body>This will discard anything in your text</Body>
<div class="switch-actions">
<Button
secondary
size="S"
on:click={() => {
targetMode = null
}}
>
No - keep text
</Button>
<Button cta size="S" on:click={switchMode}>
Yes - discard text
</Button>
</div>
</div>
</div>
{/if}
<CodeEditor
value={hbsValue}
on:change={onChangeHBSValue}
bind:getCaretPosition
bind:insertAtPos
completions={[
hbAutocomplete([
...bindingCompletions,
...getHelperCompletions(),
]),
]}
placeholder=""
/>
</div>
<div class="binding-footer">
<div class="messaging">
{#if !valid}
<div class="syntax-error">
Current Handlebars syntax is invalid, please check the
guide
<a href="https://handlebarsjs.com/guide/">here</a>
for more details.
</div>
{:else}
<Icon name="FlashOn" />
<div class="messaging-wrap">
<div>
Add available bindings by typing $ or use the menu on
the right
</div>
</div>
{/if}
</div>
<div class="actions">
{#if $admin.isDev && allowJS}
<ActionButton
secondary
on:click={() => {
convert()
targetMode = null
}}
>
Convert To JS
</ActionButton>
{/if}
<ActionButton
secondary
icon={sidebar ? "RailRightClose" : "RailRightOpen"}
on:click={() => {
sidebar = !sidebar
}}
/>
</div>
</div>
</div>
{#if sidebar}
@ -270,32 +255,130 @@
{/if}
</div>
</Tab>
{/if}
<div class="drawer-actions">
<Button
secondary
quiet
on:click={() => {
console.log(drawerActions)
drawerActions.hide()
}}
>
Cancel
</Button>
<Button
cta
on:click={() => {
bindingDrawerActions.save()
}}
>
Save
</Button>
</div>
</Tabs>
</div>
</DrawerContent>
{#if allowJS}
<Tab title="JavaScript">
<div class="main-content" class:binding-panel={sidebar}>
<div class="editor">
<div class="overlay-wrap">
{#if targetMode}
<div class="mode-overlay">
<div class="prompt-body">
<Heading size="S">
{`Switch to ${targetMode}?`}
</Heading>
<Body>This will discard anything in your text</Body>
<div class="switch-actions">
<Button
secondary
size="S"
on:click={() => {
targetMode = null
}}
>
No - keep text
</Button>
<Button cta size="S" on:click={switchMode}>
Yes - discard text
</Button>
</div>
</div>
</div>
{/if}
<CodeEditor
value={decodeJSBinding(jsValue)}
on:change={onChangeJSValue}
completions={[
jsAutocomplete([
...bindingCompletions,
...getHelperCompletions(),
]),
]}
mode={EditorModes.JS}
bind:getCaretPosition
bind:insertAtPos
/>
</div>
<div class="binding-footer">
<div class="messaging">
<Icon name="FlashOn" />
<div class="messaging-wrap">
<div>
Add available bindings by typing $ or use the menu on
the right
</div>
</div>
</div>
<div class="actions">
<ActionButton
secondary
icon={sidebar ? "RailRightClose" : "RailRightOpen"}
on:click={() => {
sidebar = !sidebar
}}
/>
</div>
</div>
</div>
{#if sidebar}
<div class="binding-picker">
<BindingPicker
{bindings}
{allowHelpers}
addHelper={onSelectHelper}
addBinding={onSelectBinding}
mode={editorMode}
/>
</div>
{/if}
</div>
</Tab>
{/if}
<div class="drawer-actions">
<Button
secondary
quiet
on:click={() => {
store.actions.settings.propertyFocus(null)
drawerActions.hide()
}}
>
Cancel
</Button>
<Button
cta
on:click={() => {
bindingDrawerActions.save()
}}
>
Save
</Button>
</div>
</Tabs>
</div>
</DrawerContent>
</span>
<style>
.messaging {
display: flex;
align-items: center;
gap: var(--spacing-m);
min-width: 0;
flex: 1;
}
.messaging-wrap {
overflow: hidden;
}
.messaging-wrap > div {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.binding-drawer :global(.drawer-contents) {
height: unset;
}
.main :global(textarea) {
min-height: 202px !important;
}
@ -323,7 +406,6 @@
}
.syntax-error {
padding-top: var(--spacing-m);
color: var(--red);
font-size: 12px;
}
@ -354,5 +436,36 @@
.editor {
padding: var(--spacing-xl);
padding-bottom: 0px;
min-width: 0;
}
.overlay-wrap {
position: relative;
}
.mode-overlay {
position: absolute;
top: 0;
left: 0;
z-index: 2;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: var(
--spectrum-textfield-m-background-color,
var(--spectrum-global-color-gray-50)
);
border-radius: var(--border-radius-s);
}
.prompt-body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--spacing-l);
}
.prompt-body .switch-actions {
display: flex;
gap: var(--spacing-l);
}
</style>

View File

@ -45,7 +45,6 @@
(!selectedCategory ? true : selectedCategory === category.name)
)
})
$: console.log(filteredCategories)
$: filteredHelpers = helpers?.filter(helper => {
return helper.label.match(searchRgx) || helper.description.match(searchRgx)
})

View File

@ -4,6 +4,9 @@
readableToRuntimeBinding,
runtimeToReadableBinding,
} from "builderStore/dataBinding"
import { store } from "builderStore"
import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
import { createEventDispatcher, setContext } from "svelte"
import { isJSBinding } from "@budibase/string-templates"
@ -20,6 +23,7 @@
export let allowHelpers = true
export let updateOnChange = true
export let drawerLeft
export let key
const dispatch = createEventDispatcher()
let bindingDrawer
@ -32,6 +36,7 @@
const saveBinding = () => {
onChange(tempValue)
store.actions.settings.propertyFocus(null)
onBlur()
bindingDrawer.hide()
}
@ -62,7 +67,13 @@
{updateOnChange}
/>
{#if !disabled}
<div class="icon" on:click={bindingDrawer.show}>
<div
class="icon"
on:click={() => {
store.actions.settings.propertyFocus(key)
bindingDrawer.show()
}}
>
<Icon size="S" name="FlashOn" />
</div>
{/if}

View File

@ -21,6 +21,7 @@
export let componentBindings = []
export let nested = false
export let highlighted = false
export let propertyFocus = false
export let info = null
$: nullishValue = value == null || value === ""
@ -72,6 +73,10 @@
if (highlighted) {
store.actions.settings.highlight(null)
}
// To fix focus 'affect' when property is target of a drawer other actions in the builder.
if (propertyFocus) {
store.actions.settings.propertyFocus(null)
}
})
</script>
@ -79,6 +84,7 @@
class="property-control"
class:wide={!label || labelHidden}
class:highlighted={highlighted && nullishValue}
class:property-focus={propertyFocus}
>
{#if label && !labelHidden}
<div class="label">
@ -125,6 +131,14 @@
background: var(--spectrum-global-color-gray-300);
border-color: var(--spectrum-global-color-static-red-600);
}
.property-control.property-focus :global(input) {
border-color: var(
--spectrum-textfield-m-border-color-down,
var(--spectrum-alias-border-color-mouse-focus)
);
}
.label {
margin-top: 16px;
transform: translateY(-50%);

View File

@ -107,7 +107,6 @@
(inCodeEditor || ["input", "textarea"].indexOf(activeTag) !== -1) &&
e.key !== "Escape"
) {
console.log("KEY PRESS")
return
}
// Key events are always for the selected component

View File

@ -140,6 +140,7 @@
nested={setting.nested}
onChange={val => updateSetting(setting, val)}
highlighted={$store.highlightedSettingKey === setting.key}
propertyFocus={$store.propertyFocus === setting.key}
info={setting.info}
props={{
// Generic settings