diff --git a/frontend/src/.d.ts b/frontend/src/.d.ts index aba6221e1c..5efbfab63d 100644 --- a/frontend/src/.d.ts +++ b/frontend/src/.d.ts @@ -1,5 +1,6 @@ declare namespace svelte.JSX { interface DOMAttributes { onclick_outside?: CompositionEventHandler + onpointerdown_outside?: (event: CustomEvent) => void } } diff --git a/frontend/src/lib/components/apps/editor/AppEditor.svelte b/frontend/src/lib/components/apps/editor/AppEditor.svelte index 9eb1afc069..435942a7e3 100644 --- a/frontend/src/lib/components/apps/editor/AppEditor.svelte +++ b/frontend/src/lib/components/apps/editor/AppEditor.svelte @@ -37,13 +37,11 @@ import { findGridItem, findGridItemParentGrid } from './appUtils' import ComponentNavigation from './component/ComponentNavigation.svelte' import CssSettings from './componentsPanel/CssSettings.svelte' - import ConnectionInstructions from './ConnectionInstructions.svelte' import SettingsPanel from './SettingsPanel.svelte' import { SecondaryMenu, secondaryMenuLeft, secondaryMenuLeftStore, - secondaryMenuRight, secondaryMenuRightStore } from './settingsPanel/secondaryMenu' import Popover from '../../Popover.svelte' @@ -294,15 +292,6 @@ hasResult: writable>({}) }) - $: if ($connectingInput.opened) { - secondaryMenuRight.open(ConnectionInstructions, {}, () => { - $connectingInput.opened = false - }) - secondaryMenuLeft.close() - } else { - secondaryMenuRight.close() - } - function onThemeChange() { $darkMode = document.documentElement.classList.contains('dark') } @@ -324,28 +313,6 @@ let toggled = false let cssToggled = false - $: if ($connectingInput.opened && !toggled) { - tmpRunnablePanelSize = runnablePanelSize - tmpGridPanelSize = gridPanelSize - - animateTo(runnablePanelSize, 0, (newValue: number) => (runnablePanelSize = newValue)) - animateTo(gridPanelSize, 100, (newValue: number) => (gridPanelSize = newValue)) - - toggled = true - } else if (!$connectingInput.opened && toggled) { - animateTo( - runnablePanelSize, - tmpRunnablePanelSize, - (newValue: number) => (runnablePanelSize = newValue) - ) - animateTo(gridPanelSize, tmpGridPanelSize, (newValue: number) => (gridPanelSize = newValue)) - - tmpRunnablePanelSize = -1 - tmpGridPanelSize = -1 - - toggled = false - } - // Animation logic for cssInput $: animateCssInput($cssEditorOpen) $: $cssEditorOpen && @@ -546,27 +513,58 @@ let centerPanelWidth = 0 - function hideLeftPanel() { + function hideLeftPanel(animate: boolean = false) { storedLeftPanelSize = leftPanelSize - leftPanelSize = 0 + if (animate) { + animateTo(leftPanelSize, 0, (newValue: number) => (leftPanelSize = newValue)) + } else { + leftPanelSize = 0 + } centerPanelSize = centerPanelSize + storedLeftPanelSize + if ($connectingInput.opened) { + $connectingInput.opened = false + } } function hideRightPanel() { storedRightPanelSize = rightPanelSize rightPanelSize = 0 centerPanelSize = centerPanelSize + storedRightPanelSize + if ($connectingInput.opened) { + $connectingInput.opened = false + } } - function hideBottomPanel() { + function hideBottomPanel(animate: boolean = false) { + if (runnablePanelSize === 0) { + return + } storedBottomPanelSize = runnablePanelSize - gridPanelSize = 99 - runnablePanelSize = 0 + if (animate) { + tmpRunnablePanelSize = runnablePanelSize + tmpGridPanelSize = gridPanelSize + + animateTo(runnablePanelSize, 0, (newValue: number) => (runnablePanelSize = newValue)) + animateTo(gridPanelSize, 100, (newValue: number) => (gridPanelSize = newValue)) + } else { + runnablePanelSize = 0 + gridPanelSize = 99 + } } - function showLeftPanel() { - leftPanelSize = storedLeftPanelSize - centerPanelSize = centerPanelSize - storedLeftPanelSize + function showLeftPanel(animate: boolean = false) { + if (leftPanelSize !== 0) { + return + } + if (animate) { + animateTo( + leftPanelSize, + storedLeftPanelSize, + (newValue: number) => (leftPanelSize = newValue) + ) + } else { + leftPanelSize = storedLeftPanelSize + } storedLeftPanelSize = 0 } @@ -576,9 +574,25 @@ storedRightPanelSize = 0 } - function showBottomPanel() { - runnablePanelSize = storedBottomPanelSize - gridPanelSize = gridPanelSize - storedBottomPanelSize + function showBottomPanel(animate: boolean = false) { + if (runnablePanelSize !== 0) { + return + } + if (animate) { + animateTo( + runnablePanelSize, + storedBottomPanelSize, + (newValue: number) => (runnablePanelSize = newValue) + ) + animateTo( + gridPanelSize, + gridPanelSize - storedBottomPanelSize, + (newValue: number) => (gridPanelSize = newValue) + ) + } else { + runnablePanelSize = storedBottomPanelSize + gridPanelSize = gridPanelSize - storedBottomPanelSize + } storedBottomPanelSize = 0 } @@ -630,10 +644,56 @@ } break } + + case 'Escape': { + if ($connectingInput.opened) { + $connectingInput.opened = false + } + break + } } } + + let previousLeftPanelHidden: boolean = false + let previousBottomPanelHidden: boolean = false + + async function updatePannelInConnecting() { + if ($connectingInput.opened && !toggled) { + previousLeftPanelHidden = leftPanelSize === 0 + previousBottomPanelHidden = runnablePanelSize === 0 + if (previousLeftPanelHidden) { + showLeftPanel(true) + } + if (!previousBottomPanelHidden) { + hideBottomPanel(true) + } + secondaryMenuLeft.close() + toggled = true + } else if (!$connectingInput.opened && toggled) { + if (previousLeftPanelHidden) { + hideLeftPanel(true) + } + if (!previousBottomPanelHidden) { + showBottomPanel(true) + } + toggled = false + } + } + + $: $connectingInput.opened, updatePannelInConnecting() + let testJob: Job | undefined = undefined let jobToWatch: { componentId: string; job: string } | undefined = undefined + + $: updateCursorStyle(!!$connectingInput.opened) + + function updateCursorStyle(disabled: boolean) { + if (disabled) { + document.documentElement.style.setProperty('--global-cursor', 'not-allowed', 'important') + } else { + document.documentElement.style.removeProperty('--global-cursor') + } + } @@ -704,6 +764,12 @@ {/if} + {#if $connectingInput.opened} +
+ +
+ {/if} + @@ -739,7 +805,7 @@
showLeftPanel()} - direction="right" + direction="left" hidden btnClasses="border bg-surface" /> @@ -749,7 +815,7 @@
showRightPanel()} - direction="left" + direction="right" hidden btnClasses="border bg-surface" /> @@ -811,7 +877,9 @@ style={$componentActive ? `top: -${$yTop}px;` : ''} > {#if $appStore.grid} - + {#if !$connectingInput?.opened} + + {/if}
- { - storedRightPanelSize = rightPanelSize - rightPanelSize = 0 - centerPanelSize = centerPanelSize + storedRightPanelSize - }} - direction="right" - /> + hideRightPanel()} direction="right" />
@@ -1034,4 +1095,13 @@ #o2 > .splitpanes__pane { overflow: visible !important; } + + /* Conditionally apply the disabled cursor globally */ + :global(*) { + cursor: var(--element-cursor, var(--global-cursor, auto)); + } + + :global(.connection-access) { + --element-cursor: auto; + } diff --git a/frontend/src/lib/components/apps/editor/ComponentHeader.svelte b/frontend/src/lib/components/apps/editor/ComponentHeader.svelte index 2f4ffe8b15..1c1bd885b5 100644 --- a/frontend/src/lib/components/apps/editor/ComponentHeader.svelte +++ b/frontend/src/lib/components/apps/editor/ComponentHeader.svelte @@ -1,7 +1,7 @@ {#if connecting} -
+
- + + -
+
{component.id}
{#if !connecting} @@ -109,8 +127,8 @@ class={twMerge( 'px-1 py-0.5 text-2xs font-bold rounded cursor-pointer w-fit h-full', fullHeight - ? ' bg-indigo-800 text-indigo-200' - : 'text-white hover:bg-indigo-700 hover:text-indigo-200' + ? 'bg-blue-300 text-blue-800' + : 'text-white hover:bg-blue-400 hover:text-white' )} on:click={() => dispatch('fillHeight')} on:pointerdown|stopPropagation @@ -122,9 +140,7 @@ title="Lock Position" class={twMerge( 'px-1 py-0.5 text-2xs font-bold rounded cursor-pointer w-fit h-full', - locked - ? ' bg-indigo-800 text-indigo-200' - : 'text-white hover:bg-indigo-700 hover:text-indigo-200' + locked ? 'bg-blue-300 text-blue-800' : 'text-white hover:bg-blue-400 hover:text-white' )} on:click={() => dispatch('lock')} on:pointerdown|stopPropagation @@ -141,10 +157,10 @@ {#if selected && !connecting && checkComponentOptions()}
{#if hasInlineEditor} @@ -153,17 +169,13 @@ class={twMerge( 'px-1 py-0.5 text-2xs font-bold rounded cursor-pointer w-fit h-full', inlineEditorOpened - ? 'bg-indigo-300 text-indigo-800' - : 'text-indigo-600 hover:bg-indigo-300 hover:text-indigo-800' + ? 'bg-blue-300 text-blue-800' + : 'text-blue-600 hover:bg-blue-300 hover:text-blue-800' )} on:click={() => dispatch('triggerInlineEditor')} on:pointerdown|stopPropagation > - {#if inlineEditorOpened} - - {:else} - - {/if} + {/if} {#if component.type === 'conditionalwrapper'} @@ -185,10 +197,10 @@ -
-
- -
diff --git a/frontend/src/lib/components/apps/editor/DecisionTreeDebug.svelte b/frontend/src/lib/components/apps/editor/DecisionTreeDebug.svelte index e35f648a26..724a3604c5 100644 --- a/frontend/src/lib/components/apps/editor/DecisionTreeDebug.svelte +++ b/frontend/src/lib/components/apps/editor/DecisionTreeDebug.svelte @@ -78,7 +78,7 @@ 'px-1 text-2xs font-bold rounded cursor-pointer w-fit h-full', componentIsDebugging ? ' hover:bg-red-300 hover:text-red-800' - : ' hover:bg-indigo-300 hover:text-indigo-800' + : 'text-blue-600 hover:bg-blue-300 hover:text-blue-800' )} on:click={() => dispatch('triggerInlineEditor')} on:pointerdown|stopPropagation diff --git a/frontend/src/lib/components/apps/editor/TabsDebug.svelte b/frontend/src/lib/components/apps/editor/TabsDebug.svelte index f19229b933..66f84d54f0 100644 --- a/frontend/src/lib/components/apps/editor/TabsDebug.svelte +++ b/frontend/src/lib/components/apps/editor/TabsDebug.svelte @@ -25,7 +25,7 @@ 'text-2xs font-bold w-fit h-full cursor-pointer rounded', isManuallySelected ? 'hover:bg-red-200 hover:text-red-800' - : ' hover:text-indigo-800 hover:bg-indigo-300' + : 'text-blue-600 hover:bg-blue-300 hover:text-blue-800' )} on:click={() => dispatch('triggerInlineEditor')} on:pointerdown|stopPropagation diff --git a/frontend/src/lib/components/apps/editor/appUtils.ts b/frontend/src/lib/components/apps/editor/appUtils.ts index 998d3d8a1c..6610de44ba 100644 --- a/frontend/src/lib/components/apps/editor/appUtils.ts +++ b/frontend/src/lib/components/apps/editor/appUtils.ts @@ -912,6 +912,7 @@ export function connectInput( if (connectingInput) { if (connectingInput.onConnect) { connectingInput.onConnect({ componentId, path }) + sendUserToast(`Connected to ${componentId}.${path}`, false) } connectingInput = { diff --git a/frontend/src/lib/components/apps/editor/component/Component.svelte b/frontend/src/lib/components/apps/editor/component/Component.svelte index 56c39ed072..3ba27484fa 100644 --- a/frontend/src/lib/components/apps/editor/component/Component.svelte +++ b/frontend/src/lib/components/apps/editor/component/Component.svelte @@ -166,10 +166,11 @@ }} on:mouseout|stopPropagation={mouseOut} class={twMerge( - 'h-full flex flex-col w-full component relative', + 'h-full flex flex-col w-full component relative connection-access', initializing ? 'overflow-hidden h-0' : '', hidden && $mode === 'preview' ? 'hidden' : '' )} + data-connection-button > {#if locked && componentActive && $componentActive && moveMode === 'move' && componentDraggedId && componentDraggedId !== component.id && cachedAreOnTheSameSubgrid}
0} {#if $hasResult[componentId] || $search == ''} -
+
{:else if $search.length > 0} diff --git a/frontend/src/lib/components/apps/editor/contextPanel/ContextPanel.svelte b/frontend/src/lib/components/apps/editor/contextPanel/ContextPanel.svelte index f1b7502d9d..6f32b182eb 100644 --- a/frontend/src/lib/components/apps/editor/contextPanel/ContextPanel.svelte +++ b/frontend/src/lib/components/apps/editor/contextPanel/ContextPanel.svelte @@ -1,5 +1,4 @@ {#if !(resourceOnly && (fieldType !== 'object' || !format?.startsWith('resource-')))} @@ -135,25 +153,7 @@ {/if} -
- -
+ {/if}
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/secondaryMenu/SecondaryMenu.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/secondaryMenu/SecondaryMenu.svelte index a64a68d34d..6d3d34dfbd 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/secondaryMenu/SecondaryMenu.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/secondaryMenu/SecondaryMenu.svelte @@ -1,23 +1,14 @@
+ import { Plug } from 'lucide-svelte' + import { clickOutside, pointerDownOutside } from '$lib/utils' + import AnimatedButton from '$lib/components/common/button/AnimatedButton.svelte' + import Button from '$lib/components/common/button/Button.svelte' + + export let isOpen = false + export let openConnection: () => void + export let closeConnection: () => void + + let selected = false + + async function getConnectionButtonElements(): Promise { + return Array.from( + document.querySelectorAll('[data-connection-button], [data-connection-button] *') + ) as HTMLElement[] + } + + function handleConnect() { + if (isOpen) { + deactivateConnection() + return + } + activateConnection() + } + + function activateConnection() { + selected = true + openConnection() + } + + function deactivateConnection() { + selected = false + closeConnection() + } + + $: !isOpen && (selected = false) + + + +
e.key === 'Escape' && handleConnect()} + on:pointerdown_outside={deactivateConnection} + on:click_outside={deactivateConnection} + class="connection-access" + data-connection-button +> + + + +
diff --git a/frontend/src/lib/components/propertyPicker/ObjectViewer.svelte b/frontend/src/lib/components/propertyPicker/ObjectViewer.svelte index ffe0269b52..1e905f6822 100644 --- a/frontend/src/lib/components/propertyPicker/ObjectViewer.svelte +++ b/frontend/src/lib/components/propertyPicker/ObjectViewer.svelte @@ -9,6 +9,7 @@ import { Download, PanelRightOpen } from 'lucide-svelte' import S3FilePicker from '../S3FilePicker.svelte' import { workspaceStore } from '$lib/stores' + import AnimatedButton from '$lib/components/common/button/AnimatedButton.svelte' export let json: any export let level = 0 @@ -21,8 +22,10 @@ export let collapseLevel: number | undefined = undefined export let prefix = '' export let expandedEvenOnLevel0: string | undefined = undefined + export let connecting = false let s3FileViewer: S3FilePicker + let hoveredKey: string | null = null const collapsedSymbol = '...' $: keys = ['object', 's3object'].includes(getTypeAsString(json)) ? Object.keys(json) : [] @@ -103,17 +106,29 @@ > {#each keys.length > keyLimit ? keys.slice(0, keyLimit) : keys as key, index (key)}
  • - + + : {#if getTypeAsString(json[key]) === 'object'} diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 4b409d0969..11cb8fa6e6 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -174,6 +174,7 @@ const portalDivs = ['app-editor-select'] interface ClickOutsideOptions { capture?: boolean exclude?: (() => Promise) | HTMLElement[] | undefined + stopPropagation?: boolean } export function clickOutside( @@ -204,6 +205,9 @@ export function clickOutside( const parent = target.closest(portalDivsSelector) if (!parent) { + if (opts?.stopPropagation) { + event.stopPropagation() + } node.dispatchEvent(new CustomEvent('click_outside', { detail: event })) } } @@ -222,6 +226,56 @@ export function clickOutside( } } +export function pointerDownOutside( + node: Node, + options?: ClickOutsideOptions +): { destroy(): void; update(newOptions: ClickOutsideOptions): void } { + const handlePointerDown = async (event: PointerEvent) => { + const target = event.target as HTMLElement + + let excludedElements: HTMLElement[] = [] + if (options?.exclude) { + if (Array.isArray(options.exclude)) { + excludedElements = options.exclude + } else { + excludedElements = await options.exclude() + } + } + + const isExcluded = excludedElements.some((excludedEl) => { + const contains = excludedEl?.contains?.(target) + const isTarget = target === excludedEl + return contains || isTarget + }) + + if (node && !node.contains(target) && !event.defaultPrevented && !isExcluded) { + const portalDivsSelector = portalDivs.map((id) => `#${id}`).join(', ') + const parent = target.closest(portalDivsSelector) + + if (!parent) { + if (options?.stopPropagation) { + event.stopPropagation() + } + console.log('dbg pointerdown_outside') + node.dispatchEvent(new CustomEvent('pointerdown_outside', { detail: event })) + return false + } + } + } + + const capture = options?.capture ?? true + document.addEventListener('pointerdown', handlePointerDown, capture ?? true) + + return { + update(newOptions: ClickOutsideOptions) { + options = newOptions + }, + destroy() { + document.removeEventListener('pointerdown', handlePointerDown, capture ?? true) + } + } +} + export interface DropdownItem { // If a DropdownItem has an action, it will be declared as a button // If a DropdownItem has no action and an href, it will be declared as a link