Add multiple style improvements to drawers
This commit is contained in:
parent
ca3f464523
commit
0217bac267
|
@ -9,7 +9,7 @@
|
||||||
export let title
|
export let title
|
||||||
export let fillWidth
|
export let fillWidth
|
||||||
export let left = "314px"
|
export let left = "314px"
|
||||||
export let width = "calc(100% - 626px)"
|
export let width = "calc(100% - 648px)"
|
||||||
export let headless = false
|
export let headless = false
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -68,10 +68,7 @@
|
||||||
{#if !headless}
|
{#if !headless}
|
||||||
<header>
|
<header>
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<Heading size="XS">{title}</Heading>
|
{title}
|
||||||
<Body size="S">
|
|
||||||
<slot name="description" />
|
|
||||||
</Body>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<Button secondary quiet on:click={hide}>Cancel</Button>
|
<Button secondary quiet on:click={hide}>Cancel</Button>
|
||||||
|
@ -85,10 +82,6 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.drawer.headless :global(.drawer-contents) {
|
|
||||||
height: calc(40vh + 75px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-m);
|
gap: var(--spacing-m);
|
||||||
|
@ -98,8 +91,11 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
border-top: var(--border-light);
|
border: var(--border-light);
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fillWidth {
|
.fillWidth {
|
||||||
|
@ -112,7 +108,7 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-bottom: var(--border-light);
|
border-bottom: var(--border-light);
|
||||||
padding: var(--spacing-l) var(--spacing-xl);
|
padding: var(--spacing-m) var(--spacing-xl);
|
||||||
gap: var(--spacing-xl);
|
gap: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
<div class="drawer-contents">
|
<script>
|
||||||
|
export let padding = true
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="drawer-contents" class:padding>
|
||||||
<div class:no-sidebar={!$$slots.sidebar} class="container">
|
<div class:no-sidebar={!$$slots.sidebar} class="container">
|
||||||
{#if $$slots.sidebar}
|
{#if $$slots.sidebar}
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
|
@ -13,7 +17,7 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.drawer-contents {
|
.drawer-contents {
|
||||||
height: 40vh;
|
height: 400px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
.container {
|
.container {
|
||||||
|
@ -27,13 +31,15 @@
|
||||||
.sidebar {
|
.sidebar {
|
||||||
border-right: var(--border-light);
|
border-right: var(--border-light);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: var(--spacing-xl);
|
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
.padding .sidebar {
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
}
|
||||||
.sidebar::-webkit-scrollbar {
|
.sidebar::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.main {
|
.padding .main {
|
||||||
padding: var(--spacing-xl);
|
padding: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
.main :global(textarea) {
|
.main :global(textarea) {
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
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 BindingPicker from "components/common/bindings/BindingSidePanel.svelte"
|
||||||
import { BindingHelpers } from "components/common/bindings/utils"
|
import { BindingHelpers } from "components/common/bindings/utils"
|
||||||
import {
|
import {
|
||||||
bindingsToCompletions,
|
bindingsToCompletions,
|
||||||
|
|
|
@ -153,6 +153,8 @@
|
||||||
syntaxHighlighting(oneDarkHighlightStyle, { fallback: true }),
|
syntaxHighlighting(oneDarkHighlightStyle, { fallback: true }),
|
||||||
highlightActiveLineGutter(),
|
highlightActiveLineGutter(),
|
||||||
highlightSpecialChars(),
|
highlightSpecialChars(),
|
||||||
|
lineNumbers(),
|
||||||
|
foldGutter(),
|
||||||
EditorView.lineWrapping,
|
EditorView.lineWrapping,
|
||||||
EditorView.updateListener.of(v => {
|
EditorView.updateListener.of(v => {
|
||||||
const docStr = v.state.doc?.toString()
|
const docStr = v.state.doc?.toString()
|
||||||
|
@ -217,8 +219,6 @@
|
||||||
if (mode.name === "javascript") {
|
if (mode.name === "javascript") {
|
||||||
complete.push(javascript())
|
complete.push(javascript())
|
||||||
complete.push(highlightWhitespace())
|
complete.push(highlightWhitespace())
|
||||||
complete.push(lineNumbers())
|
|
||||||
complete.push(foldGutter())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (placeholder) {
|
if (placeholder) {
|
||||||
|
@ -297,9 +297,14 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.code-editor.handlebars :global(.cm-content) {
|
/* Unify spacing between HBS and JS */
|
||||||
font-family: var(--font-sans);
|
.code-editor :global(.cm-content) {
|
||||||
|
padding: var(--spacing-m) 0;
|
||||||
}
|
}
|
||||||
|
.code-editor {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.code-editor :global(.cm-tooltip.cm-completionInfo) {
|
.code-editor :global(.cm-tooltip.cm-completionInfo) {
|
||||||
padding: var(--spacing-m);
|
padding: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,23 @@ export const getDefaultTheme = opts => {
|
||||||
const { height, resize, dark } = opts
|
const { height, resize, dark } = opts
|
||||||
return EditorView.theme(
|
return EditorView.theme(
|
||||||
{
|
{
|
||||||
|
".cm-gutters": {
|
||||||
|
backgroundColor: "var(--spectrum-global-color-gray-75)",
|
||||||
|
color: "var(--spectrum-global-color-gray-500)",
|
||||||
|
},
|
||||||
|
".cm-activeLineGutter": {
|
||||||
|
backgroundColor: "var(--spectrum-global-color-gray-200)",
|
||||||
|
color: "var(--spectrum-global-color-gray-700)",
|
||||||
|
},
|
||||||
|
".cm-activeLine": {
|
||||||
|
backgroundColor: "var(--spectrum-global-color-gray-75)",
|
||||||
|
},
|
||||||
|
".cm-line": {
|
||||||
|
padding: "0 var(--spacing-s)",
|
||||||
|
},
|
||||||
|
".cm-selectionBackground": {
|
||||||
|
backgroundColor: "var(--spectrum-global-color-gray-200) !important",
|
||||||
|
},
|
||||||
"&.cm-focused .cm-cursor": {
|
"&.cm-focused .cm-cursor": {
|
||||||
borderLeftColor: "var(--spectrum-alias-text-color)",
|
borderLeftColor: "var(--spectrum-alias-text-color)",
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,15 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import {
|
import { DrawerContent, ActionButton, Icon } from "@budibase/bbui"
|
||||||
DrawerContent,
|
import { createEventDispatcher, onMount } from "svelte"
|
||||||
Tabs,
|
|
||||||
Tab,
|
|
||||||
Body,
|
|
||||||
Button,
|
|
||||||
ActionButton,
|
|
||||||
Heading,
|
|
||||||
Icon,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { createEventDispatcher, onMount, getContext } from "svelte"
|
|
||||||
import {
|
import {
|
||||||
isValid,
|
isValid,
|
||||||
decodeJSBinding,
|
decodeJSBinding,
|
||||||
|
@ -21,8 +12,6 @@
|
||||||
readableToRuntimeBinding,
|
readableToRuntimeBinding,
|
||||||
runtimeToReadableBinding,
|
runtimeToReadableBinding,
|
||||||
} from "dataBinding"
|
} from "dataBinding"
|
||||||
|
|
||||||
import { admin } from "stores/portal"
|
|
||||||
import CodeEditor from "../CodeEditor/CodeEditor.svelte"
|
import CodeEditor from "../CodeEditor/CodeEditor.svelte"
|
||||||
import {
|
import {
|
||||||
getHelperCompletions,
|
getHelperCompletions,
|
||||||
|
@ -31,17 +20,15 @@
|
||||||
EditorModes,
|
EditorModes,
|
||||||
bindingsToCompletions,
|
bindingsToCompletions,
|
||||||
} from "../CodeEditor"
|
} from "../CodeEditor"
|
||||||
import BindingPicker from "./BindingPicker.svelte"
|
import BindingSidePanel from "./BindingSidePanel.svelte"
|
||||||
|
import EvaluationSidePanel from "./EvaluationSidePanel.svelte"
|
||||||
import { BindingHelpers } from "./utils"
|
import { BindingHelpers } from "./utils"
|
||||||
import formatHighlight from "json-format-highlight"
|
import formatHighlight from "json-format-highlight"
|
||||||
|
import { capitalise } from "helpers"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let bindings
|
export let bindings
|
||||||
// jsValue/hbsValue are the state of the value that is being built
|
|
||||||
// within this binding panel - the value should not be updated until
|
|
||||||
// the binding panel is saved. This is the default value of the
|
|
||||||
// expression when the binding panel is opened, but shouldn't be updated.
|
|
||||||
export let value = ""
|
export let value = ""
|
||||||
export let valid
|
export let valid
|
||||||
export let allowJS = false
|
export let allowJS = false
|
||||||
|
@ -49,23 +36,32 @@
|
||||||
export let context = null
|
export let context = null
|
||||||
export let autofocusEditor = false
|
export let autofocusEditor = false
|
||||||
|
|
||||||
const drawerActions = getContext("drawer-actions")
|
const Modes = {
|
||||||
const bindingDrawerActions = getContext("binding-drawer-actions")
|
Text: "Text",
|
||||||
|
JavaScript: "JavaScript",
|
||||||
|
}
|
||||||
|
const SidePanels = {
|
||||||
|
Bindings: "FlashOn",
|
||||||
|
Evaluation: "Play",
|
||||||
|
}
|
||||||
|
|
||||||
|
let initialValueJS = value?.startsWith?.("{{ js ")
|
||||||
|
let mode = initialValueJS ? Modes.JavaScript : Modes.Text
|
||||||
|
let sidePanel = null
|
||||||
|
|
||||||
let getCaretPosition
|
let getCaretPosition
|
||||||
let insertAtPos
|
let insertAtPos
|
||||||
let initialValueJS = typeof value === "string" && value?.startsWith("{{ js ")
|
|
||||||
let mode = initialValueJS ? "JavaScript" : "Text"
|
|
||||||
let jsValue = initialValueJS ? value : null
|
let jsValue = initialValueJS ? value : null
|
||||||
let hbsValue = initialValueJS ? null : value
|
let hbsValue = initialValueJS ? null : value
|
||||||
let sidebar = true
|
|
||||||
let targetMode = null
|
let targetMode = null
|
||||||
let expressionResult
|
let expressionResult
|
||||||
|
|
||||||
|
$: editorTabs = allowJS ? [Modes.Text, Modes.JavaScript] : [Modes.Text]
|
||||||
|
$: sideTabs = [SidePanels.Evaluation, SidePanels.Bindings]
|
||||||
$: enrichedBindings = enrichBindings(bindings, context)
|
$: enrichedBindings = enrichBindings(bindings, context)
|
||||||
$: usingJS = mode === "JavaScript"
|
$: usingJS = mode === Modes.JavaScript
|
||||||
$: editorMode =
|
$: editorMode =
|
||||||
mode === "JavaScript" ? EditorModes.JS : EditorModes.Handlebars
|
mode === Modes.JavaScript ? EditorModes.JS : EditorModes.Handlebars
|
||||||
$: bindingCompletions = bindingsToCompletions(enrichedBindings, editorMode)
|
$: bindingCompletions = bindingsToCompletions(enrichedBindings, editorMode)
|
||||||
$: runtimeExpression = readableToRuntimeBinding(enrichedBindings, value)
|
$: runtimeExpression = readableToRuntimeBinding(enrichedBindings, value)
|
||||||
$: expressionResult = processStringSync(runtimeExpression || "", context)
|
$: expressionResult = processStringSync(runtimeExpression || "", context)
|
||||||
|
@ -117,9 +113,13 @@
|
||||||
bindingHelpers.onSelectBinding(js ? jsValue : hbsValue, binding, { js })
|
bindingHelpers.onSelectBinding(js ? jsValue : hbsValue, binding, { js })
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChangeMode = e => {
|
const changeMode = newMode => {
|
||||||
mode = e.detail
|
mode = newMode
|
||||||
updateValue(mode === "JavaScript" ? jsValue : hbsValue)
|
updateValue(newMode === Modes.JavaScript ? jsValue : hbsValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeSidePanel = newSidePanel => {
|
||||||
|
sidePanel = newSidePanel === sidePanel ? null : newSidePanel
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChangeHBSValue = e => {
|
const onChangeHBSValue = e => {
|
||||||
|
@ -153,412 +153,292 @@
|
||||||
onSelectBinding("", { forceJS: true })
|
onSelectBinding("", { forceJS: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
const highlight = json => {
|
|
||||||
// Attempt to parse and then stringify, in case this is valid JSON
|
|
||||||
try {
|
|
||||||
json = JSON.stringify(JSON.parse(json), null, 2)
|
|
||||||
} catch (err) {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatHighlight(json, {
|
|
||||||
keyColor: "#e06c75",
|
|
||||||
numberColor: "#e5c07b",
|
|
||||||
stringColor: "#98c379",
|
|
||||||
trueColor: "#d19a66",
|
|
||||||
falseColor: "#d19a66",
|
|
||||||
nullColor: "#c678dd",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
valid = isValid(readableToRuntimeBinding(enrichedBindings, value))
|
valid = isValid(readableToRuntimeBinding(enrichedBindings, value))
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span class="binding-drawer">
|
<DrawerContent padding={false}>
|
||||||
<DrawerContent>
|
<div class="binding-panel">
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<Tabs
|
<div class="tabs">
|
||||||
selected={mode}
|
<div class="editor-tabs">
|
||||||
on:select={onChangeMode}
|
{#each editorTabs as tab}
|
||||||
beforeSwitch={selectedMode => {
|
<ActionButton
|
||||||
if (selectedMode === mode) {
|
size="M"
|
||||||
return true
|
|
||||||
}
|
|
||||||
const editorValue = usingJS ? decodeJSBinding(jsValue) : hbsValue
|
|
||||||
if (editorValue) {
|
|
||||||
targetMode = selectedMode
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Tab title="Text">
|
|
||||||
<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 binding</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(editorMode),
|
|
||||||
]),
|
|
||||||
]}
|
|
||||||
placeholder=""
|
|
||||||
height="100%"
|
|
||||||
autofocus={autofocusEditor}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{#if expressionResult}
|
|
||||||
<div class="result">
|
|
||||||
{@html highlight(expressionResult)}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<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/" target="_blank">
|
|
||||||
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}
|
|
||||||
<div class="binding-picker">
|
|
||||||
<BindingPicker
|
|
||||||
bindings={enrichedBindings}
|
|
||||||
{allowHelpers}
|
|
||||||
{context}
|
|
||||||
addHelper={onSelectHelper}
|
|
||||||
addBinding={onSelectBinding}
|
|
||||||
mode={editorMode}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</Tab>
|
|
||||||
{#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 binding</Body>
|
|
||||||
<div class="switch-actions">
|
|
||||||
<Button
|
|
||||||
secondary
|
|
||||||
size="S"
|
|
||||||
on:click={() => {
|
|
||||||
targetMode = null
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
No - keep javascript
|
|
||||||
</Button>
|
|
||||||
<Button cta size="S" on:click={switchMode}>
|
|
||||||
Yes - discard javascript
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<CodeEditor
|
|
||||||
value={decodeJSBinding(jsValue)}
|
|
||||||
on:change={onChangeJSValue}
|
|
||||||
completions={[
|
|
||||||
jsAutocomplete([
|
|
||||||
...bindingCompletions,
|
|
||||||
...getHelperCompletions(editorMode),
|
|
||||||
]),
|
|
||||||
]}
|
|
||||||
mode={EditorModes.JS}
|
|
||||||
bind:getCaretPosition
|
|
||||||
bind:insertAtPos
|
|
||||||
height="100%"
|
|
||||||
autofocus={autofocusEditor}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{#if expressionResult}
|
|
||||||
<div class="result">
|
|
||||||
{@html highlight(expressionResult)}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<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={enrichedBindings}
|
|
||||||
{allowHelpers}
|
|
||||||
{context}
|
|
||||||
addHelper={onSelectHelper}
|
|
||||||
addBinding={onSelectBinding}
|
|
||||||
mode={editorMode}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</Tab>
|
|
||||||
{/if}
|
|
||||||
<div class="drawer-actions">
|
|
||||||
{#if typeof drawerActions?.hide === "function" && drawerActions?.headless}
|
|
||||||
<Button
|
|
||||||
secondary
|
|
||||||
quiet
|
quiet
|
||||||
on:click={() => {
|
selected={mode === tab}
|
||||||
drawerActions.hide()
|
on:click={() => changeMode(tab)}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Cancel
|
{capitalise(tab)}
|
||||||
</Button>
|
</ActionButton>
|
||||||
{/if}
|
{/each}
|
||||||
{#if typeof bindingDrawerActions?.save === "function" && drawerActions?.headless}
|
|
||||||
<Button
|
|
||||||
cta
|
|
||||||
disabled={!valid}
|
|
||||||
on:click={() => {
|
|
||||||
bindingDrawerActions.save()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</Tabs>
|
<div class="side-tabs">
|
||||||
|
{#each sideTabs as tab}
|
||||||
|
<ActionButton
|
||||||
|
size="M"
|
||||||
|
quiet
|
||||||
|
selected={sidePanel === tab}
|
||||||
|
on:click={() => changeSidePanel(tab)}
|
||||||
|
>
|
||||||
|
<Icon name={tab} size="S" />
|
||||||
|
</ActionButton>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="editor">
|
||||||
|
{#if mode === Modes.Text}
|
||||||
|
<CodeEditor
|
||||||
|
value={hbsValue}
|
||||||
|
on:change={onChangeHBSValue}
|
||||||
|
bind:getCaretPosition
|
||||||
|
bind:insertAtPos
|
||||||
|
completions={[
|
||||||
|
hbAutocomplete([
|
||||||
|
...bindingCompletions,
|
||||||
|
...getHelperCompletions(editorMode),
|
||||||
|
]),
|
||||||
|
]}
|
||||||
|
height="100%"
|
||||||
|
autofocus={autofocusEditor}
|
||||||
|
placeholder="Add bindings by typing {{ or use the menu on the right"
|
||||||
|
/>
|
||||||
|
{:else if mode === Modes.JavaScript}
|
||||||
|
<CodeEditor
|
||||||
|
value={decodeJSBinding(jsValue)}
|
||||||
|
on:change={onChangeJSValue}
|
||||||
|
completions={[
|
||||||
|
jsAutocomplete([
|
||||||
|
...bindingCompletions,
|
||||||
|
...getHelperCompletions(editorMode),
|
||||||
|
]),
|
||||||
|
]}
|
||||||
|
mode={EditorModes.JS}
|
||||||
|
bind:getCaretPosition
|
||||||
|
bind:insertAtPos
|
||||||
|
height="100%"
|
||||||
|
autofocus={autofocusEditor}
|
||||||
|
placeholder="Add bindings by typing $ or use the menu on the right"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DrawerContent>
|
<div class="side" class:visible={!!sidePanel}>
|
||||||
</span>
|
{#if sidePanel === SidePanels.Bindings}
|
||||||
|
<BindingSidePanel
|
||||||
|
bindings={enrichedBindings}
|
||||||
|
{allowHelpers}
|
||||||
|
{context}
|
||||||
|
addHelper={onSelectHelper}
|
||||||
|
addBinding={onSelectBinding}
|
||||||
|
mode={editorMode}
|
||||||
|
/>
|
||||||
|
{:else if sidePanel === SidePanels.Evaluation}
|
||||||
|
<EvaluationSidePanel {expressionResult} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DrawerContent>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.binding-drawer :global(.container > .main) {
|
.binding-panel {
|
||||||
overflow: hidden;
|
|
||||||
height: 100%;
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.binding-drawer :global(.container > .main > .main) {
|
|
||||||
overflow: hidden;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.binding-drawer :global(.spectrum-Tabs-content) {
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.binding-drawer :global(.spectrum-Tabs-content > div),
|
|
||||||
.binding-drawer :global(.spectrum-Tabs-content > div > div),
|
|
||||||
.binding-drawer :global(.spectrum-Tabs-content .main-content) {
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
.binding-panel,
|
||||||
.binding-drawer .main-content {
|
.tabs {
|
||||||
grid-template-rows: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
.main :global(textarea) {
|
|
||||||
min-height: 202px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-content {
|
|
||||||
padding: var(--spacing-s) var(--spacing-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
.main :global(.spectrum-Tabs div.drawer-actions) {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--spacing-m);
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main :global(.spectrum-Tabs-content),
|
|
||||||
.main :global(.spectrum-Tabs-content .main-content) {
|
|
||||||
margin-top: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main :global(.spectrum-Tabs) {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.syntax-error {
|
|
||||||
color: var(--red);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.syntax-error a {
|
|
||||||
color: var(--red);
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.binding-footer {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
.main-content {
|
.main {
|
||||||
display: grid;
|
flex: 1 1 auto;
|
||||||
grid-template-columns: 1fr;
|
|
||||||
grid-template-rows: 380px;
|
|
||||||
}
|
|
||||||
.main-content.binding-panel {
|
|
||||||
grid-template-columns: 1fr 320px;
|
|
||||||
}
|
|
||||||
.binding-picker {
|
|
||||||
border-left: 2px solid var(--border-light);
|
|
||||||
border-left: var(--border-light);
|
|
||||||
overflow: scroll;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.editor {
|
|
||||||
padding: var(--spacing-xl);
|
|
||||||
min-width: 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--spacing-xl);
|
justify-content: flex-start;
|
||||||
overflow: hidden;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
.overlay-wrap {
|
.side {
|
||||||
position: relative;
|
flex: 0 0 0;
|
||||||
flex: 1;
|
transition: flex 130ms ease-out;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
.mode-overlay {
|
.side.visible {
|
||||||
position: absolute;
|
flex: 0 0 420px;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.binding-drawer :global(.code-editor),
|
/* Tabs */
|
||||||
.binding-drawer :global(.code-editor > div) {
|
.tabs {
|
||||||
|
padding: var(--spacing-m);
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
}
|
||||||
|
.editor-tabs,
|
||||||
|
.side-tabs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
|
.side-tabs :global(.icon) {
|
||||||
|
width: 16px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Editor */
|
||||||
|
.editor {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
.editor :global(.code-editor),
|
||||||
|
.editor :global(.code-editor > div),
|
||||||
|
.editor :global(.cm-editor) {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
.editor :global(.cm-editor) {
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*.binding-drawer :global(.container > .main) {*/
|
||||||
|
/* overflow: hidden;*/
|
||||||
|
/* height: 100%;*/
|
||||||
|
/* padding: 0px;*/
|
||||||
|
/*}*/
|
||||||
|
/*.binding-drawer :global(.container > .main > .main) {*/
|
||||||
|
/* overflow: hidden;*/
|
||||||
|
/* height: 100%;*/
|
||||||
|
/* display: flex;*/
|
||||||
|
/* flex-direction: column;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*.binding-drawer :global(.spectrum-Tabs-content) {*/
|
||||||
|
/* flex: 1;*/
|
||||||
|
/* overflow: hidden;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*.binding-drawer :global(.spectrum-Tabs-content > div),*/
|
||||||
|
/*.binding-drawer :global(.spectrum-Tabs-content > div > div),*/
|
||||||
|
/*.binding-drawer :global(.spectrum-Tabs-content .main-content) {*/
|
||||||
|
/* height: 100%;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*.binding-drawer .main-content {*/
|
||||||
|
/* grid-template-rows: unset;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*.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;*/
|
||||||
|
/*}*/
|
||||||
|
/*.main :global(textarea) {*/
|
||||||
|
/* min-height: 202px !important;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*.main-content {*/
|
||||||
|
/* padding: var(--spacing-s) var(--spacing-xl);*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*.main :global(.spectrum-Tabs div.drawer-actions) {*/
|
||||||
|
/* display: flex;*/
|
||||||
|
/* gap: var(--spacing-m);*/
|
||||||
|
/* margin-left: auto;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*.main :global(.spectrum-Tabs-content),*/
|
||||||
|
/*.main :global(.spectrum-Tabs-content .main-content) {*/
|
||||||
|
/* margin-top: 0px;*/
|
||||||
|
/* padding: 0px;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*.main :global(.spectrum-Tabs) {*/
|
||||||
|
/* display: flex;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*.syntax-error {*/
|
||||||
|
/* color: var(--red);*/
|
||||||
|
/* font-size: 12px;*/
|
||||||
|
/*}*/
|
||||||
|
/*.syntax-error a {*/
|
||||||
|
/* color: var(--red);*/
|
||||||
|
/* text-decoration: underline;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*.binding-footer {*/
|
||||||
|
/* width: 100%;*/
|
||||||
|
/* display: flex;*/
|
||||||
|
/* justify-content: space-between;*/
|
||||||
|
/*}*/
|
||||||
|
/*.main-content {*/
|
||||||
|
/* display: grid;*/
|
||||||
|
/* grid-template-columns: 1fr;*/
|
||||||
|
/* grid-template-rows: 380px;*/
|
||||||
|
/*}*/
|
||||||
|
/*.main-content.binding-panel {*/
|
||||||
|
/* grid-template-columns: 1fr 320px;*/
|
||||||
|
/*}*/
|
||||||
|
/*.binding-picker {*/
|
||||||
|
/* border-left: 2px solid var(--border-light);*/
|
||||||
|
/* border-left: var(--border-light);*/
|
||||||
|
/* overflow: scroll;*/
|
||||||
|
/* height: 100%;*/
|
||||||
|
/*}*/
|
||||||
|
/*.editor {*/
|
||||||
|
/* padding: var(--spacing-xl);*/
|
||||||
|
/* min-width: 0;*/
|
||||||
|
/* display: flex;*/
|
||||||
|
/* flex-direction: column;*/
|
||||||
|
/* gap: var(--spacing-xl);*/
|
||||||
|
/* overflow: hidden;*/
|
||||||
|
/*}*/
|
||||||
|
/*.overlay-wrap {*/
|
||||||
|
/* position: relative;*/
|
||||||
|
/* flex: 1;*/
|
||||||
|
/* overflow: hidden;*/
|
||||||
|
/*}*/
|
||||||
|
/*.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);*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*.binding-drawer :global(.code-editor),*/
|
||||||
|
/*.binding-drawer :global(.code-editor > div) {*/
|
||||||
|
/* height: 100%;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
.result {
|
.result {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -568,10 +448,5 @@
|
||||||
border-radius: var(--border-radius-s);
|
border-radius: var(--border-radius-s);
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
overflow-y: scroll;
|
|
||||||
overflow-x: hidden;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
max-height: 128px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -137,123 +137,129 @@
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
<Layout noPadding gap="S">
|
<div class="binding-side-panel">
|
||||||
{#if selectedCategory}
|
<Layout noPadding gap="S">
|
||||||
<div class="sub-section-back">
|
{#if selectedCategory}
|
||||||
<ActionButton
|
<div class="sub-section-back">
|
||||||
secondary
|
<ActionButton
|
||||||
icon={"ArrowLeft"}
|
secondary
|
||||||
on:click={() => {
|
icon={"ArrowLeft"}
|
||||||
selectedCategory = null
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Back
|
|
||||||
</ActionButton>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if !selectedCategory}
|
|
||||||
<div class="search">
|
|
||||||
<span class="search-input">
|
|
||||||
<Input
|
|
||||||
placeholder={"Search for bindings"}
|
|
||||||
autocomplete="off"
|
|
||||||
bind:value={search}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
|
||||||
class="search-input-icon"
|
|
||||||
on:click={() => {
|
|
||||||
search = null
|
|
||||||
}}
|
|
||||||
class:searching={search}
|
|
||||||
>
|
|
||||||
<Icon name={search ? "Close" : "Search"} />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if !selectedCategory && !search}
|
|
||||||
<ul class="category-list">
|
|
||||||
{#each categoryNames as categoryName}
|
|
||||||
<li
|
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
selectedCategory = categoryName
|
selectedCategory = null
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon name={categoryIcons[categoryName]} />
|
Back
|
||||||
<span class="category-name">{categoryName} </span>
|
</ActionButton>
|
||||||
<span class="category-chevron"><Icon name="ChevronRight" /></span>
|
</div>
|
||||||
</li>
|
{/if}
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if selectedCategory || search}
|
{#if !selectedCategory}
|
||||||
{#each filteredCategories as category}
|
<div class="search">
|
||||||
{#if category.bindings?.length}
|
<span class="search-input">
|
||||||
<div class="sub-section">
|
<Input
|
||||||
<div class="cat-heading">
|
placeholder={"Search for bindings"}
|
||||||
<Icon name={categoryIcons[category.name]} />{category.name}
|
autocomplete="off"
|
||||||
</div>
|
bind:value={search}
|
||||||
<ul>
|
/>
|
||||||
{#each category.bindings as binding}
|
</span>
|
||||||
<li
|
|
||||||
class="binding"
|
<span
|
||||||
on:mouseenter={e => showBindingPopover(binding, e.target)}
|
class="search-input-icon"
|
||||||
on:mouseleave={hidePopover}
|
on:click={() => {
|
||||||
on:click={() => addBinding(binding)}
|
search = null
|
||||||
>
|
}}
|
||||||
<span class="binding__label">
|
class:searching={search}
|
||||||
{#if binding.display?.name}
|
>
|
||||||
{binding.display.name}
|
<Icon name={search ? "Close" : "Search"} />
|
||||||
{:else if binding.fieldSchema?.name}
|
</span>
|
||||||
{binding.fieldSchema?.name}
|
</div>
|
||||||
{:else}
|
{/if}
|
||||||
{binding.readableBinding}
|
|
||||||
{/if}
|
{#if !selectedCategory && !search}
|
||||||
</span>
|
<ul class="category-list">
|
||||||
{#if binding.display?.type || binding.fieldSchema?.type}
|
{#each categoryNames as categoryName}
|
||||||
<span class="binding__typeWrap">
|
<li
|
||||||
<span class="binding__type">
|
on:click={() => {
|
||||||
{binding.display?.type || binding.fieldSchema?.type}
|
selectedCategory = categoryName
|
||||||
</span>
|
}}
|
||||||
|
>
|
||||||
|
<Icon name={categoryIcons[categoryName]} />
|
||||||
|
<span class="category-name">{categoryName} </span>
|
||||||
|
<span class="category-chevron"><Icon name="ChevronRight" /></span>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if selectedCategory || search}
|
||||||
|
{#each filteredCategories as category}
|
||||||
|
{#if category.bindings?.length}
|
||||||
|
<div class="sub-section">
|
||||||
|
<div class="cat-heading">
|
||||||
|
<Icon name={categoryIcons[category.name]} />{category.name}
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
{#each category.bindings as binding}
|
||||||
|
<li
|
||||||
|
class="binding"
|
||||||
|
on:mouseenter={e => showBindingPopover(binding, e.target)}
|
||||||
|
on:mouseleave={hidePopover}
|
||||||
|
on:click={() => addBinding(binding)}
|
||||||
|
>
|
||||||
|
<span class="binding__label">
|
||||||
|
{#if binding.display?.name}
|
||||||
|
{binding.display.name}
|
||||||
|
{:else if binding.fieldSchema?.name}
|
||||||
|
{binding.fieldSchema?.name}
|
||||||
|
{:else}
|
||||||
|
{binding.readableBinding}
|
||||||
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{#if binding.display?.type || binding.fieldSchema?.type}
|
||||||
</li>
|
<span class="binding__typeWrap">
|
||||||
{/each}
|
<span class="binding__type">
|
||||||
</ul>
|
{binding.display?.type || binding.fieldSchema?.type}
|
||||||
</div>
|
</span>
|
||||||
{/if}
|
</span>
|
||||||
{/each}
|
{/if}
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
|
||||||
{#if selectedCategory === "Helpers" || search}
|
{#if selectedCategory === "Helpers" || search}
|
||||||
{#if filteredHelpers?.length}
|
{#if filteredHelpers?.length}
|
||||||
<div class="sub-section">
|
<div class="sub-section">
|
||||||
<div class="cat-heading">Helpers</div>
|
<div class="cat-heading">Helpers</div>
|
||||||
<ul class="helpers">
|
<ul class="helpers">
|
||||||
{#each filteredHelpers as helper}
|
{#each filteredHelpers as helper}
|
||||||
<li
|
<li
|
||||||
class="binding"
|
class="binding"
|
||||||
on:mouseenter={e => showHelperPopover(helper, e.target)}
|
on:mouseenter={e => showHelperPopover(helper, e.target)}
|
||||||
on:mouseleave={hidePopover}
|
on:mouseleave={hidePopover}
|
||||||
on:click={() => addHelper(helper, mode.name === "javascript")}
|
on:click={() => addHelper(helper, mode.name === "javascript")}
|
||||||
>
|
>
|
||||||
<span class="binding__label">{helper.displayText}</span>
|
<span class="binding__label">{helper.displayText}</span>
|
||||||
<span class="binding__typeWrap">
|
<span class="binding__typeWrap">
|
||||||
<span class="binding__type">function</span>
|
<span class="binding__type">function</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
</Layout>
|
||||||
</Layout>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.binding-side-panel {
|
||||||
|
border-left: var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
.search :global(input) {
|
.search :global(input) {
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
|
@ -91,11 +91,7 @@
|
||||||
bind:this={bindingDrawer}
|
bind:this={bindingDrawer}
|
||||||
{title}
|
{title}
|
||||||
left={drawerLeft}
|
left={drawerLeft}
|
||||||
headless
|
|
||||||
>
|
>
|
||||||
<svelte:fragment slot="description">
|
|
||||||
Add the objects on the left to enrich your text.
|
|
||||||
</svelte:fragment>
|
|
||||||
<Button cta slot="buttons" disabled={!valid} on:click={saveBinding}>
|
<Button cta slot="buttons" disabled={!valid} on:click={saveBinding}>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
<script>
|
||||||
|
import formatHighlight from "json-format-highlight"
|
||||||
|
import { Icon, notifications } from "@budibase/bbui"
|
||||||
|
import { copyToClipboard } from "@budibase/bbui/helpers"
|
||||||
|
|
||||||
|
export let expressionResult
|
||||||
|
|
||||||
|
$: error = expressionResult === "Error while executing JS"
|
||||||
|
$: empty = expressionResult == null || expressionResult === ""
|
||||||
|
$: success = !error && !empty
|
||||||
|
|
||||||
|
const highlight = json => {
|
||||||
|
// Attempt to parse and then stringify, in case this is valid JSON
|
||||||
|
try {
|
||||||
|
json = JSON.stringify(JSON.parse(json), null, 2)
|
||||||
|
} catch (err) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatHighlight(json, {
|
||||||
|
keyColor: "#e06c75",
|
||||||
|
numberColor: "#e5c07b",
|
||||||
|
stringColor: "#98c379",
|
||||||
|
trueColor: "#d19a66",
|
||||||
|
falseColor: "#d19a66",
|
||||||
|
nullColor: "#c678dd",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const copy = () => {
|
||||||
|
let clipboardVal = expressionResult
|
||||||
|
if (typeof clipboardVal === "object") {
|
||||||
|
clipboardVal = JSON.stringify(clipboardVal, null, 2)
|
||||||
|
}
|
||||||
|
copyToClipboard(clipboardVal)
|
||||||
|
notifications.success("Value copied to clipboard")
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="evaluation-side-panel">
|
||||||
|
<div class="header" class:success class:error>
|
||||||
|
<div class="header-content">
|
||||||
|
{#if success}
|
||||||
|
<Icon
|
||||||
|
name="CheckmarkCircle"
|
||||||
|
color="var(--spectrum-global-color-green-600)"
|
||||||
|
/>
|
||||||
|
<span>Success</span>
|
||||||
|
<Icon name="Copy" hoverable on:click={copy} />
|
||||||
|
{:else if error}
|
||||||
|
<Icon name="Alert" color="var(--spectrum-global-color-red-600)" />
|
||||||
|
<span> Error </span>
|
||||||
|
<Icon name="Copy" hoverable on:click={copy} />
|
||||||
|
{:else}
|
||||||
|
<span>Run</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="body">
|
||||||
|
{#if expressionResult}
|
||||||
|
{@html highlight(expressionResult)}
|
||||||
|
{:else}
|
||||||
|
Your expression will be evaluated here
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.evaluation-side-panel {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
border-left: var(--border-light);
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
padding: var(--spacing-m);
|
||||||
|
flex: 0 0 auto;
|
||||||
|
position: relative;
|
||||||
|
border-bottom: var(--border-light);
|
||||||
|
}
|
||||||
|
.header-content {
|
||||||
|
height: var(--spectrum-alias-item-height-m);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 2;
|
||||||
|
position: relative;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
.header-content span {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
.header.success::before,
|
||||||
|
.header.error::before {
|
||||||
|
content: "";
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
opacity: 10%;
|
||||||
|
}
|
||||||
|
.header.success::before {
|
||||||
|
background: var(--spectrum-global-color-green-600);
|
||||||
|
}
|
||||||
|
.header.error::before {
|
||||||
|
background: var(--spectrum-global-color-red-400);
|
||||||
|
}
|
||||||
|
.body {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
padding: var(--spacing-m);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 12px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
overflow-x: hidden;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue