budibase/packages/builder/src/components/common/bindings/DrawerBindableSlot.svelte

265 lines
6.6 KiB
Svelte

<script>
import { Icon, Input, Drawer, Button } from "@budibase/bbui"
import {
readableToRuntimeBinding,
runtimeToReadableBinding,
} from "@/dataBinding"
import { FieldType } from "@budibase/types"
import ClientBindingPanel from "@/components/common/bindings/ClientBindingPanel.svelte"
import { createEventDispatcher, setContext } from "svelte"
import { isJSBinding, findHBSBlocks } from "@budibase/string-templates"
export let panel = ClientBindingPanel
export let value = ""
export let bindings = []
export let title = "Bindings"
export let placeholder
export let label
export let disabled = false
export let allowJS = true
export let allowHelpers = true
export let updateOnChange = true
export let type
export let schema
export let allowHBS = true
export let context = {}
const dispatch = createEventDispatcher()
let bindingDrawer
let currentVal = value
let attachmentTypes = [
FieldType.ATTACHMENT_SINGLE,
FieldType.ATTACHMENTS,
FieldType.SIGNATURE_SINGLE,
]
$: readableValue = runtimeToReadableBinding(bindings, value)
$: tempValue = readableValue
$: isJS = isJSBinding(value)
const saveBinding = () => {
onChange(tempValue)
onBlur()
bindingDrawer.hide()
}
setContext("binding-drawer-actions", {
save: saveBinding,
})
const onChange = value => {
if (
(type === "link" || type === "bb_reference") &&
value &&
hasValidLinks(value)
) {
currentVal = value.split(",")
} else if (type === "array" && value && hasValidOptions(value)) {
currentVal = value.split(",")
} else {
currentVal = readableToRuntimeBinding(bindings, value)
}
dispatch("change", currentVal)
}
const onBlur = () => {
dispatch("blur", currentVal)
}
const isValidDate = value => {
return !value || !isNaN(new Date(value).valueOf())
}
const hasValidLinks = value => {
let links = []
if (Array.isArray(value)) {
links = value
} else if (value && typeof value === "string") {
links = value.split(",")
} else {
return !value
}
return links.every(link => link.startsWith("ro_"))
}
const hasValidOptions = value => {
let links = []
if (Array.isArray(value)) {
links = value
} else if (value && typeof value === "string") {
links = value.split(",")
} else {
return !value
}
return links.every(link => schema?.constraints?.inclusion?.includes(link))
}
const isValidBoolean = value => {
return value === "false" || value === "true" || value == ""
}
const validationMap = {
date: isValidDate,
datetime: isValidDate,
link: hasValidLinks,
bb_reference: hasValidLinks,
bb_reference_single: hasValidLinks,
array: hasValidOptions,
longform: value => !isJSBinding(value),
json: value => !isJSBinding(value),
options: value => !isJSBinding(value) && !findHBSBlocks(value)?.length,
boolean: isValidBoolean,
attachment: false,
attachment_single: false,
signature_single: false,
}
const isValid = value => {
const validate = validationMap[type]
return validate ? validate(value) : true
}
const getIconClass = (value, type) => {
if (type === "longform" && !isJSBinding(value)) {
return "text-area-slot-icon"
}
if (type === "json" && !isJSBinding(value)) {
return "json-slot-icon"
}
if (
![
"string",
"number",
"bigint",
"barcodeqr",
"attachment",
"signature_single",
"attachment_single",
].includes(type)
) {
return "slot-icon"
}
return ""
}
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="control" class:disabled>
{#if !isValid(value) && !$$slots.default}
<Input
{label}
{disabled}
readonly={isJS}
value={isJS ? "(JavaScript function)" : readableValue}
on:change={event => onChange(event.detail)}
on:blur={onBlur}
{placeholder}
{updateOnChange}
/>
<div
class="icon"
on:click={() => {
if (!isJS) {
dispatch("change", "")
}
}}
>
<Icon disabled={isJS} size="S" name="Close" />
</div>
{:else}
<slot />
{/if}
{#if !disabled && type !== "formula" && !attachmentTypes.includes(type)}
<div
class={`icon ${getIconClass(value, type)}`}
on:click={() => {
bindingDrawer.show()
}}
>
<Icon size="S" name="FlashOn" />
</div>
{/if}
</div>
<Drawer
on:drawerHide
on:drawerShow
bind:this={bindingDrawer}
title={title ?? placeholder ?? "Bindings"}
>
<Button cta slot="buttons" on:click={saveBinding}>Save</Button>
<svelte:component
this={panel}
slot="body"
value={readableValue}
on:change={event => (tempValue = event.detail)}
{bindings}
{allowJS}
{allowHBS}
{allowHelpers}
{context}
/>
</Drawer>
<style>
.control {
flex: 1;
position: relative;
}
.slot-icon {
right: 31px;
border-right: 1px solid var(--spectrum-alias-border-color);
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
}
.text-area-slot-icon {
border-bottom: 1px solid var(--spectrum-alias-border-color);
border-bottom-right-radius: 0px;
top: 1px;
}
.json-slot-icon {
border-bottom: 1px solid var(--spectrum-alias-border-color);
border-bottom-right-radius: 0px;
top: 1px;
right: 0px;
}
.icon {
right: 1px;
bottom: 1px;
position: absolute;
justify-content: center;
align-items: center;
display: flex;
flex-direction: row;
box-sizing: border-box;
border-left: 1px solid var(--spectrum-alias-border-color);
border-top-right-radius: var(--spectrum-alias-border-radius-regular);
border-bottom-right-radius: var(--spectrum-alias-border-radius-regular);
width: 31px;
color: var(--spectrum-alias-text-color);
background-color: var(--spectrum-global-color-gray-75);
transition: background-color
var(--spectrum-global-animation-duration-100, 130ms),
box-shadow var(--spectrum-global-animation-duration-100, 130ms),
border-color var(--spectrum-global-animation-duration-100, 130ms);
height: calc(var(--spectrum-alias-item-height-m) - 2px);
}
.icon:hover {
cursor: pointer;
color: var(--spectrum-alias-text-color-hover);
background-color: var(--spectrum-global-color-gray-50);
border-color: var(--spectrum-alias-border-color-hover);
}
.control:not(.disabled) :global(.spectrum-Textfield-input) {
padding-right: 40px;
}
</style>