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,
|
Icon,
|
||||||
AbsTooltip,
|
AbsTooltip,
|
||||||
TooltipType,
|
TooltipType,
|
||||||
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import BindingPanel from "components/common/bindings/BindingPanel.svelte"
|
import BindingPanel from "components/common/bindings/BindingPanel.svelte"
|
||||||
import { decodeJSBinding, encodeJSBinding } from "@budibase/string-templates"
|
import { decodeJSBinding, encodeJSBinding } from "@budibase/string-templates"
|
||||||
|
@ -22,24 +23,38 @@
|
||||||
let drawer
|
let drawer
|
||||||
let name = ""
|
let name = ""
|
||||||
let code = ""
|
let code = ""
|
||||||
|
let loading = false
|
||||||
|
|
||||||
$: key = snippet?.name
|
$: key = snippet?.name
|
||||||
$: name = snippet?.name || "MySnippet"
|
$: name = snippet?.name || "MySnippet"
|
||||||
$: code = snippet?.code ? encodeJSBinding(snippet.code) : ""
|
$: code = snippet?.code ? encodeJSBinding(snippet.code) : ""
|
||||||
$: rawJS = decodeJSBinding(code)
|
$: rawJS = decodeJSBinding(code)
|
||||||
$: nameError = validateName(name)
|
$: nameError = validateName(name, $snippetStore)
|
||||||
|
|
||||||
const saveSnippet = async () => {
|
const saveSnippet = async () => {
|
||||||
await snippetStore.saveSnippet({
|
loading = true
|
||||||
name,
|
try {
|
||||||
code: rawJS,
|
await snippetStore.saveSnippet({
|
||||||
})
|
name,
|
||||||
drawer.hide()
|
code: rawJS,
|
||||||
|
})
|
||||||
|
drawer.hide()
|
||||||
|
notifications.success(`Snippet ${name} saved`)
|
||||||
|
} catch (error) {
|
||||||
|
notifications.error("Error saving snippet")
|
||||||
|
}
|
||||||
|
loading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteSnippet = async () => {
|
const deleteSnippet = async () => {
|
||||||
await snippetStore.deleteSnippet(snippet.name)
|
loading = true
|
||||||
drawer.hide()
|
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
|
// 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
|
// 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
|
// try executing it and see if it's valid JS. The initial regex prevents
|
||||||
// against any potential XSS attacks here.
|
// against any potential XSS attacks here.
|
||||||
const validateName = name => {
|
const validateName = (name, snippets) => {
|
||||||
if (!name?.length) {
|
if (!name?.length) {
|
||||||
return "Name is required"
|
return "Name is required"
|
||||||
}
|
}
|
||||||
|
@ -58,6 +73,9 @@
|
||||||
if (!roughValidNameRegex.test(name)) {
|
if (!roughValidNameRegex.test(name)) {
|
||||||
return "No special characters or spaces"
|
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})()`
|
const js = `(function ${name}(){return true})()`
|
||||||
try {
|
try {
|
||||||
return eval(js) === true ? null : "Invalid name"
|
return eval(js) === true ? null : "Invalid name"
|
||||||
|
@ -89,9 +107,11 @@
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="buttons">
|
<svelte:fragment slot="buttons">
|
||||||
{#if snippet}
|
{#if snippet}
|
||||||
<Button warning on:click={deleteSnippet}>Delete</Button>
|
<Button warning on:click={deleteSnippet} disabled={loading}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
<Button cta on:click={saveSnippet} disabled={nameError != null}>
|
<Button cta on:click={saveSnippet} disabled={loading || nameError != null}>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
|
|
@ -1,58 +1,33 @@
|
||||||
import { writable } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
|
import { API } from "api"
|
||||||
const EXAMPLE_SNIPPETS = [
|
import { appStore } from "./app"
|
||||||
{
|
|
||||||
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
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const createSnippetStore = () => {
|
const createSnippetStore = () => {
|
||||||
const store = writable(EXAMPLE_SNIPPETS)
|
const store = writable([])
|
||||||
|
|
||||||
const syncMetadata = metadata => {
|
const syncMetadata = metadata => {
|
||||||
store.set(metadata?.snippets || EXAMPLE_SNIPPETS)
|
store.set(metadata?.snippets || [])
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveSnippet = updatedSnippet => {
|
const saveSnippet = async updatedSnippet => {
|
||||||
store.update(state => [
|
const snippets = [
|
||||||
...state.filter(snippet => snippet.name !== updatedSnippet.name),
|
...get(store).filter(snippet => snippet.name !== updatedSnippet.name),
|
||||||
updatedSnippet,
|
updatedSnippet,
|
||||||
])
|
]
|
||||||
|
const app = await API.saveAppMetadata({
|
||||||
|
appId: get(appStore).appId,
|
||||||
|
metadata: { snippets },
|
||||||
|
})
|
||||||
|
syncMetadata(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteSnippet = snippetName => {
|
const deleteSnippet = async snippetName => {
|
||||||
store.update(state => state.filter(snippet => snippet.name !== snippetName))
|
const snippets = get(store).filter(snippet => snippet.name !== snippetName)
|
||||||
|
const app = await API.saveAppMetadata({
|
||||||
|
appId: get(appStore).appId,
|
||||||
|
metadata: { snippets },
|
||||||
|
})
|
||||||
|
syncMetadata(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { derived } from "svelte/store"
|
import { derived } from "svelte/store"
|
||||||
import { appStore } from "../app.js"
|
import { appStore } from "../app.js"
|
||||||
|
|
||||||
export const snippets = derived(appStore, $appStore => $appStore.snippets)
|
export const snippets = derived(appStore, $appStore => {
|
||||||
|
return $appStore?.application?.snippets || []
|
||||||
snippets.subscribe(console.log)
|
})
|
||||||
|
|
Loading…
Reference in New Issue