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} -
-
- -
Add option
-
-
- - 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} +
+
+ +
Add option
+
+
+ + 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 @@
{:else} - + Edit column import { Icon } from "@budibase/bbui" - import { getColor } from "../lib/utils" import { onMount } from "svelte" import GridPopover from "../overlays/GridPopover.svelte" + import { OptionColours } from "../../../constants" export let value export let schema @@ -13,6 +13,8 @@ export let api export let contentLines = 1 + const InvalidColor = "hsla(0, 0%, 70%, 0.3)" + let isOpen = false let focusedOptionIdx = null let anchor @@ -38,8 +40,11 @@ } const getOptionColor = value => { - const index = value ? options.indexOf(value) : null - return getColor(index) + let idx = value ? options.indexOf(value) : null + if (idx == null || idx === -1) { + return InvalidColor + } + return OptionColours[idx % OptionColours.length] } const toggleOption = option => { diff --git a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte index 8ebe64ffb6..462d53445d 100644 --- a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte @@ -1,9 +1,9 @@