1
0
Fork 0
mirror of synced 2024-07-06 15:00:49 +12:00

Add automatic naming of snippets

This commit is contained in:
Andrew Kingston 2024-03-06 20:27:46 +00:00
parent 4d271ccb53
commit cb7f33de77
8 changed files with 130 additions and 17 deletions

View file

@ -1,6 +1,6 @@
<script>
import BindingPanel from "./BindingPanel.svelte"
import { previewStore, snippetStore } from "stores/builder"
import { previewStore, snippets } from "stores/builder"
import { onMount } from "svelte"
export let bindings = []
@ -30,7 +30,7 @@
bind:valid
bindings={enrichedBindings}
context={$previewStore.selectedComponentContext}
snippets={$snippetStore}
snippets={$snippets}
{value}
{allowJS}
{allowHelpers}

View file

@ -1,6 +1,6 @@
<script>
import BindingPanel from "./BindingPanel.svelte"
import { snippetStore } from "stores/builder"
import { snippets } from "stores/builder"
export let bindings = []
export let valid
@ -23,7 +23,7 @@
<BindingPanel
bind:valid
bindings={enrichedBindings}
snippets={$snippetStore}
snippets={$snippets}
{value}
{allowJS}
{context}

View file

@ -10,7 +10,8 @@
} from "@budibase/bbui"
import BindingPanel from "components/common/bindings/BindingPanel.svelte"
import { decodeJSBinding, encodeJSBinding } from "@budibase/string-templates"
import { snippetStore } from "stores/builder"
import { snippets } from "stores/builder"
import { getSequentialName } from "helpers/duplicate"
export let snippet
@ -25,16 +26,17 @@
let code = ""
let loading = false
$: defaultName = getSequentialName($snippets, "MySnippet", x => x.name)
$: key = snippet?.name
$: name = snippet?.name || "MySnippet"
$: name = snippet?.name || defaultName
$: code = snippet?.code ? encodeJSBinding(snippet.code) : ""
$: rawJS = decodeJSBinding(code)
$: nameError = validateName(name, $snippetStore)
$: nameError = validateName(name, $snippets)
const saveSnippet = async () => {
loading = true
try {
await snippetStore.saveSnippet({
await snippets.saveSnippet({
name,
code: rawJS,
})
@ -49,7 +51,7 @@
const deleteSnippet = async () => {
loading = true
try {
await snippetStore.deleteSnippet(snippet.name)
await snippets.deleteSnippet(snippet.name)
drawer.hide()
} catch (error) {
notifications.error("Error deleting snippet")

View file

@ -48,3 +48,53 @@ export const duplicateName = (name, allNames) => {
return `${baseName} ${number}`
}
/**
* More flexible alternative to the above function, which handles getting the
* next sequential name from an array of existing items while accounting for
* any type of prefix, and being able to deeply retrieve that name from the
* existing item array.
*
* Examples with a prefix of "foo":
* [] => "foo"
* ["foo"] => "foo2"
* ["foo", "foo6"] => "foo7"
*
* Examples with a prefix of "foo " (space at the end):
* [] => "foo"
* ["foo"] => "foo 2"
* ["foo", "foo 6"] => "foo 7"
*
* @param items the array of existing items
* @param prefix the string prefix of each name, including any spaces desired
* @param getName optional function to extract the name for an item, if not a
* flat array of strings
*/
export const getSequentialName = (items, prefix, getName = x => x) => {
if (!prefix?.length || !getName) {
return null
}
const trimmedPrefix = prefix.trim()
if (!items?.length) {
return trimmedPrefix
}
let max = 0
items.forEach(item => {
const name = getName(item)
if (typeof name !== "string" || !name.startsWith(trimmedPrefix)) {
return
}
const split = name.split(trimmedPrefix)
if (split.length !== 2) {
return
}
if (split[1].trim() === "") {
split[1] = "1"
}
const num = parseInt(split[1])
if (num > max) {
max = num
}
})
return max === 0 ? trimmedPrefix : `${prefix}${max + 1}`
}

View file

@ -1,5 +1,5 @@
import { expect, describe, it } from "vitest"
import { duplicateName } from "../duplicate"
import { duplicateName, getSequentialName } from "../duplicate"
describe("duplicate", () => {
describe("duplicates a name ", () => {
@ -40,3 +40,64 @@ describe("duplicate", () => {
})
})
})
describe("getSequentialName", () => {
it("handles nullish items", async () => {
const name = getSequentialName(null, "foo", () => {})
expect(name).toBe("foo")
})
it("handles nullish prefix", async () => {
const name = getSequentialName([], null, () => {})
expect(name).toBe(null)
})
it("handles nullish getName function", async () => {
const name = getSequentialName([], "foo", null)
expect(name).toBe(null)
})
it("handles just the prefix", async () => {
const name = getSequentialName(["foo"], "foo", x => x)
expect(name).toBe("foo2")
})
it("handles continuous ranges", async () => {
const name = getSequentialName(["foo", "foo2", "foo3"], "foo", x => x)
expect(name).toBe("foo4")
})
it("handles discontinuous ranges", async () => {
const name = getSequentialName(["foo", "foo3"], "foo", x => x)
expect(name).toBe("foo4")
})
it("handles a space inside the prefix", async () => {
const name = getSequentialName(["foo", "foo 2", "foo 3"], "foo ", x => x)
expect(name).toBe("foo 4")
})
it("handles a space inside the prefix with just the prefix", async () => {
const name = getSequentialName(["foo"], "foo ", x => x)
expect(name).toBe("foo 2")
})
it("handles no matches", async () => {
const name = getSequentialName(["aaa", "bbb"], "foo", x => x)
expect(name).toBe("foo")
})
it("handles similar names", async () => {
const name = getSequentialName(
["fooo1", "2foo", "a3foo4", "5foo5"],
"foo",
x => x
)
expect(name).toBe("foo")
})
it("handles non-string names", async () => {
const name = getSequentialName([null, 4123, [], {}], "foo", x => x)
expect(name).toBe("foo")
})
})

View file

@ -18,7 +18,7 @@ import {
} from "./automations.js"
import { userStore, userSelectedResourceMap, isOnlyUser } from "./users.js"
import { deploymentStore } from "./deployments.js"
import { snippetStore } from "./snippets"
import { snippets } from "./snippets"
// Backend
import { tables } from "./tables"
@ -63,7 +63,7 @@ export {
queries,
flags,
hoverStore,
snippetStore,
snippets,
}
export const reset = () => {
@ -103,7 +103,7 @@ export const initialise = async pkg => {
builderStore.init(application)
navigationStore.syncAppNavigation(application?.navigation)
themeStore.syncAppTheme(application)
snippetStore.syncMetadata(application)
snippets.syncMetadata(application)
screenStore.syncAppScreens(pkg)
layoutStore.syncAppLayouts(pkg)
resetBuilderHistory()

View file

@ -2,7 +2,7 @@ import { writable, get } from "svelte/store"
import { API } from "api"
import { appStore } from "./app"
const createSnippetStore = () => {
const createsnippets = () => {
const store = writable([])
const syncMetadata = metadata => {
@ -38,4 +38,4 @@ const createSnippetStore = () => {
}
}
export const snippetStore = createSnippetStore()
export const snippets = createsnippets()

View file

@ -6,7 +6,7 @@ import {
themeStore,
navigationStore,
deploymentStore,
snippetStore,
snippets,
datasources,
tables,
} from "stores/builder"
@ -65,7 +65,7 @@ export const createBuilderWebsocket = appId => {
appStore.syncMetadata(metadata)
themeStore.syncMetadata(metadata)
navigationStore.syncMetadata(metadata)
snippetStore.syncMetadata(metadata)
snippets.syncMetadata(metadata)
})
socket.onOther(
BuilderSocketEvent.AppPublishChange,