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,
|
2024-03-06 20:07:16 +01:00
|
|
|
notifications,
|
2024-03-06 15:33:17 +01:00
|
|
|
} from "@budibase/bbui"
|
2024-12-18 10:47:27 +01:00
|
|
|
import BindingPanel from "@/components/common/bindings/BindingPanel.svelte"
|
2024-03-06 14:33:00 +01:00
|
|
|
import { decodeJSBinding, encodeJSBinding } from "@budibase/string-templates"
|
2024-12-18 10:47:27 +01:00
|
|
|
import { snippets } from "@/stores/builder"
|
|
|
|
import { getSequentialName } from "@/helpers/duplicate"
|
|
|
|
import ConfirmDialog from "@/components/common/ConfirmDialog.svelte"
|
2024-03-13 12:48:17 +01:00
|
|
|
import { ValidSnippetNameRegex } from "@budibase/shared-core"
|
2025-02-21 10:41:13 +01:00
|
|
|
import type { Snippet } from "@budibase/types"
|
2024-03-06 14:33:00 +01:00
|
|
|
|
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 14:33:00 +01:00
|
|
|
|
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 = ""
|
2024-03-06 20:07:16 +01:00
|
|
|
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,
|
|
|
|
})
|
2024-03-06 14:33:00 +01:00
|
|
|
$: key = snippet?.name
|
2024-03-06 21:27:46 +01:00
|
|
|
$: name = snippet?.name || defaultName
|
2024-03-06 14:33:00 +01:00
|
|
|
$: code = snippet?.code ? encodeJSBinding(snippet.code) : ""
|
|
|
|
$: rawJS = decodeJSBinding(code)
|
2024-03-06 21:27:46 +01:00
|
|
|
$: nameError = validateName(name, $snippets)
|
2024-03-06 14:33:00 +01:00
|
|
|
|
|
|
|
const saveSnippet = async () => {
|
2024-03-06 20:07:16 +01:00
|
|
|
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)
|
2024-03-06 20:07:16 +01:00
|
|
|
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) {
|
2024-03-13 12:48:17 +01:00
|
|
|
notifications.error(error.message || "Error saving snippet")
|
2024-03-06 20:07:16 +01:00
|
|
|
}
|
|
|
|
loading = false
|
2024-03-06 14:33:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const deleteSnippet = async () => {
|
2024-03-06 20:07:16 +01:00
|
|
|
loading = true
|
|
|
|
try {
|
2025-02-21 11:50:01 +01:00
|
|
|
if (snippet) {
|
|
|
|
await snippets.deleteSnippet(snippet.name)
|
|
|
|
}
|
2024-03-06 20:07:16 +01:00
|
|
|
drawer.hide()
|
|
|
|
} catch (error) {
|
|
|
|
notifications.error("Error deleting snippet")
|
|
|
|
}
|
|
|
|
loading = false
|
2024-03-06 14:33:00 +01:00
|
|
|
}
|
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"
|
|
|
|
}
|
2024-12-03 19:55:06 +01:00
|
|
|
if (!snippet?.name && snippets.some(snippet => snippet.name === name)) {
|
2024-03-13 12:48:17 +01:00
|
|
|
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"
|
|
|
|
}
|
2024-03-13 12:48:17 +01:00
|
|
|
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
|
|
|
}
|
2024-03-13 12:48:17 +01:00
|
|
|
return null
|
2024-03-06 15:33:17 +01:00
|
|
|
}
|
2024-03-06 14:33:00 +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>
|
2024-03-06 14:33:00 +01:00
|
|
|
<svelte:fragment slot="buttons">
|
|
|
|
{#if snippet}
|
2024-03-07 09:25:11 +01:00
|
|
|
<Button
|
|
|
|
warning
|
|
|
|
on:click={deleteConfirmationDialog.show}
|
|
|
|
disabled={loading}
|
|
|
|
>
|
2024-03-06 20:07:16 +01:00
|
|
|
Delete
|
|
|
|
</Button>
|
2024-03-06 14:33:00 +01:00
|
|
|
{/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>
|
2024-03-06 14:33:00 +01:00
|
|
|
</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}
|
2024-03-06 14:33:00 +01:00
|
|
|
placeholder="return function(input) ❴ ... ❵"
|
|
|
|
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>
|
2024-03-06 14:33:00 +01:00
|
|
|
{/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>
|