Add automatic naming of snippets
This commit is contained in:
parent
4d271ccb53
commit
cb7f33de77
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import BindingPanel from "./BindingPanel.svelte"
|
import BindingPanel from "./BindingPanel.svelte"
|
||||||
import { previewStore, snippetStore } from "stores/builder"
|
import { previewStore, snippets } from "stores/builder"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
export let bindings = []
|
export let bindings = []
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
bind:valid
|
bind:valid
|
||||||
bindings={enrichedBindings}
|
bindings={enrichedBindings}
|
||||||
context={$previewStore.selectedComponentContext}
|
context={$previewStore.selectedComponentContext}
|
||||||
snippets={$snippetStore}
|
snippets={$snippets}
|
||||||
{value}
|
{value}
|
||||||
{allowJS}
|
{allowJS}
|
||||||
{allowHelpers}
|
{allowHelpers}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import BindingPanel from "./BindingPanel.svelte"
|
import BindingPanel from "./BindingPanel.svelte"
|
||||||
import { snippetStore } from "stores/builder"
|
import { snippets } from "stores/builder"
|
||||||
|
|
||||||
export let bindings = []
|
export let bindings = []
|
||||||
export let valid
|
export let valid
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
<BindingPanel
|
<BindingPanel
|
||||||
bind:valid
|
bind:valid
|
||||||
bindings={enrichedBindings}
|
bindings={enrichedBindings}
|
||||||
snippets={$snippetStore}
|
snippets={$snippets}
|
||||||
{value}
|
{value}
|
||||||
{allowJS}
|
{allowJS}
|
||||||
{context}
|
{context}
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
} 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"
|
||||||
import { snippetStore } from "stores/builder"
|
import { snippets } from "stores/builder"
|
||||||
|
import { getSequentialName } from "helpers/duplicate"
|
||||||
|
|
||||||
export let snippet
|
export let snippet
|
||||||
|
|
||||||
|
@ -25,16 +26,17 @@
|
||||||
let code = ""
|
let code = ""
|
||||||
let loading = false
|
let loading = false
|
||||||
|
|
||||||
|
$: defaultName = getSequentialName($snippets, "MySnippet", x => x.name)
|
||||||
$: key = snippet?.name
|
$: key = snippet?.name
|
||||||
$: name = snippet?.name || "MySnippet"
|
$: name = snippet?.name || defaultName
|
||||||
$: code = snippet?.code ? encodeJSBinding(snippet.code) : ""
|
$: code = snippet?.code ? encodeJSBinding(snippet.code) : ""
|
||||||
$: rawJS = decodeJSBinding(code)
|
$: rawJS = decodeJSBinding(code)
|
||||||
$: nameError = validateName(name, $snippetStore)
|
$: nameError = validateName(name, $snippets)
|
||||||
|
|
||||||
const saveSnippet = async () => {
|
const saveSnippet = async () => {
|
||||||
loading = true
|
loading = true
|
||||||
try {
|
try {
|
||||||
await snippetStore.saveSnippet({
|
await snippets.saveSnippet({
|
||||||
name,
|
name,
|
||||||
code: rawJS,
|
code: rawJS,
|
||||||
})
|
})
|
||||||
|
@ -49,7 +51,7 @@
|
||||||
const deleteSnippet = async () => {
|
const deleteSnippet = async () => {
|
||||||
loading = true
|
loading = true
|
||||||
try {
|
try {
|
||||||
await snippetStore.deleteSnippet(snippet.name)
|
await snippets.deleteSnippet(snippet.name)
|
||||||
drawer.hide()
|
drawer.hide()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error deleting snippet")
|
notifications.error("Error deleting snippet")
|
||||||
|
|
|
@ -48,3 +48,53 @@ export const duplicateName = (name, allNames) => {
|
||||||
|
|
||||||
return `${baseName} ${number}`
|
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 { expect, describe, it } from "vitest"
|
||||||
import { duplicateName } from "../duplicate"
|
import { duplicateName, getSequentialName } from "../duplicate"
|
||||||
|
|
||||||
describe("duplicate", () => {
|
describe("duplicate", () => {
|
||||||
describe("duplicates a name ", () => {
|
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"
|
} from "./automations.js"
|
||||||
import { userStore, userSelectedResourceMap, isOnlyUser } from "./users.js"
|
import { userStore, userSelectedResourceMap, isOnlyUser } from "./users.js"
|
||||||
import { deploymentStore } from "./deployments.js"
|
import { deploymentStore } from "./deployments.js"
|
||||||
import { snippetStore } from "./snippets"
|
import { snippets } from "./snippets"
|
||||||
|
|
||||||
// Backend
|
// Backend
|
||||||
import { tables } from "./tables"
|
import { tables } from "./tables"
|
||||||
|
@ -63,7 +63,7 @@ export {
|
||||||
queries,
|
queries,
|
||||||
flags,
|
flags,
|
||||||
hoverStore,
|
hoverStore,
|
||||||
snippetStore,
|
snippets,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const reset = () => {
|
export const reset = () => {
|
||||||
|
@ -103,7 +103,7 @@ export const initialise = async pkg => {
|
||||||
builderStore.init(application)
|
builderStore.init(application)
|
||||||
navigationStore.syncAppNavigation(application?.navigation)
|
navigationStore.syncAppNavigation(application?.navigation)
|
||||||
themeStore.syncAppTheme(application)
|
themeStore.syncAppTheme(application)
|
||||||
snippetStore.syncMetadata(application)
|
snippets.syncMetadata(application)
|
||||||
screenStore.syncAppScreens(pkg)
|
screenStore.syncAppScreens(pkg)
|
||||||
layoutStore.syncAppLayouts(pkg)
|
layoutStore.syncAppLayouts(pkg)
|
||||||
resetBuilderHistory()
|
resetBuilderHistory()
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { writable, get } from "svelte/store"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { appStore } from "./app"
|
import { appStore } from "./app"
|
||||||
|
|
||||||
const createSnippetStore = () => {
|
const createsnippets = () => {
|
||||||
const store = writable([])
|
const store = writable([])
|
||||||
|
|
||||||
const syncMetadata = metadata => {
|
const syncMetadata = metadata => {
|
||||||
|
@ -38,4 +38,4 @@ const createSnippetStore = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const snippetStore = createSnippetStore()
|
export const snippets = createsnippets()
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
themeStore,
|
themeStore,
|
||||||
navigationStore,
|
navigationStore,
|
||||||
deploymentStore,
|
deploymentStore,
|
||||||
snippetStore,
|
snippets,
|
||||||
datasources,
|
datasources,
|
||||||
tables,
|
tables,
|
||||||
} from "stores/builder"
|
} from "stores/builder"
|
||||||
|
@ -65,7 +65,7 @@ export const createBuilderWebsocket = appId => {
|
||||||
appStore.syncMetadata(metadata)
|
appStore.syncMetadata(metadata)
|
||||||
themeStore.syncMetadata(metadata)
|
themeStore.syncMetadata(metadata)
|
||||||
navigationStore.syncMetadata(metadata)
|
navigationStore.syncMetadata(metadata)
|
||||||
snippetStore.syncMetadata(metadata)
|
snippets.syncMetadata(metadata)
|
||||||
})
|
})
|
||||||
socket.onOther(
|
socket.onOther(
|
||||||
BuilderSocketEvent.AppPublishChange,
|
BuilderSocketEvent.AppPublishChange,
|
||||||
|
|
Loading…
Reference in New Issue