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

172 lines
4.2 KiB
Svelte
Raw Normal View History

2025-02-21 10:21:21 +01:00
<script lang="ts">
2024-03-06 15:33:17 +01:00
import {
Button,
Drawer,
Input,
Icon,
AbsTooltip,
TooltipType,
notifications,
2024-03-06 15:33:17 +01:00
} from "@budibase/bbui"
import BindingPanel from "@/components/common/bindings/BindingPanel.svelte"
import { decodeJSBinding, encodeJSBinding } from "@budibase/string-templates"
import { snippets } from "@/stores/builder"
import { getSequentialName } from "@/helpers/duplicate"
import ConfirmDialog from "@/components/common/ConfirmDialog.svelte"
import { ValidSnippetNameRegex } from "@budibase/shared-core"
2025-02-21 10:41:13 +01:00
import type { Snippet } from "@budibase/types"
2025-03-03 14:12:36 +01:00
export const show = () => {
if (!snippet) {
key = Math.random().toString()
// Reset state when creating multiple snippets
code = ""
name = defaultName
}
drawer.show()
}
2025-03-03 13:52:46 +01:00
export const hide = () => drawer.hide()
2025-02-21 11:50:01 +01:00
export let snippet: Snippet | null
2024-03-06 19:36:22 +01:00
const firstCharNumberRegex = /^[0-9].*$/
2024-03-06 15:33:17 +01:00
2025-02-21 10:21:21 +01:00
let drawer: Drawer
2024-03-06 15:33:17 +01:00
let name = ""
let code = ""
let loading = false
2025-02-21 10:21:21 +01:00
let deleteConfirmationDialog: ConfirmDialog
2024-03-06 15:33:17 +01:00
2025-01-14 11:14:34 +01:00
$: defaultName = getSequentialName($snippets, "MySnippet", {
getName: x => x.name,
})
$: key = snippet?.name
2024-03-06 21:27:46 +01:00
$: name = snippet?.name || defaultName
$: code = snippet?.code ? encodeJSBinding(snippet.code) : ""
$: rawJS = decodeJSBinding(code)
2024-03-06 21:27:46 +01:00
$: nameError = validateName(name, $snippets)
const saveSnippet = async () => {
loading = true
try {
2025-02-21 10:21:21 +01:00
const newSnippet: Snippet = { name, code: rawJS || "" }
2024-03-07 09:25:11 +01:00
await snippets.saveSnippet(newSnippet)
drawer.hide()
2024-03-07 09:25:11 +01:00
notifications.success(`Snippet ${newSnippet.name} saved`)
2025-02-21 10:21:21 +01:00
} catch (error: any) {
notifications.error(error.message || "Error saving snippet")
}
loading = false
}
const deleteSnippet = async () => {
loading = true
try {
2025-02-21 11:50:01 +01:00
if (snippet) {
await snippets.deleteSnippet(snippet.name)
}
drawer.hide()
} catch (error) {
notifications.error("Error deleting snippet")
}
loading = false
}
2024-03-06 15:33:17 +01:00
2025-02-21 10:21:21 +01:00
const validateName = (name: string, snippets: Snippet[]) => {
2024-03-06 19:36:22 +01:00
if (!name?.length) {
return "Name is required"
}
if (!snippet?.name && snippets.some(snippet => snippet.name === name)) {
return "That name is already in use"
}
2024-03-06 19:36:22 +01:00
if (firstCharNumberRegex.test(name)) {
return "Can't start with a number"
}
if (!ValidSnippetNameRegex.test(name)) {
2024-03-06 19:36:22 +01:00
return "No special characters or spaces"
2024-03-06 15:33:17 +01:00
}
return null
2024-03-06 15:33:17 +01:00
}
</script>
2025-03-03 13:52:46 +01:00
<Drawer bind:this={drawer}>
2024-03-06 15:33:17 +01:00
<svelte:fragment slot="title">
{#if snippet}
{snippet.name}
{:else}
2024-03-06 19:36:22 +01:00
<div class="name" class:invalid={nameError != null}>
2024-03-06 15:33:17 +01:00
<span>Name</span>
<Input bind:value={name} />
2024-03-06 19:36:22 +01:00
{#if nameError}
<AbsTooltip text={nameError} type={TooltipType.Negative}>
2024-03-06 15:33:17 +01:00
<Icon
name="Help"
size="S"
color="var(--spectrum-global-color-red-400)"
/>
</AbsTooltip>
{/if}
</div>
{/if}
</svelte:fragment>
<svelte:fragment slot="buttons">
{#if snippet}
2024-03-07 09:25:11 +01:00
<Button
warning
on:click={deleteConfirmationDialog.show}
disabled={loading}
>
Delete
</Button>
{/if}
2025-02-21 10:21:21 +01:00
<Button
cta
on:click={saveSnippet}
disabled={!code || loading || !!nameError}
>
2024-03-06 15:33:17 +01:00
Save
</Button>
</svelte:fragment>
<svelte:fragment slot="body">
{#key key}
<BindingPanel
allowHBS={false}
allowJS
allowSnippets={false}
2025-02-13 11:54:56 +01:00
allowHelpers={false}
2024-03-06 15:33:17 +01:00
showTabBar={false}
placeholder="return function(input) &#10100; ... &#10101;"
value={code}
on:change={e => (code = e.detail)}
2024-03-06 14:59:20 +01:00
>
2025-02-21 10:21:21 +01:00
<Input placeholder="Name" />
2024-03-06 14:59:20 +01:00
</BindingPanel>
{/key}
</svelte:fragment>
</Drawer>
2024-03-06 15:33:17 +01:00
2024-03-07 09:25:11 +01:00
<ConfirmDialog
bind:this={deleteConfirmationDialog}
title="Delete snippet"
body={`Are you sure you want to delete ${snippet?.name}?`}
onOk={deleteSnippet}
/>
2024-03-06 15:33:17 +01:00
<style>
.name {
display: flex;
gap: var(--spacing-l);
align-items: center;
position: relative;
}
2024-03-06 19:36:22 +01:00
.name :global(input) {
2024-03-06 15:33:17 +01:00
width: 200px;
}
2024-03-06 19:36:22 +01:00
.name.invalid :global(input) {
padding-right: 32px;
}
2024-03-06 15:33:17 +01:00
.name :global(.icon) {
position: absolute;
right: 10px;
}
</style>