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