Add multiple style improvements to drawers
This commit is contained in:
parent
ca3f464523
commit
0217bac267
|
@ -9,7 +9,7 @@
|
|||
export let title
|
||||
export let fillWidth
|
||||
export let left = "314px"
|
||||
export let width = "calc(100% - 626px)"
|
||||
export let width = "calc(100% - 648px)"
|
||||
export let headless = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
@ -68,10 +68,7 @@
|
|||
{#if !headless}
|
||||
<header>
|
||||
<div class="text">
|
||||
<Heading size="XS">{title}</Heading>
|
||||
<Body size="S">
|
||||
<slot name="description" />
|
||||
</Body>
|
||||
{title}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<Button secondary quiet on:click={hide}>Cancel</Button>
|
||||
|
@ -85,10 +82,6 @@
|
|||
{/if}
|
||||
|
||||
<style>
|
||||
.drawer.headless :global(.drawer-contents) {
|
||||
height: calc(40vh + 75px);
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
gap: var(--spacing-m);
|
||||
|
@ -98,8 +91,11 @@
|
|||
position: absolute;
|
||||
bottom: 0;
|
||||
background: var(--background);
|
||||
border-top: var(--border-light);
|
||||
border: var(--border-light);
|
||||
z-index: 3;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.fillWidth {
|
||||
|
@ -112,7 +108,7 @@
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: var(--border-light);
|
||||
padding: var(--spacing-l) var(--spacing-xl);
|
||||
padding: var(--spacing-m) 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">
|
||||
{#if $$slots.sidebar}
|
||||
<div class="sidebar">
|
||||
|
@ -13,7 +17,7 @@
|
|||
|
||||
<style>
|
||||
.drawer-contents {
|
||||
height: 40vh;
|
||||
height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.container {
|
||||
|
@ -27,13 +31,15 @@
|
|||
.sidebar {
|
||||
border-right: var(--border-light);
|
||||
overflow: auto;
|
||||
padding: var(--spacing-xl);
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.padding .sidebar {
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
.sidebar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.main {
|
||||
.padding .main {
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
.main :global(textarea) {
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
import Editor from "components/integration/QueryEditor.svelte"
|
||||
import ModalBindableInput from "components/common/bindings/ModalBindableInput.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 {
|
||||
bindingsToCompletions,
|
||||
|
|
|
@ -153,6 +153,8 @@
|
|||
syntaxHighlighting(oneDarkHighlightStyle, { fallback: true }),
|
||||
highlightActiveLineGutter(),
|
||||
highlightSpecialChars(),
|
||||
lineNumbers(),
|
||||
foldGutter(),
|
||||
EditorView.lineWrapping,
|
||||
EditorView.updateListener.of(v => {
|
||||
const docStr = v.state.doc?.toString()
|
||||
|
@ -217,8 +219,6 @@
|
|||
if (mode.name === "javascript") {
|
||||
complete.push(javascript())
|
||||
complete.push(highlightWhitespace())
|
||||
complete.push(lineNumbers())
|
||||
complete.push(foldGutter())
|
||||
}
|
||||
|
||||
if (placeholder) {
|
||||
|
@ -297,9 +297,14 @@
|
|||
</div>
|
||||
|
||||
<style>
|
||||
.code-editor.handlebars :global(.cm-content) {
|
||||
font-family: var(--font-sans);
|
||||
/* Unify spacing between HBS and JS */
|
||||
.code-editor :global(.cm-content) {
|
||||
padding: var(--spacing-m) 0;
|
||||
}
|
||||
.code-editor {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.code-editor :global(.cm-tooltip.cm-completionInfo) {
|
||||
padding: var(--spacing-m);
|
||||
}
|
||||
|
|
|
@ -31,6 +31,23 @@ export const getDefaultTheme = opts => {
|
|||
const { height, resize, dark } = opts
|
||||
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": {
|
||||
borderLeftColor: "var(--spectrum-alias-text-color)",
|
||||
},
|
||||
|
|
|
@ -1,15 +1,6 @@
|
|||
<script>
|
||||
import {
|
||||
DrawerContent,
|
||||
Tabs,
|
||||
Tab,
|
||||
Body,
|
||||
Button,
|
||||
ActionButton,
|
||||
Heading,
|
||||
Icon,
|
||||
} from "@budibase/bbui"
|
||||
import { createEventDispatcher, onMount, getContext } from "svelte"
|
||||
import { DrawerContent, ActionButton, Icon } from "@budibase/bbui"
|
||||
import { createEventDispatcher, onMount } from "svelte"
|
||||
import {
|
||||
isValid,
|
||||
decodeJSBinding,
|
||||
|
@ -21,8 +12,6 @@
|
|||
readableToRuntimeBinding,
|
||||
runtimeToReadableBinding,
|
||||
} from "dataBinding"
|
||||
|
||||
import { admin } from "stores/portal"
|
||||
import CodeEditor from "../CodeEditor/CodeEditor.svelte"
|
||||
import {
|
||||
getHelperCompletions,
|
||||
|
@ -31,17 +20,15 @@
|
|||
EditorModes,
|
||||
bindingsToCompletions,
|
||||
} from "../CodeEditor"
|
||||
import BindingPicker from "./BindingPicker.svelte"
|
||||
import BindingSidePanel from "./BindingSidePanel.svelte"
|
||||
import EvaluationSidePanel from "./EvaluationSidePanel.svelte"
|
||||
import { BindingHelpers } from "./utils"
|
||||
import formatHighlight from "json-format-highlight"
|
||||
import { capitalise } from "helpers"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
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 valid
|
||||
export let allowJS = false
|
||||
|
@ -49,23 +36,32 @@
|
|||
export let context = null
|
||||
export let autofocusEditor = false
|
||||
|
||||
const drawerActions = getContext("drawer-actions")
|
||||
const bindingDrawerActions = getContext("binding-drawer-actions")
|
||||
const Modes = {
|
||||
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 insertAtPos
|
||||
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 targetMode = null
|
||||
let expressionResult
|
||||
|
||||
$: editorTabs = allowJS ? [Modes.Text, Modes.JavaScript] : [Modes.Text]
|
||||
$: sideTabs = [SidePanels.Evaluation, SidePanels.Bindings]
|
||||
$: enrichedBindings = enrichBindings(bindings, context)
|
||||
$: usingJS = mode === "JavaScript"
|
||||
$: usingJS = mode === Modes.JavaScript
|
||||
$: editorMode =
|
||||
mode === "JavaScript" ? EditorModes.JS : EditorModes.Handlebars
|
||||
mode === Modes.JavaScript ? EditorModes.JS : EditorModes.Handlebars
|
||||
$: bindingCompletions = bindingsToCompletions(enrichedBindings, editorMode)
|
||||
$: runtimeExpression = readableToRuntimeBinding(enrichedBindings, value)
|
||||
$: expressionResult = processStringSync(runtimeExpression || "", context)
|
||||
|
@ -117,9 +113,13 @@
|
|||
bindingHelpers.onSelectBinding(js ? jsValue : hbsValue, binding, { js })
|
||||
}
|
||||
|
||||
const onChangeMode = e => {
|
||||
mode = e.detail
|
||||
updateValue(mode === "JavaScript" ? jsValue : hbsValue)
|
||||
const changeMode = newMode => {
|
||||
mode = newMode
|
||||
updateValue(newMode === Modes.JavaScript ? jsValue : hbsValue)
|
||||
}
|
||||
|
||||
const changeSidePanel = newSidePanel => {
|
||||
sidePanel = newSidePanel === sidePanel ? null : newSidePanel
|
||||
}
|
||||
|
||||
const onChangeHBSValue = e => {
|
||||
|
@ -153,412 +153,292 @@
|
|||
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(() => {
|
||||
valid = isValid(readableToRuntimeBinding(enrichedBindings, value))
|
||||
})
|
||||
</script>
|
||||
|
||||
<span class="binding-drawer">
|
||||
<DrawerContent>
|
||||
<DrawerContent padding={false}>
|
||||
<div class="binding-panel">
|
||||
<div class="main">
|
||||
<Tabs
|
||||
selected={mode}
|
||||
on:select={onChangeMode}
|
||||
beforeSwitch={selectedMode => {
|
||||
if (selectedMode === mode) {
|
||||
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
|
||||
<div class="tabs">
|
||||
<div class="editor-tabs">
|
||||
{#each editorTabs as tab}
|
||||
<ActionButton
|
||||
size="M"
|
||||
quiet
|
||||
on:click={() => {
|
||||
drawerActions.hide()
|
||||
}}
|
||||
selected={mode === tab}
|
||||
on:click={() => changeMode(tab)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
{/if}
|
||||
{#if typeof bindingDrawerActions?.save === "function" && drawerActions?.headless}
|
||||
<Button
|
||||
cta
|
||||
disabled={!valid}
|
||||
on:click={() => {
|
||||
bindingDrawerActions.save()
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
{/if}
|
||||
{capitalise(tab)}
|
||||
</ActionButton>
|
||||
{/each}
|
||||
</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>
|
||||
</DrawerContent>
|
||||
</span>
|
||||
<div class="side" class:visible={!!sidePanel}>
|
||||
{#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>
|
||||
.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) {
|
||||
.binding-panel {
|
||||
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%;
|
||||
.binding-panel,
|
||||
.tabs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
}
|
||||
.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;
|
||||
.main {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xl);
|
||||
overflow: hidden;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
}
|
||||
.overlay-wrap {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
.side {
|
||||
flex: 0 0 0;
|
||||
transition: flex 130ms ease-out;
|
||||
}
|
||||
.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);
|
||||
.side.visible {
|
||||
flex: 0 0 420px;
|
||||
}
|
||||
|
||||
.binding-drawer :global(.code-editor),
|
||||
.binding-drawer :global(.code-editor > div) {
|
||||
/* Tabs */
|
||||
.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%;
|
||||
}
|
||||
.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 {
|
||||
margin: 0;
|
||||
|
@ -568,10 +448,5 @@
|
|||
border-radius: var(--border-radius-s);
|
||||
font-family: monospace;
|
||||
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>
|
||||
|
|
|
@ -137,123 +137,129 @@
|
|||
</div>
|
||||
</Popover>
|
||||
|
||||
<Layout noPadding gap="S">
|
||||
{#if selectedCategory}
|
||||
<div class="sub-section-back">
|
||||
<ActionButton
|
||||
secondary
|
||||
icon={"ArrowLeft"}
|
||||
on:click={() => {
|
||||
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
|
||||
<div class="binding-side-panel">
|
||||
<Layout noPadding gap="S">
|
||||
{#if selectedCategory}
|
||||
<div class="sub-section-back">
|
||||
<ActionButton
|
||||
secondary
|
||||
icon={"ArrowLeft"}
|
||||
on:click={() => {
|
||||
selectedCategory = categoryName
|
||||
selectedCategory = null
|
||||
}}
|
||||
>
|
||||
<Icon name={categoryIcons[categoryName]} />
|
||||
<span class="category-name">{categoryName} </span>
|
||||
<span class="category-chevron"><Icon name="ChevronRight" /></span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
Back
|
||||
</ActionButton>
|
||||
</div>
|
||||
{/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>
|
||||
{#if binding.display?.type || binding.fieldSchema?.type}
|
||||
<span class="binding__typeWrap">
|
||||
<span class="binding__type">
|
||||
{binding.display?.type || binding.fieldSchema?.type}
|
||||
</span>
|
||||
{#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={() => {
|
||||
selectedCategory = categoryName
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if binding.display?.type || binding.fieldSchema?.type}
|
||||
<span class="binding__typeWrap">
|
||||
<span class="binding__type">
|
||||
{binding.display?.type || binding.fieldSchema?.type}
|
||||
</span>
|
||||
</span>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
{#if selectedCategory === "Helpers" || search}
|
||||
{#if filteredHelpers?.length}
|
||||
<div class="sub-section">
|
||||
<div class="cat-heading">Helpers</div>
|
||||
<ul class="helpers">
|
||||
{#each filteredHelpers as helper}
|
||||
<li
|
||||
class="binding"
|
||||
on:mouseenter={e => showHelperPopover(helper, e.target)}
|
||||
on:mouseleave={hidePopover}
|
||||
on:click={() => addHelper(helper, mode.name === "javascript")}
|
||||
>
|
||||
<span class="binding__label">{helper.displayText}</span>
|
||||
<span class="binding__typeWrap">
|
||||
<span class="binding__type">function</span>
|
||||
</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{#if selectedCategory === "Helpers" || search}
|
||||
{#if filteredHelpers?.length}
|
||||
<div class="sub-section">
|
||||
<div class="cat-heading">Helpers</div>
|
||||
<ul class="helpers">
|
||||
{#each filteredHelpers as helper}
|
||||
<li
|
||||
class="binding"
|
||||
on:mouseenter={e => showHelperPopover(helper, e.target)}
|
||||
on:mouseleave={hidePopover}
|
||||
on:click={() => addHelper(helper, mode.name === "javascript")}
|
||||
>
|
||||
<span class="binding__label">{helper.displayText}</span>
|
||||
<span class="binding__typeWrap">
|
||||
<span class="binding__type">function</span>
|
||||
</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</Layout>
|
||||
</Layout>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.binding-side-panel {
|
||||
border-left: var(--border-light);
|
||||
}
|
||||
|
||||
.search :global(input) {
|
||||
border: none;
|
||||
border-radius: 0;
|
|
@ -91,11 +91,7 @@
|
|||
bind:this={bindingDrawer}
|
||||
{title}
|
||||
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}>
|
||||
Save
|
||||
</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