Add automatic naming of snippets
This commit is contained in:
parent
4d271ccb53
commit
cb7f33de77
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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}`
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue