Add real snippet saving and fix snippet evaluation in client apps
This commit is contained in:
parent
01679fbd01
commit
4d271ccb53
|
@ -6,6 +6,7 @@
|
|||
Icon,
|
||||
AbsTooltip,
|
||||
TooltipType,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import BindingPanel from "components/common/bindings/BindingPanel.svelte"
|
||||
import { decodeJSBinding, encodeJSBinding } from "@budibase/string-templates"
|
||||
|
@ -22,24 +23,38 @@
|
|||
let drawer
|
||||
let name = ""
|
||||
let code = ""
|
||||
let loading = false
|
||||
|
||||
$: key = snippet?.name
|
||||
$: name = snippet?.name || "MySnippet"
|
||||
$: code = snippet?.code ? encodeJSBinding(snippet.code) : ""
|
||||
$: rawJS = decodeJSBinding(code)
|
||||
$: nameError = validateName(name)
|
||||
$: nameError = validateName(name, $snippetStore)
|
||||
|
||||
const saveSnippet = async () => {
|
||||
await snippetStore.saveSnippet({
|
||||
name,
|
||||
code: rawJS,
|
||||
})
|
||||
drawer.hide()
|
||||
loading = true
|
||||
try {
|
||||
await snippetStore.saveSnippet({
|
||||
name,
|
||||
code: rawJS,
|
||||
})
|
||||
drawer.hide()
|
||||
notifications.success(`Snippet ${name} saved`)
|
||||
} catch (error) {
|
||||
notifications.error("Error saving snippet")
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
|
||||
const deleteSnippet = async () => {
|
||||
await snippetStore.deleteSnippet(snippet.name)
|
||||
drawer.hide()
|
||||
loading = true
|
||||
try {
|
||||
await snippetStore.deleteSnippet(snippet.name)
|
||||
drawer.hide()
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting snippet")
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
|
||||
// Validating function names is not as easy as you think. A simple regex does
|
||||
|
@ -48,7 +63,7 @@
|
|||
// Instead, we can run a simple regex to roughly validate it, then basically
|
||||
// try executing it and see if it's valid JS. The initial regex prevents
|
||||
// against any potential XSS attacks here.
|
||||
const validateName = name => {
|
||||
const validateName = (name, snippets) => {
|
||||
if (!name?.length) {
|
||||
return "Name is required"
|
||||
}
|
||||
|
@ -58,6 +73,9 @@
|
|||
if (!roughValidNameRegex.test(name)) {
|
||||
return "No special characters or spaces"
|
||||
}
|
||||
if (snippets.some(snippet => snippet.name === name)) {
|
||||
return "That name is already in use"
|
||||
}
|
||||
const js = `(function ${name}(){return true})()`
|
||||
try {
|
||||
return eval(js) === true ? null : "Invalid name"
|
||||
|
@ -89,9 +107,11 @@
|
|||
</svelte:fragment>
|
||||
<svelte:fragment slot="buttons">
|
||||
{#if snippet}
|
||||
<Button warning on:click={deleteSnippet}>Delete</Button>
|
||||
<Button warning on:click={deleteSnippet} disabled={loading}>
|
||||
Delete
|
||||
</Button>
|
||||
{/if}
|
||||
<Button cta on:click={saveSnippet} disabled={nameError != null}>
|
||||
<Button cta on:click={saveSnippet} disabled={loading || nameError != null}>
|
||||
Save
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
|
|
|
@ -1,58 +1,33 @@
|
|||
import { writable } from "svelte/store"
|
||||
|
||||
const EXAMPLE_SNIPPETS = [
|
||||
{
|
||||
name: "Square",
|
||||
code: `
|
||||
return function(num) {
|
||||
return num * num
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "HelloWorld",
|
||||
code: `
|
||||
return "Hello, world!"
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "Colorful",
|
||||
code: `
|
||||
let a = null
|
||||
let b = "asdasd"
|
||||
let c = 123123
|
||||
let d = undefined
|
||||
let e = [1, 2, 3]
|
||||
let f = { foo: "bar" }
|
||||
let g = Math.round(1.234)
|
||||
if (a === b) {
|
||||
return c ?? e
|
||||
}
|
||||
return d || f
|
||||
// comment
|
||||
let h = 1 + 2 + 3 * 3
|
||||
let i = true
|
||||
let j = false
|
||||
`,
|
||||
},
|
||||
]
|
||||
import { writable, get } from "svelte/store"
|
||||
import { API } from "api"
|
||||
import { appStore } from "./app"
|
||||
|
||||
const createSnippetStore = () => {
|
||||
const store = writable(EXAMPLE_SNIPPETS)
|
||||
const store = writable([])
|
||||
|
||||
const syncMetadata = metadata => {
|
||||
store.set(metadata?.snippets || EXAMPLE_SNIPPETS)
|
||||
store.set(metadata?.snippets || [])
|
||||
}
|
||||
|
||||
const saveSnippet = updatedSnippet => {
|
||||
store.update(state => [
|
||||
...state.filter(snippet => snippet.name !== updatedSnippet.name),
|
||||
const saveSnippet = async updatedSnippet => {
|
||||
const snippets = [
|
||||
...get(store).filter(snippet => snippet.name !== updatedSnippet.name),
|
||||
updatedSnippet,
|
||||
])
|
||||
]
|
||||
const app = await API.saveAppMetadata({
|
||||
appId: get(appStore).appId,
|
||||
metadata: { snippets },
|
||||
})
|
||||
syncMetadata(app)
|
||||
}
|
||||
|
||||
const deleteSnippet = snippetName => {
|
||||
store.update(state => state.filter(snippet => snippet.name !== snippetName))
|
||||
const deleteSnippet = async snippetName => {
|
||||
const snippets = get(store).filter(snippet => snippet.name !== snippetName)
|
||||
const app = await API.saveAppMetadata({
|
||||
appId: get(appStore).appId,
|
||||
metadata: { snippets },
|
||||
})
|
||||
syncMetadata(app)
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { derived } from "svelte/store"
|
||||
import { appStore } from "../app.js"
|
||||
|
||||
export const snippets = derived(appStore, $appStore => $appStore.snippets)
|
||||
|
||||
snippets.subscribe(console.log)
|
||||
export const snippets = derived(appStore, $appStore => {
|
||||
return $appStore?.application?.snippets || []
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue