diff --git a/package.json b/package.json
index 98524e0ee4..f9aef2f36b 100644
--- a/package.json
+++ b/package.json
@@ -74,8 +74,8 @@
"build:docker:single:multiarch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/single/Dockerfile -t budibase:latest .",
"build:docker:single": "./scripts/build-single-image.sh",
"build:docker:dependencies": "docker build -f hosting/dependencies/Dockerfile -t budibase/dependencies:latest ./hosting",
- "publish:docker:couch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/couchdb/Dockerfile -t budibase/couchdb:latest -t budibase/couchdb:v3.2.1 --push ./hosting/couchdb",
- "publish:docker:couch-sqs": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/couchdb/Dockerfile.v2 -t budibase/couchdb:v3.2.1-sqs --push ./hosting/couchdb",
+ "publish:docker:couch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/couchdb/Dockerfile -t budibase/couchdb:latest -t budibase/couchdb:v3.3.3 --push ./hosting/couchdb",
+ "publish:docker:couch-sqs": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/couchdb/Dockerfile.v2 -t budibase/couchdb:v3.3.3-sqs --push ./hosting/couchdb",
"publish:docker:dependencies": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/dependencies/Dockerfile -t budibase/dependencies:latest -t budibase/dependencies:v3.2.1 --push ./hosting",
"release:helm": "node scripts/releaseHelmChart",
"env:multi:enable": "lerna run --stream env:multi:enable",
diff --git a/packages/bbui/src/Form/Core/Picker.svelte b/packages/bbui/src/Form/Core/Picker.svelte
index c6d4ed3353..88866196a7 100644
--- a/packages/bbui/src/Form/Core/Picker.svelte
+++ b/packages/bbui/src/Form/Core/Picker.svelte
@@ -157,7 +157,7 @@
useAnchorWidth={!autoWidth}
maxWidth={autoWidth ? 400 : null}
customHeight={customPopoverHeight}
- maxHeight={240}
+ maxHeight={360}
>
- import { flip } from "svelte/animate"
- import { dndzone } from "svelte-dnd-action"
- import Icon from "../Icon/Icon.svelte"
- import Popover from "../Popover/Popover.svelte"
- import { onMount } from "svelte"
-
- const flipDurationMs = 150
-
- export let constraints
- export let optionColors = {}
- let options = []
-
- let colorPopovers = []
- let anchors = []
-
- let colorsArray = [
- "hsla(0, 90%, 75%, 0.3)",
- "hsla(50, 80%, 75%, 0.3)",
- "hsla(120, 90%, 75%, 0.3)",
- "hsla(200, 90%, 75%, 0.3)",
- "hsla(240, 90%, 75%, 0.3)",
- "hsla(320, 90%, 75%, 0.3)",
- ]
- const removeInput = idx => {
- delete optionColors[options[idx].name]
- constraints.inclusion = constraints.inclusion.filter((e, i) => i !== idx)
- options = options.filter((e, i) => i !== idx)
- colorPopovers.pop(undefined)
- anchors.pop(undefined)
- }
-
- const addNewInput = () => {
- options = [
- ...options,
- { name: `Option ${constraints.inclusion.length + 1}`, id: Math.random() },
- ]
- constraints.inclusion = [
- ...constraints.inclusion,
- `Option ${constraints.inclusion.length + 1}`,
- ]
-
- colorPopovers.push(undefined)
- anchors.push(undefined)
- }
-
- const handleDndConsider = e => {
- options = e.detail.items
- }
- const handleDndFinalize = e => {
- options = e.detail.items
- constraints.inclusion = options.map(option => option.name)
- }
-
- const handleColorChange = (optionName, color, idx) => {
- optionColors[optionName] = color
- colorPopovers[idx].hide()
- }
-
- const handleNameChange = (optionName, idx, value) => {
- constraints.inclusion[idx] = value
- options[idx].name = value
- optionColors[value] = optionColors[optionName]
- delete optionColors[optionName]
- }
-
- const openColorPickerPopover = (optionIdx, target) => {
- colorPopovers[optionIdx].show()
- anchors[optionIdx] = target
- }
-
- onMount(() => {
- // Initialize anchor arrays on mount, assuming 'options' is already populated
- colorPopovers = constraints.inclusion.map(() => undefined)
- anchors = constraints.inclusion.map(() => undefined)
-
- options = constraints.inclusion.map(value => ({
- name: value,
- id: Math.random(),
- }))
- })
-
-
-
-
-
-
- {#each options as option, idx (option.id)}
-
-
-
-
-
-
openColorPickerPopover(idx, e.target)}
- >
-
-
- {#each colorsArray as color}
-
handleColorChange(option.name, color, idx)}
- style="--color:{color};"
- class="circle circle-hover"
- />
- {/each}
-
-
-
-
-
- handleNameChange(option.name, idx, e.target.value)}
- value={option.name}
- placeholder="Option name"
- />
-
-
-
-
-
- {/each}
-
-
-
-
-
diff --git a/packages/bbui/src/index.js b/packages/bbui/src/index.js
index e746e3a72b..7a4ff3f081 100644
--- a/packages/bbui/src/index.js
+++ b/packages/bbui/src/index.js
@@ -89,7 +89,6 @@ export { default as ListItem } from "./List/ListItem.svelte"
export { default as IconSideNav } from "./IconSideNav/IconSideNav.svelte"
export { default as IconSideNavItem } from "./IconSideNav/IconSideNavItem.svelte"
export { default as Accordion } from "./Accordion/Accordion.svelte"
-export { default as OptionSelectDnD } from "./OptionSelectDnD/OptionSelectDnD.svelte"
export { default as AbsTooltip } from "./Tooltip/AbsTooltip.svelte"
export { TooltipPosition, TooltipType } from "./Tooltip/AbsTooltip.svelte"
diff --git a/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte b/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte
index 1a6bb86113..b012766171 100644
--- a/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte
+++ b/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte
@@ -92,7 +92,6 @@
/>
{:else if type === "attachment"}
{:else if type === "attachment_single"}
source._id === table?.sourceId
@@ -559,9 +561,10 @@
bind:value={editableColumn.constraints.length.maximum}
/>
{:else if editableColumn.type === FieldType.OPTIONS}
-
{:else if editableColumn.type === FieldType.LONGFORM}
@@ -582,9 +585,10 @@
/>
{:else if editableColumn.type === FieldType.ARRAY}
-
{:else if editableColumn.type === DATE_TYPE && !editableColumn.autocolumn}
diff --git a/packages/builder/src/components/backend/DataTable/modals/OptionsEditor.svelte b/packages/builder/src/components/backend/DataTable/modals/OptionsEditor.svelte
new file mode 100644
index 0000000000..8e35e38ae9
--- /dev/null
+++ b/packages/builder/src/components/backend/DataTable/modals/OptionsEditor.svelte
@@ -0,0 +1,252 @@
+
+
+
+
+
+
options.set(e.detail.items)}
+ on:finalize={e => options.set(e.detail.items)}
+ >
+ {#each $enrichedOptions as option (option.id)}
+
+
+
+
+
openColorPicker(option.id)}
+ >
+
+
+
+ {#each OptionColours as colorOption}
+
handleColorChange(option.id, colorOption)}
+ style="--color:{colorOption};"
+ class="circle"
+ class:selected={colorOption === option.color}
+ />
+ {/each}
+
+
+
+
+
handleNameChange(option.id, e.target.value)}
+ />
+
removeInput(option.id)}
+ />
+
+ {/each}
+
+
+
+
+
diff --git a/packages/builder/src/components/design/settings/controls/DataSourceSelect/DataSourceSelect.svelte b/packages/builder/src/components/design/settings/controls/DataSourceSelect/DataSourceSelect.svelte
index bf0aa75bed..e247e4cc88 100644
--- a/packages/builder/src/components/design/settings/controls/DataSourceSelect/DataSourceSelect.svelte
+++ b/packages/builder/src/components/design/settings/controls/DataSourceSelect/DataSourceSelect.svelte
@@ -309,7 +309,7 @@
{#if links?.length}
{
* @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) => {
+export const getSequentialName = (
+ items,
+ prefix,
+ { getName = x => x, numberFirstItem = false } = {}
+) => {
if (!prefix?.length || !getName) {
return null
}
const trimmedPrefix = prefix.trim()
+ const firstName = numberFirstItem ? `${prefix}1` : trimmedPrefix
if (!items?.length) {
- return trimmedPrefix
+ return firstName
}
let max = 0
items.forEach(item => {
@@ -96,5 +101,5 @@ export const getSequentialName = (items, prefix, getName = x => x) => {
max = num
}
})
- return max === 0 ? trimmedPrefix : `${prefix}${max + 1}`
+ return max === 0 ? firstName : `${prefix}${max + 1}`
}
diff --git a/packages/builder/src/helpers/tests/duplicate.test.js b/packages/builder/src/helpers/tests/duplicate.test.js
index 7e51c5ff2a..131e76a6c2 100644
--- a/packages/builder/src/helpers/tests/duplicate.test.js
+++ b/packages/builder/src/helpers/tests/duplicate.test.js
@@ -43,61 +43,71 @@ describe("duplicate", () => {
describe("getSequentialName", () => {
it("handles nullish items", async () => {
- const name = getSequentialName(null, "foo", () => {})
+ 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)
+ const name = getSequentialName([], null)
expect(name).toBe(null)
})
it("handles just the prefix", async () => {
- const name = getSequentialName(["foo"], "foo", x => x)
+ const name = getSequentialName(["foo"], "foo")
expect(name).toBe("foo2")
})
it("handles continuous ranges", async () => {
- const name = getSequentialName(["foo", "foo2", "foo3"], "foo", x => x)
+ const name = getSequentialName(["foo", "foo2", "foo3"], "foo")
expect(name).toBe("foo4")
})
it("handles discontinuous ranges", async () => {
- const name = getSequentialName(["foo", "foo3"], "foo", x => x)
+ const name = getSequentialName(["foo", "foo3"], "foo")
expect(name).toBe("foo4")
})
it("handles a space inside the prefix", async () => {
- const name = getSequentialName(["foo", "foo 2", "foo 3"], "foo ", x => x)
+ const name = getSequentialName(["foo", "foo 2", "foo 3"], "foo ")
expect(name).toBe("foo 4")
})
it("handles a space inside the prefix with just the prefix", async () => {
- const name = getSequentialName(["foo"], "foo ", x => x)
+ const name = getSequentialName(["foo"], "foo ")
expect(name).toBe("foo 2")
})
it("handles no matches", async () => {
- const name = getSequentialName(["aaa", "bbb"], "foo", x => x)
+ const name = getSequentialName(["aaa", "bbb"], "foo")
expect(name).toBe("foo")
})
it("handles similar names", async () => {
- const name = getSequentialName(
- ["fooo1", "2foo", "a3foo4", "5foo5"],
- "foo",
- x => x
- )
+ const name = getSequentialName(["fooo1", "2foo", "a3foo4", "5foo5"], "foo")
expect(name).toBe("foo")
})
it("handles non-string names", async () => {
- const name = getSequentialName([null, 4123, [], {}], "foo", x => x)
+ const name = getSequentialName([null, 4123, [], {}], "foo")
expect(name).toBe("foo")
})
+
+ it("handles deep getters", async () => {
+ const name = getSequentialName([{ a: "foo 1" }], "foo ", {
+ getName: x => x.a,
+ })
+ expect(name).toBe("foo 2")
+ })
+
+ it("handles a mixture of spaces and not", async () => {
+ const name = getSequentialName(["foo", "foo 1", "foo 2"], "foo")
+ expect(name).toBe("foo3")
+ })
+
+ it("handles numbering the first item", async () => {
+ const name = getSequentialName(["foo1", "foo2", "foo"], "foo ", {
+ numberFirstItem: true,
+ })
+ expect(name).toBe("foo 3")
+ })
})
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Navigation/NavItemConfiguration.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Navigation/NavItemConfiguration.svelte
index dffa4bf3ff..db55f501f0 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Navigation/NavItemConfiguration.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Navigation/NavItemConfiguration.svelte
@@ -48,7 +48,9 @@
...navItems,
{
id: generate(),
- text: getSequentialName(navItems, "Nav Item ", x => x.text),
+ text: getSequentialName(navItems, "Nav Item ", {
+ getName: x => x.text,
+ }),
url: "",
roleId: Constants.Roles.BASIC,
type: "link",
diff --git a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
index de527246ca..c999bf6006 100644
--- a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
@@ -29,6 +29,7 @@
focusedCellId,
filter,
inlineFilters,
+ keyboardBlocked,
} = getContext("grid")
const searchableTypes = [
@@ -57,6 +58,8 @@
$: searching = searchValue != null
$: debouncedUpdateFilter(searchValue)
$: orderable = !column.primaryDisplay
+ $: editable = $config.canEditColumns && !column.schema.disabled
+ $: keyboardBlocked.set(open)
const close = () => {
open = false
@@ -231,6 +234,14 @@
}
const debouncedUpdateFilter = debounce(updateFilter, 250)
+ const handleDoubleClick = () => {
+ if (!editable || searching) {
+ return
+ }
+ open = true
+ editColumn()
+ }
+
onMount(() => subscribe("close-edit-column", close))
@@ -241,14 +252,15 @@