diff --git a/packages/bbui/src/Icon/Icon.svelte b/packages/bbui/src/Icon/Icon.svelte index e293bd408b..6ae1f4ca67 100644 --- a/packages/bbui/src/Icon/Icon.svelte +++ b/packages/bbui/src/Icon/Icon.svelte @@ -29,6 +29,7 @@ >
+ import { contextMenuStore } from "stores/builder" + import { Popover, Menu, MenuItem } from "@budibase/bbui" + + let dropdown + let anchor + + const handleKeyDown = () => { + if ($contextMenuStore.visible) { + contextMenuStore.close() + } + } + + const handleItemClick = async itemCallback => { + await itemCallback() + contextMenuStore.close() + } + + + +{#key $contextMenuStore.position} +
+{/key} + + + + {#each $contextMenuStore.items as item} + {#if item.visible} + handleItemClick(item.callback)} + disabled={item.disabled} + > + {item.name} + + {/if} + {/each} + + + + diff --git a/packages/builder/src/components/automation/AutomationPanel/AutomationList.svelte b/packages/builder/src/components/automation/AutomationPanel/AutomationList.svelte deleted file mode 100644 index 9d946fe55d..0000000000 --- a/packages/builder/src/components/automation/AutomationPanel/AutomationList.svelte +++ /dev/null @@ -1,48 +0,0 @@ - - -
- {#each $automationStore.automations.sort(aut => aut.name) as automation} - selectAutomation(automation._id)} - selectedBy={$userSelectedResourceMap[automation._id]} - > - - - {/each} -
- - diff --git a/packages/builder/src/components/automation/AutomationPanel/AutomationNavItem.svelte b/packages/builder/src/components/automation/AutomationPanel/AutomationNavItem.svelte new file mode 100644 index 0000000000..31f86f5d78 --- /dev/null +++ b/packages/builder/src/components/automation/AutomationPanel/AutomationNavItem.svelte @@ -0,0 +1,123 @@ + + + automationStore.actions.select(automation._id)} + selectedBy={$userSelectedResourceMap[automation._id]} + disabled={automation.disabled} +> +
+ +
+
+ + + Are you sure you wish to delete the automation + {automation.name}? + This action cannot be undone. + + + + diff --git a/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte b/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte index b5fe6d03fd..c3ef3157bd 100644 --- a/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte +++ b/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte @@ -3,20 +3,13 @@ import { Modal, notifications, Layout } from "@budibase/bbui" import NavHeader from "components/common/NavHeader.svelte" import { onMount } from "svelte" - import { - automationStore, - selectedAutomation, - userSelectedResourceMap, - } from "stores/builder" - import NavItem from "components/common/NavItem.svelte" - import EditAutomationPopover from "./EditAutomationPopover.svelte" + import { automationStore } from "stores/builder" + import AutomationNavItem from "./AutomationNavItem.svelte" export let modal export let webhookModal let searchString - $: selectedAutomationId = $selectedAutomation?._id - $: filteredAutomations = $automationStore.automations .filter(automation => { return ( @@ -49,10 +42,6 @@ notifications.error("Error getting automations list") } }) - - function selectAutomation(id) { - automationStore.actions.select(id) - } {#each triggerGroup.entries as automation} - selectAutomation(automation._id)} - selectedBy={$userSelectedResourceMap[automation._id]} - disabled={automation.disabled} - > - - + {/each}
{/each} diff --git a/packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte b/packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte deleted file mode 100644 index 9465374ae2..0000000000 --- a/packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte +++ /dev/null @@ -1,73 +0,0 @@ - - - -
- -
- Duplicate - Edit - - {automation.disabled ? "Activate" : "Pause"} - - Delete -
- - - Are you sure you wish to delete the automation - {automation.name}? - This action cannot be undone. - - - - diff --git a/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte b/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte index 32e38803b3..0d4361954f 100644 --- a/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte +++ b/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte @@ -79,18 +79,18 @@ table = $tables.list.find(table => table._id === tableId) + schemaFields = Object.entries(table?.schema ?? {}) + .filter(entry => { + const [, field] = entry + return field.type !== "formula" && !field.autocolumn + }) + .sort(([nameA], [nameB]) => { + return nameA < nameB ? -1 : 1 + }) + if (table) { editableRow["tableId"] = tableId - schemaFields = Object.entries(table?.schema ?? {}) - .filter(entry => { - const [, field] = entry - return field.type !== "formula" && !field.autocolumn - }) - .sort(([nameA], [nameB]) => { - return nameA < nameB ? -1 : 1 - }) - // Parse out any data not in the schema. for (const column in editableFields) { if (!Object.hasOwn(table?.schema, column)) { diff --git a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavItem/DatasourceNavItem.svelte b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavItem/DatasourceNavItem.svelte new file mode 100644 index 0000000000..1ba32838ab --- /dev/null +++ b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavItem/DatasourceNavItem.svelte @@ -0,0 +1,82 @@ + + + +
+ +
+ {#if datasource._id !== BUDIBASE_INTERNAL_DB_ID} + + {/if} +
+ + + + diff --git a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavItem/DeleteConfirmationModal.svelte similarity index 50% rename from packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte rename to packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavItem/DeleteConfirmationModal.svelte index 79efd276f8..13380c2700 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavItem/DeleteConfirmationModal.svelte @@ -1,15 +1,16 @@ - -
- -
- {#if datasource.type !== BUDIBASE_DATASOURCE_TYPE} - Edit - {/if} - Delete -
- {datasource.name}? This action cannot be undone. - - - diff --git a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte index 23081c92c4..e0745c15a1 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte @@ -1,7 +1,6 @@ + + goto(`./query/${query._id}`)} + selectedBy={$userSelectedResourceMap[query._id]} +> + + + + + Are you sure you wish to delete this query? This action cannot be undone. + + + diff --git a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditQueryPopover.svelte b/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditQueryPopover.svelte deleted file mode 100644 index d77c5cc8c0..0000000000 --- a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditQueryPopover.svelte +++ /dev/null @@ -1,59 +0,0 @@ - - - -
- -
- Delete - Duplicate -
- - - Are you sure you wish to delete this query? This action cannot be undone. - - - diff --git a/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte b/packages/builder/src/components/backend/TableNavigator/TableNavItem/DeleteConfirmationModal.svelte similarity index 54% rename from packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte rename to packages/builder/src/components/backend/TableNavigator/TableNavItem/DeleteConfirmationModal.svelte index f2c726c8bf..21a763be08 100644 --- a/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte +++ b/packages/builder/src/components/backend/TableNavigator/TableNavItem/DeleteConfirmationModal.svelte @@ -1,35 +1,15 @@ - -
- -
- {#if !externalTable} - Edit - {/if} - Delete -
- - - -
editTableNameModal.confirm()}> - -
-
-
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentTree.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentTree.svelte index 0219dc304d..997fac6f10 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentTree.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentTree.svelte @@ -1,7 +1,6 @@ @@ -93,6 +106,7 @@ {#each filteredComponents || [] as component, index (component._id)} {@const opened = isOpen(component, openNodes)}
  • openContextMenu(e, component, opened)} on:click|stopPropagation={() => { componentStore.select(component._id) }} @@ -107,7 +121,8 @@ on:dragover={dragover(component, index)} on:iconClick={() => handleIconClick(component._id)} on:drop={onDrop} - hovering={$hoverStore.componentId === component._id} + hovering={$hoverStore.componentId === component._id || + component._id === $contextMenuStore.id} on:mouseenter={() => hover(component._id)} on:mouseleave={() => hover(null)} text={getComponentText(component)} @@ -120,7 +135,12 @@ highlighted={isChildOfSelectedComponent(component)} selectedBy={$userSelectedResourceMap[component._id]} > - + openContextMenu(e, component, opened)} + /> {#if opened} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ScreenslotDropdownMenu.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ScreenslotDropdownMenu.svelte deleted file mode 100644 index ddb1630644..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ScreenslotDropdownMenu.svelte +++ /dev/null @@ -1,57 +0,0 @@ - - -{#if showMenu} - -
    - -
    - storeComponentForCopy(false)} - > - Copy - - pasteComponent("inside")} - disabled={noPaste} - > - Paste - -
    -{/if} - - diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/getComponentContextMenuItems.js b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/getComponentContextMenuItems.js new file mode 100644 index 0000000000..f2dfb73a68 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/getComponentContextMenuItems.js @@ -0,0 +1,123 @@ +import { get } from "svelte/store" +import { componentStore } from "stores/builder" + +const getContextMenuItems = (component, componentCollapsed) => { + const definition = componentStore.getDefinition(component?._component) + const noPaste = !get(componentStore).componentToPaste + const isBlock = definition?.block === true + const canEject = !(definition?.ejectable === false) + const hasChildren = component?._children?.length + + const keyboardEvent = (key, ctrlKey = false) => { + document.dispatchEvent( + new CustomEvent("component-menu", { + detail: { + key, + ctrlKey, + id: component?._id, + }, + }) + ) + } + + return [ + { + icon: "Delete", + name: "Delete", + keyBind: "!BackAndroid", + visible: true, + disabled: false, + callback: () => keyboardEvent("Delete"), + }, + { + icon: "ChevronUp", + name: "Move up", + keyBind: "Ctrl+!ArrowUp", + visible: true, + disabled: false, + callback: () => keyboardEvent("ArrowUp", true), + }, + { + icon: "ChevronDown", + name: "Move down", + keyBind: "Ctrl+!ArrowDown", + visible: true, + disabled: false, + callback: () => keyboardEvent("ArrowDown", true), + }, + { + icon: "Duplicate", + name: "Duplicate", + keyBind: "Ctrl+D", + visible: true, + disabled: false, + callback: () => keyboardEvent("d", true), + }, + { + icon: "Cut", + name: "Cut", + keyBind: "Ctrl+X", + visible: true, + disabled: false, + callback: () => keyboardEvent("x", true), + }, + { + icon: "Copy", + name: "Copy", + keyBind: "Ctrl+C", + visible: true, + disabled: false, + callback: () => keyboardEvent("c", true), + }, + { + icon: "LayersSendToBack", + name: "Paste", + keyBind: "Ctrl+V", + visible: true, + disabled: noPaste, + callback: () => keyboardEvent("v", true), + }, + { + icon: "Export", + name: "Eject block", + keyBind: "Ctrl+E", + visible: isBlock && canEject, + disabled: false, + callback: () => keyboardEvent("e", true), + }, + { + icon: "TreeExpand", + name: "Expand", + keyBind: "!ArrowRight", + visible: hasChildren, + disabled: !componentCollapsed, + callback: () => keyboardEvent("ArrowRight", false), + }, + { + icon: "TreeExpandAll", + name: "Expand All", + keyBind: "Ctrl+!ArrowRight", + visible: hasChildren, + disabled: !componentCollapsed, + callback: () => keyboardEvent("ArrowRight", true), + }, + { + icon: "TreeCollapse", + name: "Collapse", + keyBind: "!ArrowLeft", + visible: hasChildren, + disabled: componentCollapsed, + callback: () => keyboardEvent("ArrowLeft", false), + }, + { + icon: "TreeCollapseAll", + name: "Collapse All", + keyBind: "Ctrl+!ArrowLeft", + visible: hasChildren, + disabled: componentCollapsed, + callback: () => keyboardEvent("ArrowLeft", true), + }, + ] +} + +export default getContextMenuItems diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/getScreenContextMenuItems.js b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/getScreenContextMenuItems.js new file mode 100644 index 0000000000..25f2e908e6 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/getScreenContextMenuItems.js @@ -0,0 +1,40 @@ +import { get } from "svelte/store" +import { componentStore } from "stores/builder" +import { notifications } from "@budibase/bbui" + +const getContextMenuItems = (component, showCopy) => { + const noPaste = !get(componentStore).componentToPaste + + const storeComponentForCopy = (cut = false) => { + componentStore.copy(component, cut) + } + + const pasteComponent = mode => { + try { + componentStore.paste(component, mode) + } catch (error) { + notifications.error("Error saving component") + } + } + + return [ + { + icon: "Copy", + name: "Copy", + keyBind: "Ctrl+C", + visible: showCopy, + disabled: false, + callback: () => storeComponentForCopy(false), + }, + { + icon: "LayersSendToBack", + name: "Paste", + keyBind: "Ctrl+V", + visible: true, + disabled: noPaste, + callback: () => pasteComponent("inside"), + }, + ] +} + +export default getContextMenuItems diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/index.svelte index 4a6716ebc5..fce8c12800 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/index.svelte @@ -7,14 +7,15 @@ componentStore, userSelectedResourceMap, hoverStore, + contextMenuStore, } from "stores/builder" import NavItem from "components/common/NavItem.svelte" import ComponentTree from "./ComponentTree.svelte" import { dndStore, DropPosition } from "./dndStore.js" - import ScreenslotDropdownMenu from "./ScreenslotDropdownMenu.svelte" import DNDPositionIndicator from "./DNDPositionIndicator.svelte" import ComponentKeyHandler from "./ComponentKeyHandler.svelte" import ComponentScrollWrapper from "./ComponentScrollWrapper.svelte" + import getScreenContextMenuItems from "./getScreenContextMenuItems" let scrolling = false @@ -43,6 +44,32 @@ } const hover = hoverStore.hover + + // showCopy is used to hide the copy button when the user right-clicks the empty + // background of their component tree. Pasting in the empty space makes sense, + // but copying it doesn't + const openScreenContextMenu = (e, showCopy) => { + const screenComponent = $selectedScreen?.props + const definition = componentStore.getDefinition(screenComponent?._component) + // "editable" has been repurposed for inline text editing. + // It remains here for legacy compatibility. + // Future components should define "static": true for indicate they should + // not show a context menu. + if (definition?.editable !== false && definition?.static !== true) { + e.preventDefault() + e.stopPropagation() + + const items = getScreenContextMenuItems(screenComponent, showCopy) + contextMenuStore.open( + `${showCopy ? "background-" : ""}screenComponent._id`, + items, + { + x: e.clientX, + y: e.clientY, + } + ) + } + } @@ -56,8 +83,11 @@
  • -
    {#if noScreens} @@ -155,6 +181,7 @@ /> {/if} + diff --git a/packages/builder/src/pages/builder/portal/apps/_components/PortalSideBar.svelte b/packages/builder/src/pages/builder/portal/apps/_components/PortalSideBar.svelte index e9cd170c0b..0f72accf9f 100644 --- a/packages/builder/src/pages/builder/portal/apps/_components/PortalSideBar.svelte +++ b/packages/builder/src/pages/builder/portal/apps/_components/PortalSideBar.svelte @@ -1,11 +1,9 @@