diff --git a/lerna.json b/lerna.json index 0606169dd3..81625b3b04 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.2.12-alpha.32", + "version": "2.2.12-alpha.42", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index ca87d6daa4..fa643c995f 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/backend-core", - "version": "2.2.12-alpha.32", + "version": "2.2.12-alpha.42", "description": "Budibase backend core libraries used in server and worker", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -23,7 +23,7 @@ }, "dependencies": { "@budibase/nano": "10.1.1", - "@budibase/types": "2.2.12-alpha.32", + "@budibase/types": "2.2.12-alpha.42", "@shopify/jest-koa-mocks": "5.0.1", "@techpass/passport-openidconnect": "0.3.2", "aws-cloudfront-sign": "2.2.0", diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 6b67ca77d0..f07b5bf0a8 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "2.2.12-alpha.32", + "version": "2.2.12-alpha.42", "license": "MPL-2.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", @@ -38,7 +38,8 @@ ], "dependencies": { "@adobe/spectrum-css-workflow-icons": "1.2.1", - "@budibase/string-templates": "2.2.12-alpha.32", + "@budibase/string-templates": "2.2.12-alpha.42", + "@spectrum-css/accordion": "3.0.24", "@spectrum-css/actionbutton": "1.0.1", "@spectrum-css/actiongroup": "1.0.1", "@spectrum-css/avatar": "3.0.2", diff --git a/packages/bbui/src/Accordion/Accordion.svelte b/packages/bbui/src/Accordion/Accordion.svelte new file mode 100644 index 0000000000..1c88450c9a --- /dev/null +++ b/packages/bbui/src/Accordion/Accordion.svelte @@ -0,0 +1,58 @@ + + +
+
+

+ + +

+
+ +
+
+
+ + diff --git a/packages/bbui/src/ActionButton/ActionButton.svelte b/packages/bbui/src/ActionButton/ActionButton.svelte index cfc810807e..cc4417be2a 100644 --- a/packages/bbui/src/ActionButton/ActionButton.svelte +++ b/packages/bbui/src/ActionButton/ActionButton.svelte @@ -88,6 +88,7 @@ } .is-selected:not(.spectrum-ActionButton--emphasized) { background: var(--spectrum-global-color-gray-300); + border-color: var(--spectrum-global-color-gray-700); } .noPadding { padding: 0; diff --git a/packages/bbui/src/Actions/click_outside.js b/packages/bbui/src/Actions/click_outside.js index 6842b94a32..bdcbaa5d88 100644 --- a/packages/bbui/src/Actions/click_outside.js +++ b/packages/bbui/src/Actions/click_outside.js @@ -1,4 +1,4 @@ -const ignoredClasses = [".flatpickr-calendar"] +const ignoredClasses = [".flatpickr-calendar", ".spectrum-Popover"] let clickHandlers = [] /** @@ -19,7 +19,7 @@ const handleClick = event => { } // Ignore clicks for modals, unless the handler is registered from a modal - const sourceInModal = handler.element.closest(".spectrum-Modal") != null + const sourceInModal = handler.anchor.closest(".spectrum-Modal") != null const clickInModal = event.target.closest(".spectrum-Modal") != null if (clickInModal && !sourceInModal) { return @@ -33,10 +33,10 @@ document.documentElement.addEventListener("click", handleClick, true) /** * Adds or updates a click handler */ -const updateHandler = (id, element, callback) => { +const updateHandler = (id, element, anchor, callback) => { let existingHandler = clickHandlers.find(x => x.id === id) if (!existingHandler) { - clickHandlers.push({ id, element, callback }) + clickHandlers.push({ id, element, anchor, callback }) } else { existingHandler.callback = callback } @@ -51,12 +51,22 @@ const removeHandler = id => { /** * Svelte action to apply a click outside handler for a certain element + * opts.anchor is an optional param specifying the real root source of the + * component being observed. This is required for things like popovers, where + * the element using the clickoutside action is the popover, but the popover is + * rendered at the root of the DOM somewhere, whereas the popover anchor is the + * element we actually want to consider when determining the source component. */ -export default (element, callback) => { +export default (element, opts) => { const id = Math.random() - updateHandler(id, element, callback) + const update = newOpts => { + const callback = newOpts?.callback || newOpts + const anchor = newOpts?.anchor || element + updateHandler(id, element, anchor, callback) + } + update(opts) return { - update: newCallback => updateHandler(id, element, newCallback), + update, destroy: () => removeHandler(id), } } diff --git a/packages/bbui/src/FancyForm/FancyButton.svelte b/packages/bbui/src/FancyForm/FancyButton.svelte new file mode 100644 index 0000000000..09615df8fa --- /dev/null +++ b/packages/bbui/src/FancyForm/FancyButton.svelte @@ -0,0 +1,29 @@ + + + + {#if icon} + {#if icon.includes("/")} + button + {:else} + + {/if} + {/if} +
+ +
+
+ + diff --git a/packages/bbui/src/FancyForm/FancyButtonRadio.svelte b/packages/bbui/src/FancyForm/FancyButtonRadio.svelte new file mode 100644 index 0000000000..510fd8efb8 --- /dev/null +++ b/packages/bbui/src/FancyForm/FancyButtonRadio.svelte @@ -0,0 +1,70 @@ + + + + {#if label} + {label} + {/if} + +
+ {#each options as option} + onChange(getOptionValue(option))} + > + {getOptionLabel(option)} + + {/each} +
+
+ + diff --git a/packages/bbui/src/FancyForm/FancyCheckbox.svelte b/packages/bbui/src/FancyForm/FancyCheckbox.svelte new file mode 100644 index 0000000000..191cc79485 --- /dev/null +++ b/packages/bbui/src/FancyForm/FancyCheckbox.svelte @@ -0,0 +1,53 @@ + + + + + + +
+ {#if text} + {text} + {/if} + +
+
+ + diff --git a/packages/bbui/src/FancyForm/FancyField.svelte b/packages/bbui/src/FancyForm/FancyField.svelte new file mode 100644 index 0000000000..89f2dec7d0 --- /dev/null +++ b/packages/bbui/src/FancyForm/FancyField.svelte @@ -0,0 +1,126 @@ + + +
+
+
+ +
+ {#if error} +
+ +
+ {/if} +
+ {#if error} +
+ {error} +
+ {/if} +
+ + diff --git a/packages/bbui/src/FancyForm/FancyFieldLabel.svelte b/packages/bbui/src/FancyForm/FancyFieldLabel.svelte new file mode 100644 index 0000000000..181cff50e4 --- /dev/null +++ b/packages/bbui/src/FancyForm/FancyFieldLabel.svelte @@ -0,0 +1,25 @@ + + +
+ +
+ + diff --git a/packages/bbui/src/FancyForm/FancyForm.svelte b/packages/bbui/src/FancyForm/FancyForm.svelte new file mode 100644 index 0000000000..f874238572 --- /dev/null +++ b/packages/bbui/src/FancyForm/FancyForm.svelte @@ -0,0 +1,40 @@ + + +
+ +
+ + diff --git a/packages/bbui/src/FancyForm/FancyInput.svelte b/packages/bbui/src/FancyForm/FancyInput.svelte new file mode 100644 index 0000000000..8735e2c30c --- /dev/null +++ b/packages/bbui/src/FancyForm/FancyInput.svelte @@ -0,0 +1,77 @@ + + + + {#if label} + {label} + {/if} + (focused = true)} + on:blur={() => (focused = false)} + class:placeholder + /> + {#if suffix && !placeholder} +
{suffix}
+ {/if} +
+ + diff --git a/packages/bbui/src/FancyForm/FancySelect.svelte b/packages/bbui/src/FancyForm/FancySelect.svelte new file mode 100644 index 0000000000..ee43ecc3ca --- /dev/null +++ b/packages/bbui/src/FancyForm/FancySelect.svelte @@ -0,0 +1,147 @@ + + + (open = true)} +> + {#if label} + {label} + {/if} + +
+ {selectedLabel || ""} +
+ +
+ +
+
+ + (open = false)} + useAnchorWidth={true} + maxWidth={null} +> +
+ {#if options.length} + {#each options as option, idx} +
onChange(getOptionValue(option, idx))} + > + + {getOptionLabel(option, idx)} + + {#if value === getOptionValue(option, idx)} + + {/if} +
+ {/each} + {/if} +
+
+ + diff --git a/packages/bbui/src/FancyForm/index.js b/packages/bbui/src/FancyForm/index.js new file mode 100644 index 0000000000..241036fb35 --- /dev/null +++ b/packages/bbui/src/FancyForm/index.js @@ -0,0 +1,6 @@ +export { default as FancyInput } from "./FancyInput.svelte" +export { default as FancyCheckbox } from "./FancyCheckbox.svelte" +export { default as FancySelect } from "./FancySelect.svelte" +export { default as FancyButton } from "./FancyButton.svelte" +export { default as FancyForm } from "./FancyForm.svelte" +export { default as FancyButtonRadio } from "./FancyButtonRadio.svelte" diff --git a/packages/bbui/src/Form/Core/Select.svelte b/packages/bbui/src/Form/Core/Select.svelte index 3e15b7f6ef..721083e3a6 100644 --- a/packages/bbui/src/Form/Core/Select.svelte +++ b/packages/bbui/src/Form/Core/Select.svelte @@ -18,8 +18,11 @@ export let autoWidth = false export let autocomplete = false export let sort = false + const dispatch = createEventDispatcher() + let open = false + $: fieldText = getFieldText(value, options, placeholder) $: fieldIcon = getFieldAttribute(getOptionIcon, value, options) $: fieldColour = getFieldAttribute(getOptionColour, value, options) diff --git a/packages/bbui/src/Layout/Page.svelte b/packages/bbui/src/Layout/Page.svelte index 15aabd2c61..01111fda9a 100644 --- a/packages/bbui/src/Layout/Page.svelte +++ b/packages/bbui/src/Layout/Page.svelte @@ -18,6 +18,7 @@
+
import "@spectrum-css/link/dist/index-vars.css" + import { createEventDispatcher } from "svelte" export let href = "#" export let size = "M" @@ -9,10 +10,12 @@ export let overBackground = false export let target export let download + + const dispatch = createEventDispatcher() dispatch("click") && e.stopPropagation()} {href} {target} {download} diff --git a/packages/bbui/src/Popover/Popover.svelte b/packages/bbui/src/Popover/Popover.svelte index 83afa2794e..2dc455dfb5 100644 --- a/packages/bbui/src/Popover/Popover.svelte +++ b/packages/bbui/src/Popover/Popover.svelte @@ -75,7 +75,10 @@ useAnchorWidth, showTip, }} - use:clickOutside={dismissible ? handleOutsideClick : () => {}} + use:clickOutside={{ + callback: dismissible ? handleOutsideClick : () => {}, + anchor, + }} on:keydown={handleEscape} class={"spectrum-Popover is-open " + (tooltipClasses || "")} role="presentation" diff --git a/packages/bbui/src/index.js b/packages/bbui/src/index.js index 601c4dcbca..b56aa597ad 100644 --- a/packages/bbui/src/index.js +++ b/packages/bbui/src/index.js @@ -75,6 +75,7 @@ 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 Slider } from "./Form/Slider.svelte" +export { default as Accordion } from "./Accordion/Accordion.svelte" // Renderers export { default as BoldRenderer } from "./Table/BoldRenderer.svelte" @@ -101,3 +102,6 @@ export { banner, BANNER_TYPES } from "./Stores/banner" // Helpers export * as Helpers from "./helpers" + +// Fancy form components +export * from "./FancyForm" diff --git a/packages/bbui/yarn.lock b/packages/bbui/yarn.lock index 72e36a6474..16f1feb920 100644 --- a/packages/bbui/yarn.lock +++ b/packages/bbui/yarn.lock @@ -109,6 +109,11 @@ estree-walker "^1.0.1" picomatch "^2.2.2" +"@spectrum-css/accordion@3.0.24": + version "3.0.24" + resolved "https://registry.yarnpkg.com/@spectrum-css/accordion/-/accordion-3.0.24.tgz#f89066c120c57b0cfc9aba66d60c39fc1cf69f74" + integrity sha512-jNOmUsxmiT3lRLButnN5KKHM94fd+87fjiF8L0c4uRNgJl6ZsBuxPXrM15lV4y1f8D2IACAw01/ZkGRAeaCOFA== + "@spectrum-css/actionbutton@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@spectrum-css/actionbutton/-/actionbutton-1.0.1.tgz#9c75da37ea6915919fb574c74bd60dacc03b6577" diff --git a/packages/builder/package.json b/packages/builder/package.json index 82c939d1f4..5025bec4f3 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "2.2.12-alpha.32", + "version": "2.2.12-alpha.42", "license": "GPL-3.0", "private": true, "scripts": { @@ -71,11 +71,12 @@ } }, "dependencies": { - "@budibase/bbui": "2.2.12-alpha.32", - "@budibase/client": "2.2.12-alpha.32", - "@budibase/frontend-core": "2.2.12-alpha.32", - "@budibase/string-templates": "2.2.12-alpha.32", + "@budibase/bbui": "2.2.12-alpha.42", + "@budibase/client": "2.2.12-alpha.42", + "@budibase/frontend-core": "2.2.12-alpha.42", + "@budibase/string-templates": "2.2.12-alpha.42", "@sentry/browser": "5.19.1", + "@spectrum-css/accordion": "^3.0.24", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", "codemirror": "^5.59.0", diff --git a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte index 05649e1773..6b35f3313f 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte @@ -6,6 +6,7 @@ Toggle, Button, TextArea, + Accordion, } from "@budibase/bbui" import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte" import { capitalise } from "helpers" @@ -51,15 +52,24 @@ let addButton - function getDisplayName(key) { + function getDisplayName(key, fieldKey) { let name - if (schema[key]?.display) { + if (fieldKey && schema[key]["fields"][fieldKey]?.display) { + name = schema[key]["fields"][fieldKey].display + } else if (fieldKey) { + name = fieldKey + } else if (schema[key]?.display) { name = schema[key].display } else { name = key } return capitalise(name) } + function getFieldGroupKeys(fieldGroup) { + return Object.entries(schema[fieldGroup].fields || {}) + .filter(el => filter(el)) + .map(([key]) => key) + }
@@ -100,6 +110,27 @@ error={$validation.errors[configKey]} />
+ {:else if schema[configKey].type === "fieldGroup"} + !!config[fieldKey] + )} + header={getDisplayName(configKey)} + > + + {#each getFieldGroupKeys(configKey) as fieldKey} +
+ + +
+ {/each} +
+
{:else}
diff --git a/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte b/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte index e4cbbfc7aa..e43437d756 100644 --- a/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte +++ b/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte @@ -19,6 +19,8 @@ export let close const colNotSet = "Please specify a column name" + const relationshipAlreadyExists = + "A relationship between these tables already exists." const relationshipTypes = [ { label: "One to Many", @@ -154,6 +156,10 @@ if (!isMany && !fromPrimary) { errObj.fromPrimary = "Please pick the primary key" } + if (isMany && relationshipExists()) { + errObj.fromTable = relationshipAlreadyExists + errObj.toTable = relationshipAlreadyExists + } // currently don't support relationships back onto the table itself, needs to relate out const tableError = "From/to/through tables must be different" @@ -271,6 +277,35 @@ toRelationship = relateTo } + function relationshipExists() { + if ( + originalFromTable && + originalToTable && + originalFromTable === fromTable && + originalToTable === toTable + ) { + return false + } + let fromThroughLinks = Object.values( + datasource.entities[fromTable.name].schema + ).filter(value => value.through) + let toThroughLinks = Object.values( + datasource.entities[toTable.name].schema + ).filter(value => value.through) + + const matchAgainstUserInput = (fromTableId, toTableId) => + (fromTableId === fromId && toTableId === toId) || + (fromTableId === toId && toTableId === fromId) + + return !!fromThroughLinks.find(from => + toThroughLinks.find( + to => + from.through === to.through && + matchAgainstUserInput(from.tableId, to.tableId) + ) + ) + } + function removeExistingRelationship() { if (originalFromTable && originalFromColumnName) { delete datasource.entities[originalFromTable.name].schema[ @@ -332,8 +367,13 @@ bind:error={errors.fromTable} on:change={e => { fromColumn = tableOptions.find(opt => opt.value === e.detail)?.label || "" + if (errors.fromTable === relationshipAlreadyExists) { + errors.toColumn = null + } errors.fromTable = null errors.fromColumn = null + errors.toTable = null + errors.throughTable = null }} /> {#if isManyToOne && fromTable} @@ -352,8 +392,13 @@ bind:error={errors.toTable} on:change={e => { toColumn = tableOptions.find(opt => opt.value === e.detail)?.label || "" + if (errors.toTable === relationshipAlreadyExists) { + errors.fromColumn = null + } errors.toTable = null errors.toColumn = null + errors.fromTable = null + errors.throughTable = null }} /> {#if isManyToMany} @@ -362,6 +407,11 @@ options={tableOptions} bind:value={throughId} bind:error={errors.throughTable} + on:change={() => { + errors.fromTable = null + errors.toTable = null + errors.throughTable = null + }} /> {#if fromTable && toTable && throughTable}