From 3f26c411c92aa47f44bf93968c036d8e303fa044 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Tue, 30 Aug 2022 10:16:15 +0100 Subject: [PATCH] Revert "Revert "Feature/binding ux update"" --- .../bbui/src/Actions/position_dropdown.js | 21 +- packages/bbui/src/Popover/Popover.svelte | 3 +- .../cypress/integration/createBinding.spec.js | 10 +- .../builder/src/builderStore/dataBinding.js | 39 ++- .../common/bindings/BindingPanel.svelte | 273 ++++++++++++++---- .../common/bindings/ClientBindingPanel.svelte | 1 - packages/client/manifest.json | 48 ++- 7 files changed, 305 insertions(+), 90 deletions(-) diff --git a/packages/bbui/src/Actions/position_dropdown.js b/packages/bbui/src/Actions/position_dropdown.js index a25cc1bbd5..7570a39c8c 100644 --- a/packages/bbui/src/Actions/position_dropdown.js +++ b/packages/bbui/src/Actions/position_dropdown.js @@ -1,4 +1,4 @@ -export default function positionDropdown(element, { anchor, align }) { +export default function positionDropdown(element, { anchor, align, maxWidth }) { let positionSide = "top" let maxHeight = 0 let dimensions = getDimensions(anchor) @@ -34,13 +34,24 @@ export default function positionDropdown(element, { anchor, align }) { } function calcLeftPosition() { - return align === "right" - ? dimensions.left + dimensions.width - dimensions.containerWidth - : dimensions.left + let left + + if (align == "right") { + left = dimensions.left + dimensions.width - dimensions.containerWidth + } else if (align == "right-side") { + left = dimensions.left + dimensions.width + } else { + left = dimensions.left + } + + return left } element.style.position = "absolute" element.style.zIndex = "9999" + if (maxWidth) { + element.style.maxWidth = `${maxWidth}px` + } element.style.minWidth = `${dimensions.width}px` element.style.maxHeight = `${maxHeight.toFixed(0)}px` element.style.transformOrigin = `center ${positionSide}` @@ -54,10 +65,8 @@ export default function positionDropdown(element, { anchor, align }) { element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px` }) }) - resizeObserver.observe(anchor) resizeObserver.observe(element) - return { destroy() { resizeObserver.disconnect() diff --git a/packages/bbui/src/Popover/Popover.svelte b/packages/bbui/src/Popover/Popover.svelte index 756e5e6a09..91581724d5 100644 --- a/packages/bbui/src/Popover/Popover.svelte +++ b/packages/bbui/src/Popover/Popover.svelte @@ -11,6 +11,7 @@ export let align = "right" export let portalTarget export let dataCy + export let maxWidth export let direction = "bottom" export let showTip = false @@ -45,7 +46,7 @@
{ it("should add a current user binding", () => { cy.searchAndAddComponent("Paragraph").then(() => { - addSettingBinding("text", "Current User._id") + addSettingBinding("text", ["Current User", "_id"], "Current User._id") }) }) @@ -28,7 +28,7 @@ filterTests(['smoke', 'all'], () => { const paramName = "foo" cy.createScreen(`/test/:${paramName}`) cy.searchAndAddComponent("Paragraph").then(componentId => { - addSettingBinding("text", `URL.${paramName}`) + addSettingBinding("text", ["URL", paramName], `URL.${paramName}`) // The builder preview pages don't have a real URL, so all we can do // is check that we were able to bind to the property, and that the // component exists on the page @@ -47,11 +47,13 @@ filterTests(['smoke', 'all'], () => { }) }) - const addSettingBinding = (setting, bindingText, clickOption = true) => { + const addSettingBinding = (setting, bindingCategories, bindingText, clickOption = true) => { cy.get(`[data-cy="setting-${setting}"] [data-cy=text-binding-button]`).click() + cy.get(".category-list li").contains(bindingCategories[0]) cy.get(".drawer").within(() => { if (clickOption) { - cy.contains(bindingText).click() + cy.get(".category-list li").contains(bindingCategories[0]).click() + cy.get("li.binding").contains(bindingCategories[1]).click() cy.get("textarea").should("have.value", `{{ ${bindingText} }}`) } else { cy.get("textarea").type(bindingText) diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index 13b749e19f..d961a3a1cd 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -299,7 +299,10 @@ const getProviderContextBindings = (asset, dataProviders) => { schema = {} const values = context.values || [] values.forEach(value => { - schema[value.key] = { name: value.label, type: "string" } + schema[value.key] = { + name: value.label, + type: value.type || "string", + } }) } else if (context.type === "schema") { // Schema contexts are generated dynamically depending on their data @@ -359,6 +362,12 @@ const getProviderContextBindings = (asset, dataProviders) => { providerId, // Table ID is used by JSON fields to know what table the field is in tableId: table?._id, + category: component._instanceName, + icon: def.icon, + display: { + name: fieldSchema.name || key, + type: fieldSchema.type, + }, }) }) }) @@ -385,6 +394,9 @@ const getUserBindings = () => { // datasource options, based on bindable properties fieldSchema, providerId: "user", + category: "Current User", + icon: "User", + display: fieldSchema, }) }) return bindings @@ -401,11 +413,17 @@ const getDeviceBindings = () => { type: "context", runtimeBinding: `${safeDevice}.${makePropSafe("mobile")}`, readableBinding: `Device.Mobile`, + category: "Device", + icon: "DevicePhone", + display: { type: "boolean", name: "mobile" }, }) bindings.push({ type: "context", runtimeBinding: `${safeDevice}.${makePropSafe("tablet")}`, readableBinding: `Device.Tablet`, + category: "Device", + icon: "DevicePhone", + display: { type: "boolean", name: "tablet" }, }) } return bindings @@ -429,6 +447,8 @@ const getSelectedRowsBindings = asset => { "selectedRows" )}`, readableBinding: `${table._instanceName}.Selected rows`, + category: "Selected rows", + icon: "ViewRow", })) ) @@ -460,6 +480,9 @@ const getStateBindings = () => { type: "context", runtimeBinding: `${safeState}.${makePropSafe(key)}`, readableBinding: `State.${key}`, + category: "State", + icon: "AutomatedSegment", + display: { name: key }, })) } return bindings @@ -482,11 +505,17 @@ const getUrlBindings = asset => { type: "context", runtimeBinding: `${safeURL}.${makePropSafe(param)}`, readableBinding: `URL.${param}`, + category: "URL", + icon: "RailTop", + display: { type: "string" }, })) const queryParamsBinding = { type: "context", runtimeBinding: makePropSafe("query"), readableBinding: "Query params", + category: "URL", + icon: "RailTop", + display: { type: "object" }, } return urlParamBindings.concat([queryParamsBinding]) } @@ -497,6 +526,9 @@ const getRoleBindings = () => { type: "context", runtimeBinding: `trim "${role._id}"`, readableBinding: `Role.${role.name}`, + category: "Role", + icon: "UserGroup", + display: { type: "string", name: role.name }, } }) } @@ -518,6 +550,7 @@ export const getEventContextBindings = ( // Check if any context bindings are provided by the component for this // setting const component = findComponent(asset.props, componentId) + const def = store.actions.components.getDefinition(component?._component) const settings = getComponentSettings(component?._component) const eventSetting = settings.find(setting => setting.key === settingKey) if (eventSetting?.context?.length) { @@ -527,6 +560,8 @@ export const getEventContextBindings = ( runtimeBinding: `${makePropSafe("eventContext")}.${makePropSafe( contextEntry.key )}`, + category: component._instanceName, + icon: def.icon, }) }) } @@ -548,6 +583,8 @@ export const getEventContextBindings = ( bindings.push({ readableBinding: `Action ${idx + 1}.${contextValue.label}`, runtimeBinding: `actions.${idx}.${contextValue.value}`, + category: "Actions", + icon: "JourneyAction", }) }) } diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index 49cbd434cf..ffa0e98819 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -9,6 +9,9 @@ Body, Layout, Button, + ActionButton, + Icon, + Popover, } from "@budibase/bbui" import { createEventDispatcher, onMount } from "svelte" import { @@ -45,9 +48,25 @@ let jsValue = initialValueJS ? value : null let hbsValue = initialValueJS ? null : value + let selectedCategory = null + + let popover + let popoverAnchor + let hoverTarget + $: usingJS = mode === "JavaScript" $: searchRgx = new RegExp(search, "ig") $: categories = Object.entries(groupBy("category", bindings)) + + $: bindingIcons = bindings?.reduce((acc, ele) => { + if (ele.icon) { + acc[ele.category] = acc[ele.category] || ele.icon + } + return acc + }, {}) + + $: categoryIcons = { ...bindingIcons, Helpers: "MagicWand" } + $: filteredCategories = categories .map(([name, categoryBindings]) => ({ name, @@ -55,10 +74,19 @@ return binding.readableBinding.match(searchRgx) }), })) - .filter(category => category.bindings?.length > 0) + .filter(category => { + return ( + category.bindings?.length > 0 && + (!selectedCategory ? true : selectedCategory === category.name) + ) + }) + $: filteredHelpers = helpers?.filter(helper => { return helper.label.match(searchRgx) || helper.description.match(searchRgx) }) + + $: categoryNames = [...categories.map(cat => cat[0]), "Helpers"] + $: codeMirrorHints = bindings?.map(x => `$("${x.readableBinding}")`) const updateValue = val => { @@ -140,58 +168,163 @@ }) + + + +
+ {#if hoverTarget.title} +
{hoverTarget.title}
+ {/if} + {#if hoverTarget.description} +
+ {@html hoverTarget.description} +
+ {/if} + {#if hoverTarget.example} +
{hoverTarget.example}
+ {/if} +
+
+
+
+ -
-
+ + {#if selectedCategory} +
+ { + selectedCategory = null + }} + > + Back + +
+ {/if} + + {#if !selectedCategory}
Search
-
- {#each filteredCategories as category} - {#if category.bindings?.length} -
-
{category.name}
+ {/if} + + {#if !selectedCategory && !search} +
    + {#each categoryNames as categoryName} +
  • { + selectedCategory = categoryName + }} + > + + {categoryName} + +
  • + {/each} +
+ {/if} + + {#if selectedCategory || search} + {#each filteredCategories as category} + {#if category.bindings?.length} +
+ {category.name} +
    {#each category.bindings as binding} -
  • addBinding(binding)}> - {binding.readableBinding} - {#if binding.type} - {binding.type} - {/if} - {#if binding.description} -
    -
    - {binding.description || ""} -
    +
  • { + popoverAnchor = e.target + if (!binding.description) { + return + } + hoverTarget = { + title: binding.display.name || binding.fieldSchema.name, + description: binding.description, + } + popover.show() + e.stopPropagation() + }} + on:mouseleave={() => { + popover.hide() + popoverAnchor = null + hoverTarget = null + }} + on:focus={() => {}} + on:blur={() => {}} + on:click={() => addBinding(binding)} + > + + {#if binding.display?.name} + {binding.display.name} + {:else if binding.fieldSchema?.name} + {binding.fieldSchema?.name} + {:else} + {binding.readableBinding} + {/if} + + + {#if binding.display?.type || binding.fieldSchema?.type} + + + {binding.display?.type || binding.fieldSchema?.type} + + {/if}
  • {/each}
-
+ {/if} + {/each} + + {#if selectedCategory === "Helpers" || search} + {#if filteredHelpers?.length} +
Helpers
+
    + {#each filteredHelpers as helper} +
  • addHelper(helper, usingJS)} + on:mouseenter={e => { + popoverAnchor = e.target + if (!helper.displayText && helper.description) { + return + } + hoverTarget = { + title: helper.displayText, + description: helper.description, + example: getHelperExample(helper, usingJS), + } + popover.show() + e.stopPropagation() + }} + on:mouseleave={() => { + popover.hide() + popoverAnchor = null + hoverTarget = null + }} + on:focus={() => {}} + on:blur={() => {}} + > + {helper.displayText} + + function + +
  • + {/each} +
+ {/if} {/if} - {/each} - {#if filteredHelpers?.length} -
-
Helpers
-
    - {#each filteredHelpers as helper} -
  • addHelper(helper, usingJS)}> -
    -
    {helper.displayText}
    -
    - {@html helper.description} -
    -
    {getHelperExample(
    -                      helper,
    -                      usingJS
    -                    )}
    -
    -
  • - {/each} -
-
{/if} -
+
@@ -241,6 +374,35 @@