mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 10:02:05 +00:00
feat(editor): Improve UX of embedded NDV experiment (no-changelog) (#17515)
This commit is contained in:
@@ -1569,6 +1569,8 @@
|
||||
"nodeView.setupTemplate": "Set up template",
|
||||
"nodeView.expandAllNodes": "Expand all nodes",
|
||||
"nodeView.collapseAllNodes": "Collapse all nodes",
|
||||
"nodeView.enterZoomMode": "Enter zoom mode",
|
||||
"nodeView.leaveZoomMode": "Leave zoom mode",
|
||||
"nodeViewV2.showError.editingNotAllowed": "Editing is not allowed",
|
||||
"nodeViewV2.showError.failedToCreateNode": "Failed to create node",
|
||||
"contextMenu.node": "node | nodes",
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
"@typescript/vfs": "^1.6.0",
|
||||
"@vue-flow/background": "^1.3.2",
|
||||
"@vue-flow/controls": "^1.1.2",
|
||||
"@vue-flow/core": "^1.42.1",
|
||||
"@vue-flow/core": "^1.45.0",
|
||||
"@vue-flow/minimap": "^1.5.2",
|
||||
"@vue-flow/node-resizer": "^1.4.0",
|
||||
"@vueuse/components": "^10.11.0",
|
||||
|
||||
@@ -92,7 +92,10 @@ export function createCanvasGraphNode({
|
||||
isParent: false,
|
||||
selected: false,
|
||||
resizing: false,
|
||||
handleBounds: {},
|
||||
handleBounds: {
|
||||
source: null,
|
||||
target: null,
|
||||
},
|
||||
events: {},
|
||||
data: createCanvasNodeData({ id, type, ...data }),
|
||||
...rest,
|
||||
|
||||
@@ -70,7 +70,6 @@ const props = withDefaults(
|
||||
inputSize: number;
|
||||
activeNode?: INodeUi;
|
||||
isEmbeddedInCanvas?: boolean;
|
||||
noWheel?: boolean;
|
||||
subTitle?: string;
|
||||
}>(),
|
||||
{
|
||||
@@ -81,7 +80,6 @@ const props = withDefaults(
|
||||
blockUI: false,
|
||||
activeNode: undefined,
|
||||
isEmbeddedInCanvas: false,
|
||||
noWheel: false,
|
||||
subTitle: undefined,
|
||||
},
|
||||
);
|
||||
@@ -98,6 +96,7 @@ const emit = defineEmits<{
|
||||
];
|
||||
activate: [];
|
||||
execute: [];
|
||||
captureWheelBody: [WheelEvent];
|
||||
}>();
|
||||
|
||||
const slots = defineSlots<{ actions?: {} }>();
|
||||
@@ -814,12 +813,6 @@ function displayCredentials(credentialTypeDescription: INodeCredentialDescriptio
|
||||
nodeHelpers.displayParameter(node.value.parameters, credentialTypeDescription, '', node.value)
|
||||
);
|
||||
}
|
||||
|
||||
function handleWheelEvent(event: WheelEvent) {
|
||||
if (event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -941,11 +934,10 @@ function handleWheelEvent(event: WheelEvent) {
|
||||
:class="[
|
||||
'node-parameters-wrapper',
|
||||
shouldShowStaticScrollbar ? 'with-static-scrollbar' : '',
|
||||
noWheel && shouldShowStaticScrollbar ? 'nowheel' : '',
|
||||
{ 'ndv-v2': isNDVV2 },
|
||||
]"
|
||||
data-test-id="node-parameters"
|
||||
@wheel="noWheel ? handleWheelEvent : undefined"
|
||||
@wheel.capture="emit('captureWheelBody', $event)"
|
||||
>
|
||||
<N8nNotice
|
||||
v-if="hasForeignCredential && !isHomeProjectTeam"
|
||||
@@ -1148,7 +1140,7 @@ function handleWheelEvent(event: WheelEvent) {
|
||||
}
|
||||
|
||||
&.embedded .node-parameters-wrapper.with-static-scrollbar {
|
||||
padding: 0 var(--spacing-2xs) var(--spacing-xs) var(--spacing-xs);
|
||||
padding: 0 var(--spacing-4xs) var(--spacing-xs) var(--spacing-xs);
|
||||
|
||||
@supports not (selector(::-webkit-scrollbar)) {
|
||||
scrollbar-width: thin;
|
||||
|
||||
@@ -160,6 +160,7 @@ const {
|
||||
nodesSelectionActive,
|
||||
userSelectionRect,
|
||||
setViewport,
|
||||
setCenter,
|
||||
onEdgeMouseLeave,
|
||||
onEdgeMouseEnter,
|
||||
onEdgeMouseMove,
|
||||
@@ -285,6 +286,18 @@ function selectUpstreamNodes(id: string) {
|
||||
onSelectNodes({ ids: [...upstreamNodes.map((node) => node.id), id] });
|
||||
}
|
||||
|
||||
function onToggleZoomMode() {
|
||||
experimentalNdvStore.toggleZoomMode({
|
||||
canvasViewport: viewport.value,
|
||||
canvasDimensions: dimensions.value,
|
||||
selectedNodes: selectedNodes.value,
|
||||
setViewport,
|
||||
fitView,
|
||||
zoomTo,
|
||||
setCenter,
|
||||
});
|
||||
}
|
||||
|
||||
const keyMap = computed(() => {
|
||||
const readOnlyKeymap: KeyMap = {
|
||||
ctrl_shift_o: emitWithLastSelectedNode((id) => emit('open:sub-workflow', id)),
|
||||
@@ -308,6 +321,7 @@ const keyMap = computed(() => {
|
||||
l: () => emit('update:logs-open'),
|
||||
i: () => emit('update:logs:input-open'),
|
||||
o: () => emit('update:logs:output-open'),
|
||||
z: onToggleZoomMode,
|
||||
};
|
||||
|
||||
if (props.readOnly) return readOnlyKeymap;
|
||||
@@ -431,7 +445,7 @@ function onSelectNodes({ ids, panIntoView }: CanvasEventBusEvents['nodes:select'
|
||||
|
||||
const newViewport = updateViewportToContainNodes(viewport.value, dimensions.value, nodes, 100);
|
||||
|
||||
void setViewport(newViewport, { duration: 200 });
|
||||
void setViewport(newViewport, { duration: 200, interpolate: 'linear' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,6 +469,18 @@ function onUpdateNodeOutputs(id: string) {
|
||||
emit('update:node:outputs', id);
|
||||
}
|
||||
|
||||
function onFocusNode(id: string) {
|
||||
const node = vueFlow.nodeLookup.value.get(id);
|
||||
|
||||
if (node) {
|
||||
experimentalNdvStore.focusNode(node, {
|
||||
canvasViewport: viewport.value,
|
||||
canvasDimensions: dimensions.value,
|
||||
setCenter,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connections / Edges
|
||||
*/
|
||||
@@ -896,7 +922,6 @@ provide(CanvasKey, {
|
||||
:event-bus="eventBus"
|
||||
:hovered="nodesHoveredById[nodeProps.id]"
|
||||
:nearby-hovered="nodeProps.id === hoveredTriggerNode.id.value"
|
||||
:is-experimental-ndv-active="isExperimentalNdvActive"
|
||||
@delete="onDeleteNode"
|
||||
@run="onRunNode"
|
||||
@select="onSelectNode"
|
||||
@@ -909,6 +934,7 @@ provide(CanvasKey, {
|
||||
@update:outputs="onUpdateNodeOutputs"
|
||||
@move="onUpdateNodePosition"
|
||||
@add="onClickNodeAdd"
|
||||
@focus="onFocusNode"
|
||||
>
|
||||
<template v-if="$slots.nodeToolbar" #toolbar="toolbarProps">
|
||||
<slot name="nodeToolbar" v-bind="toolbarProps" />
|
||||
@@ -972,6 +998,7 @@ provide(CanvasKey, {
|
||||
@zoom-out="onZoomOut"
|
||||
@reset-zoom="onResetZoom"
|
||||
@tidy-up="onTidyUp({ source: 'canvas-button' })"
|
||||
@toggle-zoom-mode="onToggleZoomMode"
|
||||
/>
|
||||
|
||||
<Suspense>
|
||||
@@ -1004,7 +1031,7 @@ provide(CanvasKey, {
|
||||
}
|
||||
|
||||
&.isExperimentalNdvActive {
|
||||
--canvas-zoom-compensation-factor: 0.67;
|
||||
--canvas-zoom-compensation-factor: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,12 +5,12 @@ import { useI18n } from '@n8n/i18n';
|
||||
import { Controls } from '@vue-flow/controls';
|
||||
import { computed } from 'vue';
|
||||
import { useExperimentalNdvStore } from '../../experimental/experimentalNdv.store';
|
||||
import { N8nIconButton } from '@n8n/design-system';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
zoom?: number;
|
||||
readOnly?: boolean;
|
||||
isExperimentalNdvActive: boolean;
|
||||
}>(),
|
||||
{
|
||||
zoom: 1,
|
||||
@@ -24,13 +24,18 @@ const emit = defineEmits<{
|
||||
'zoom-out': [];
|
||||
'zoom-to-fit': [];
|
||||
'tidy-up': [];
|
||||
'toggle-zoom-mode': [];
|
||||
}>();
|
||||
|
||||
const i18n = useI18n();
|
||||
|
||||
const experimentalNdvStore = useExperimentalNdvStore();
|
||||
|
||||
const isResetZoomVisible = computed(() => props.zoom !== 1);
|
||||
const isExperimentalNdvActive = computed(() => experimentalNdvStore.isActive(props.zoom));
|
||||
|
||||
const isToggleZoomVisible = computed(() => experimentalNdvStore.isEnabled);
|
||||
|
||||
const isResetZoomVisible = computed(() => !isToggleZoomVisible.value && props.zoom !== 1);
|
||||
|
||||
function onResetZoom() {
|
||||
emit('reset-zoom');
|
||||
@@ -84,6 +89,22 @@ function onTidyUp() {
|
||||
@click="onZoomOut"
|
||||
/>
|
||||
</KeyboardShortcutTooltip>
|
||||
<KeyboardShortcutTooltip
|
||||
v-if="isToggleZoomVisible"
|
||||
:label="
|
||||
i18n.baseText(isExperimentalNdvActive ? 'nodeView.leaveZoomMode' : 'nodeView.enterZoomMode')
|
||||
"
|
||||
:shortcut="{ keys: ['Z'] }"
|
||||
>
|
||||
<N8nIconButton
|
||||
square
|
||||
type="tertiary"
|
||||
size="large"
|
||||
:class="$style.iconButton"
|
||||
:icon="isExperimentalNdvActive ? 'undo-2' : 'crosshair'"
|
||||
@click="emit('toggle-zoom-mode')"
|
||||
/>
|
||||
</KeyboardShortcutTooltip>
|
||||
<KeyboardShortcutTooltip
|
||||
v-if="isResetZoomVisible"
|
||||
:label="i18n.baseText('nodeView.resetZoom')"
|
||||
|
||||
@@ -18,6 +18,7 @@ exports[`CanvasControlButtons > should render correctly 1`] = `
|
||||
<!--teleport start-->
|
||||
<!--teleport end-->
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
<n8n-button-stub block="false" element="button" label="" square="true" active="false" disabled="false" loading="false" outline="false" size="large" text="false" type="tertiary" data-test-id="tidy-up-button" class="iconButton el-tooltip__trigger el-tooltip__trigger"></n8n-button-stub>
|
||||
<!--teleport start-->
|
||||
<!--teleport end-->
|
||||
|
||||
@@ -64,6 +64,7 @@ const emit = defineEmits<{
|
||||
'update:inputs': [id: string];
|
||||
'update:outputs': [id: string];
|
||||
move: [id: string, position: XYPosition];
|
||||
focus: [id: string];
|
||||
}>();
|
||||
|
||||
const style = useCssModule();
|
||||
@@ -270,6 +271,10 @@ function onMove(position: XYPosition) {
|
||||
emit('move', props.id, position);
|
||||
}
|
||||
|
||||
function onFocus(id: string) {
|
||||
emit('focus', id);
|
||||
}
|
||||
|
||||
function onUpdateClass({ className, add = true }: CanvasNodeEventBusEvents['update:node:class']) {
|
||||
nodeClasses.value = add
|
||||
? [...new Set([...nodeClasses.value, className])]
|
||||
@@ -393,6 +398,7 @@ onBeforeUnmount(() => {
|
||||
@run="onRun"
|
||||
@update="onUpdate"
|
||||
@open:contextmenu="onOpenContextMenuFromToolbar"
|
||||
@focus="onFocus"
|
||||
/>
|
||||
|
||||
<CanvasNodeRenderer
|
||||
|
||||
@@ -14,6 +14,7 @@ const emit = defineEmits<{
|
||||
run: [];
|
||||
update: [parameters: Record<string, unknown>];
|
||||
'open:contextmenu': [event: MouseEvent];
|
||||
focus: [id: string];
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -105,7 +106,7 @@ function onMouseLeave() {
|
||||
|
||||
function onFocusNode() {
|
||||
if (node.value) {
|
||||
experimentalNdvStore.focusNode(node.value.id);
|
||||
emit('focus', node.value.id);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -189,6 +190,7 @@ function onFocusNode() {
|
||||
|
||||
&.isExperimentalNdvActive {
|
||||
justify-content: center;
|
||||
padding-bottom: var(--spacing-3xs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,8 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { createEventBus } from '@n8n/utils/event-bus';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const { nodeId, noWheel, isReadOnly, subTitle } = defineProps<{
|
||||
const { nodeId, isReadOnly, subTitle } = defineProps<{
|
||||
nodeId: string;
|
||||
noWheel?: boolean;
|
||||
isReadOnly?: boolean;
|
||||
subTitle?: string;
|
||||
}>();
|
||||
@@ -26,6 +25,29 @@ function handleValueChanged(parameterData: IUpdateInformation) {
|
||||
void renameNode(parameterData.oldValue as string, parameterData.value as string);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCaptureWheelEvent(event: WheelEvent) {
|
||||
if (event.ctrlKey) {
|
||||
// If the event is pinch, let it propagate and zoom canvas
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
event.currentTarget instanceof HTMLElement &&
|
||||
event.currentTarget.scrollHeight <= event.currentTarget.offsetHeight
|
||||
) {
|
||||
// If the settings pane doesn't have to scroll, let it propagate and move the canvas
|
||||
return;
|
||||
}
|
||||
|
||||
// If the event has larger horizontal element, let it propagate and move the canvas
|
||||
if (Math.abs(event.deltaX) >= Math.abs(event.deltaY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, let it scroll the settings pane
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -40,9 +62,9 @@ function handleValueChanged(parameterData: IUpdateInformation) {
|
||||
:executable="false"
|
||||
:input-size="0"
|
||||
is-embedded-in-canvas
|
||||
:no-wheel="noWheel"
|
||||
:sub-title="subTitle"
|
||||
@value-changed="handleValueChanged"
|
||||
@capture-wheel-body="handleCaptureWheelEvent"
|
||||
>
|
||||
<template #actions>
|
||||
<slot name="actions" />
|
||||
|
||||
@@ -75,6 +75,9 @@ onBeforeUnmount(() => {
|
||||
:width="360"
|
||||
:offset="8"
|
||||
:append-to="vf.viewportRef?.value"
|
||||
:popper-options="{
|
||||
modifiers: [{ name: 'flip', enabled: false }],
|
||||
}"
|
||||
>
|
||||
<template #reference>
|
||||
<slot />
|
||||
|
||||
@@ -58,8 +58,8 @@ const isVisible = computed(() =>
|
||||
{
|
||||
x: -vf.viewport.value.x / vf.viewport.value.zoom,
|
||||
y: -vf.viewport.value.y / vf.viewport.value.zoom,
|
||||
width: vf.viewportRef.value?.offsetWidth ?? 0,
|
||||
height: vf.viewportRef.value?.offsetHeight ?? 0,
|
||||
width: vf.dimensions.value.width,
|
||||
height: vf.dimensions.value.height,
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -148,6 +148,8 @@ watchOnce(isVisible, (visible) => {
|
||||
:style="{
|
||||
'--zoom': `${1 / experimentalNdvStore.maxCanvasZoom}`,
|
||||
'--node-width-scaler': isConfigurable ? 1 : 1.5,
|
||||
'--max-height-on-focus': `${(vf.dimensions.value.height * 0.8) / experimentalNdvStore.maxCanvasZoom}px`,
|
||||
pointerEvents: isMoving ? 'none' : 'auto', // Don't interrupt canvas panning
|
||||
}"
|
||||
>
|
||||
<template v-if="!node || !isOnceVisible" />
|
||||
@@ -162,9 +164,6 @@ watchOnce(isVisible, (visible) => {
|
||||
tabindex="-1"
|
||||
:node-id="nodeId"
|
||||
:class="$style.settingsView"
|
||||
:no-wheel="
|
||||
!isMoving /* to not interrupt panning while allowing scroll of the settings pane, allow wheel event while panning */
|
||||
"
|
||||
:is-read-only="isReadOnly"
|
||||
:sub-title="subTitle"
|
||||
>
|
||||
@@ -197,23 +196,31 @@ watchOnce(isVisible, (visible) => {
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
:root .component {
|
||||
position: relative;
|
||||
.component {
|
||||
align-items: flex-start;
|
||||
justify-content: stretch;
|
||||
border-width: 1px !important;
|
||||
border-radius: var(--border-radius-base) !important;
|
||||
width: calc(var(--canvas-node--width) * var(--node-width-scaler));
|
||||
width: calc(var(--canvas-node--width) * var(--node-width-scaler)) !important;
|
||||
overflow: hidden;
|
||||
|
||||
--canvas-node--border-color: var(--color-text-lighter);
|
||||
--expanded-max-height: min(
|
||||
calc(var(--canvas-node--height) * 2),
|
||||
var(--max-height-on-focus),
|
||||
300px
|
||||
);
|
||||
|
||||
&.expanded {
|
||||
user-select: text;
|
||||
cursor: auto;
|
||||
height: auto;
|
||||
max-height: min(calc(var(--canvas-node--height) * 2), 300px);
|
||||
max-height: var(--expanded-max-height);
|
||||
min-height: var(--spacing-3xl);
|
||||
|
||||
:global(.selected) & {
|
||||
max-height: var(--max-height-on-focus);
|
||||
}
|
||||
}
|
||||
&.collapsed {
|
||||
overflow: hidden;
|
||||
@@ -229,14 +236,18 @@ watchOnce(isVisible, (visible) => {
|
||||
}
|
||||
}
|
||||
|
||||
:root .collapsedContent,
|
||||
:root .settingsView {
|
||||
.collapsedContent,
|
||||
.settingsView {
|
||||
z-index: 1000;
|
||||
width: 100%;
|
||||
|
||||
height: auto;
|
||||
max-height: calc(min(calc(var(--canvas-node--height) * 2), 300px) - var(--border-width-base) * 2);
|
||||
max-height: calc(var(--expanded-max-height) - var(--border-width-base) * 2);
|
||||
min-height: var(--spacing-2xl); // should be multiple of GRID_SIZE
|
||||
|
||||
:global(.selected) & {
|
||||
max-height: calc(var(--max-height-on-focus) - var(--border-width-base) * 2);
|
||||
}
|
||||
}
|
||||
|
||||
.collapsedContent {
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import { computed, shallowRef } from 'vue';
|
||||
import { computed, ref, shallowRef } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useVueFlow } from '@vue-flow/core';
|
||||
import { calculateNodeSize } from '@/utils/nodeViewUtils';
|
||||
import {
|
||||
type Dimensions,
|
||||
type FitView,
|
||||
type GraphNode,
|
||||
type SetCenter,
|
||||
type SetViewport,
|
||||
type ViewportTransform,
|
||||
type ZoomTo,
|
||||
} from '@vue-flow/core';
|
||||
import { CanvasNodeRenderType, type CanvasNodeData } from '@/types';
|
||||
|
||||
export const useExperimentalNdvStore = defineStore('experimentalNdv', () => {
|
||||
const workflowStore = useWorkflowsStore();
|
||||
@@ -17,6 +25,7 @@ export const useExperimentalNdvStore = defineStore('experimentalNdv', () => {
|
||||
isEnabled.value ? settingsStore.experimental__minZoomNodeSettingsInCanvas : 4,
|
||||
);
|
||||
|
||||
const previousViewport = ref<ViewportTransform>();
|
||||
const collapsedNodes = shallowRef<Partial<Record<string, boolean>>>({});
|
||||
|
||||
function setNodeExpanded(nodeId: string, isExpanded?: boolean) {
|
||||
@@ -41,51 +50,89 @@ export const useExperimentalNdvStore = defineStore('experimentalNdv', () => {
|
||||
}
|
||||
|
||||
function isActive(canvasZoom: number) {
|
||||
return isEnabled.value && canvasZoom === maxCanvasZoom.value;
|
||||
return isEnabled.value && Math.abs(canvasZoom - maxCanvasZoom.value) < 0.000001;
|
||||
}
|
||||
|
||||
function focusNode(nodeId: string) {
|
||||
const nodeToFocus = workflowStore.getNodeById(nodeId);
|
||||
interface FocusNodeOptions {
|
||||
collapseOthers?: boolean;
|
||||
canvasViewport: ViewportTransform;
|
||||
canvasDimensions: Dimensions;
|
||||
setCenter: SetCenter;
|
||||
}
|
||||
|
||||
if (!nodeToFocus) {
|
||||
function focusNode(
|
||||
node: GraphNode<CanvasNodeData>,
|
||||
{ collapseOthers = true, canvasDimensions, canvasViewport, setCenter }: FocusNodeOptions,
|
||||
) {
|
||||
collapsedNodes.value = collapseOthers
|
||||
? workflowStore.allNodes.reduce<Partial<Record<string, boolean>>>((acc, n) => {
|
||||
acc[n.id] = n.id !== node.id;
|
||||
return acc;
|
||||
}, {})
|
||||
: { ...collapsedNodes.value, [node.id]: false };
|
||||
|
||||
const topMargin = 80; // pixels
|
||||
const nodeWidth = node.dimensions.width * (isActive(canvasViewport.zoom) ? 1 : 1.5);
|
||||
|
||||
// Move the node to top center of the canvas
|
||||
void setCenter(
|
||||
node.position.x + nodeWidth / 2,
|
||||
node.position.y + (canvasDimensions.height * (1 / 2) - topMargin) / maxCanvasZoom.value,
|
||||
{
|
||||
duration: 200,
|
||||
zoom: maxCanvasZoom.value,
|
||||
interpolate: 'linear',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
interface ToggleZoomModeOptions {
|
||||
canvasViewport: ViewportTransform;
|
||||
canvasDimensions: Dimensions;
|
||||
selectedNodes: Array<GraphNode<CanvasNodeData>>;
|
||||
setViewport: SetViewport;
|
||||
fitView: FitView;
|
||||
zoomTo: ZoomTo;
|
||||
setCenter: SetCenter;
|
||||
}
|
||||
|
||||
function toggleZoomMode(options: ToggleZoomModeOptions) {
|
||||
if (isActive(options.canvasViewport.zoom)) {
|
||||
if (previousViewport.value === undefined) {
|
||||
void options.fitView({ duration: 200, interpolate: 'linear' });
|
||||
return;
|
||||
}
|
||||
|
||||
void options.setViewport(previousViewport.value, { duration: 200, interpolate: 'linear' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Call useVueFlow() here because having it in setup fn scope seem to cause initialization problem
|
||||
const vueFlow = useVueFlow(workflowStore.workflow.id);
|
||||
previousViewport.value = options.canvasViewport;
|
||||
|
||||
collapsedNodes.value = workflowStore.allNodes.reduce<Partial<Record<string, boolean>>>(
|
||||
(acc, node) => {
|
||||
acc[node.id] = node.id !== nodeId;
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
const toFocus = options.selectedNodes
|
||||
.filter((node) => node.data.render.type === CanvasNodeRenderType.Default)
|
||||
.toSorted((a, b) =>
|
||||
a.position.y === b.position.y ? a.position.x - b.position.x : a.position.y - b.position.y,
|
||||
)[0];
|
||||
|
||||
const workflow = workflowStore.getCurrentWorkflow();
|
||||
const nodeSize = calculateNodeSize(
|
||||
workflow.getChildNodes(nodeToFocus.name, 'ALL_NON_MAIN').length > 0,
|
||||
workflow.getParentNodes(nodeToFocus.name, 'ALL_NON_MAIN').length > 0,
|
||||
workflow.getParentNodes(nodeToFocus.name, 'main').length,
|
||||
workflow.getChildNodes(nodeToFocus.name, 'main').length,
|
||||
workflow.getParentNodes(nodeToFocus.name, 'ALL_NON_MAIN').length,
|
||||
);
|
||||
if (toFocus) {
|
||||
focusNode(toFocus, { ...options, collapseOthers: false });
|
||||
return;
|
||||
}
|
||||
|
||||
void vueFlow.setCenter(
|
||||
nodeToFocus.position[0] + (nodeSize.width * 1.5) / 2,
|
||||
nodeToFocus.position[1] + 80,
|
||||
{ duration: 200, zoom: maxCanvasZoom.value },
|
||||
);
|
||||
void options.zoomTo(maxCanvasZoom.value, { duration: 200, interpolate: 'linear' });
|
||||
}
|
||||
|
||||
return {
|
||||
isEnabled,
|
||||
maxCanvasZoom,
|
||||
previousZoom: computed(() => previousViewport.value),
|
||||
collapsedNodes: computed(() => collapsedNodes.value),
|
||||
isActive,
|
||||
setNodeExpanded,
|
||||
expandAllNodes,
|
||||
collapseAllNodes,
|
||||
toggleZoomMode,
|
||||
focusNode,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -15,7 +15,6 @@ import { useUIStore } from '@/stores/ui.store';
|
||||
import { shallowRef, watch } from 'vue';
|
||||
import { computed, type ComputedRef } from 'vue';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { useExperimentalNdvStore } from '@/components/canvas/experimental/experimentalNdv.store';
|
||||
|
||||
export function useLogsSelection(
|
||||
execution: ComputedRef<IExecutionResponse | undefined>,
|
||||
@@ -34,19 +33,13 @@ export function useLogsSelection(
|
||||
const uiStore = useUIStore();
|
||||
const canvasStore = useCanvasStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const experimentalNdvStore = useExperimentalNdvStore();
|
||||
|
||||
function syncSelectionToCanvasIfEnabled(value: LogEntry) {
|
||||
if (!logsStore.isLogSelectionSyncedWithCanvas) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (experimentalNdvStore.isEnabled) {
|
||||
canvasEventBus.emit('nodes:select', { ids: [value.node.id], panIntoView: false });
|
||||
experimentalNdvStore.focusNode(value.node.id);
|
||||
} else {
|
||||
canvasEventBus.emit('nodes:select', { ids: [value.node.id], panIntoView: true });
|
||||
}
|
||||
canvasEventBus.emit('nodes:select', { ids: [value.node.id], panIntoView: true });
|
||||
}
|
||||
|
||||
function select(value: LogEntry | undefined) {
|
||||
|
||||
@@ -544,7 +544,10 @@ describe('calculateNodeSize', () => {
|
||||
function createTestGraphNode(data: Partial<GraphNode> = {}): GraphNode {
|
||||
return {
|
||||
computedPosition: { z: 0, ...(data.position ?? { x: 0, y: 0 }) },
|
||||
handleBounds: {},
|
||||
handleBounds: {
|
||||
source: null,
|
||||
target: null,
|
||||
},
|
||||
dimensions: { width: 0, height: 0 },
|
||||
isParent: true,
|
||||
selected: false,
|
||||
|
||||
35
pnpm-lock.yaml
generated
35
pnpm-lock.yaml
generated
@@ -2360,19 +2360,19 @@ importers:
|
||||
version: 1.6.0(typescript@5.8.3)
|
||||
'@vue-flow/background':
|
||||
specifier: ^1.3.2
|
||||
version: 1.3.2(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))
|
||||
version: 1.3.2(@vue-flow/core@1.45.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))
|
||||
'@vue-flow/controls':
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.2(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))
|
||||
version: 1.1.2(@vue-flow/core@1.45.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))
|
||||
'@vue-flow/core':
|
||||
specifier: ^1.42.1
|
||||
version: 1.42.1(vue@3.5.13(typescript@5.8.3))
|
||||
specifier: ^1.45.0
|
||||
version: 1.45.0(vue@3.5.13(typescript@5.8.3))
|
||||
'@vue-flow/minimap':
|
||||
specifier: ^1.5.2
|
||||
version: 1.5.2(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))
|
||||
version: 1.5.2(@vue-flow/core@1.45.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))
|
||||
'@vue-flow/node-resizer':
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))
|
||||
version: 1.4.0(@vue-flow/core@1.45.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))
|
||||
'@vueuse/components':
|
||||
specifier: ^10.11.0
|
||||
version: 10.11.0(vue@3.5.13(typescript@5.8.3))
|
||||
@@ -7706,8 +7706,8 @@ packages:
|
||||
'@vue-flow/core': ^1.23.0
|
||||
vue: ^3.3.0
|
||||
|
||||
'@vue-flow/core@1.42.1':
|
||||
resolution: {integrity: sha512-QzzTxMAXfOeETKc+N3XMp5XpiPxKBHK5kq98avgTsE6MXyeU2E8EkANwwgSB/hvJ/k36RjU0Y7BOwCHiqiI1tw==}
|
||||
'@vue-flow/core@1.45.0':
|
||||
resolution: {integrity: sha512-+Qd4fTnCfrhfYQzlHyf5Jt7rNE4PlDnEJEJZH9v6hDZoTOeOy1RhS85cSxKYxdsJ31Ttj2v3yabhoVfBf+bmJA==}
|
||||
peerDependencies:
|
||||
vue: ^3.3.0
|
||||
|
||||
@@ -22328,36 +22328,37 @@ snapshots:
|
||||
path-browserify: 1.0.1
|
||||
vscode-uri: 3.0.8
|
||||
|
||||
'@vue-flow/background@1.3.2(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))':
|
||||
'@vue-flow/background@1.3.2(@vue-flow/core@1.45.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@vue-flow/core': 1.42.1(vue@3.5.13(typescript@5.8.3))
|
||||
'@vue-flow/core': 1.45.0(vue@3.5.13(typescript@5.8.3))
|
||||
vue: 3.5.13(typescript@5.8.3)
|
||||
|
||||
'@vue-flow/controls@1.1.2(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))':
|
||||
'@vue-flow/controls@1.1.2(@vue-flow/core@1.45.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@vue-flow/core': 1.42.1(vue@3.5.13(typescript@5.8.3))
|
||||
'@vue-flow/core': 1.45.0(vue@3.5.13(typescript@5.8.3))
|
||||
vue: 3.5.13(typescript@5.8.3)
|
||||
|
||||
'@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.8.3))':
|
||||
'@vue-flow/core@1.45.0(vue@3.5.13(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@vueuse/core': 10.11.0(vue@3.5.13(typescript@5.8.3))
|
||||
d3-drag: 3.0.0
|
||||
d3-interpolate: 3.0.1
|
||||
d3-selection: 3.0.0
|
||||
d3-zoom: 3.0.0
|
||||
vue: 3.5.13(typescript@5.8.3)
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
|
||||
'@vue-flow/minimap@1.5.2(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))':
|
||||
'@vue-flow/minimap@1.5.2(@vue-flow/core@1.45.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@vue-flow/core': 1.42.1(vue@3.5.13(typescript@5.8.3))
|
||||
'@vue-flow/core': 1.45.0(vue@3.5.13(typescript@5.8.3))
|
||||
d3-selection: 3.0.0
|
||||
d3-zoom: 3.0.0
|
||||
vue: 3.5.13(typescript@5.8.3)
|
||||
|
||||
'@vue-flow/node-resizer@1.4.0(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))':
|
||||
'@vue-flow/node-resizer@1.4.0(@vue-flow/core@1.45.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@vue-flow/core': 1.42.1(vue@3.5.13(typescript@5.8.3))
|
||||
'@vue-flow/core': 1.45.0(vue@3.5.13(typescript@5.8.3))
|
||||
d3-drag: 3.0.0
|
||||
d3-selection: 3.0.0
|
||||
vue: 3.5.13(typescript@5.8.3)
|
||||
|
||||
Reference in New Issue
Block a user