mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
fix(editor): Ai 668 UI changes to mock nodes modal (no-changelog) (#13899)
Co-authored-by: Oleg Ivaniv <me@olegivaniv.com>
This commit is contained in:
committed by
GitHub
parent
c91688d494
commit
4a1e5798ff
@@ -1,15 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import Canvas from '@/components/canvas/Canvas.vue';
|
||||
import CanvasNode from '@/components/canvas/elements/nodes/CanvasNode.vue';
|
||||
import { useCanvasMapping } from '@/composables/useCanvasMapping';
|
||||
import { useCanvasOperations } from '@/composables/useCanvasOperations';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import type { CanvasConnectionPort, CanvasEventBusEvents, CanvasNodeData } from '@/types';
|
||||
import type { CanvasConnectionPort, CanvasNodeData } from '@/types';
|
||||
import { N8nButton, N8nHeading, N8nSpinner, N8nText, N8nTooltip } from '@n8n/design-system';
|
||||
import { useVueFlow } from '@vue-flow/core';
|
||||
import { N8nTooltip } from '@n8n/design-system';
|
||||
import { createEventBus } from '@n8n/utils/event-bus';
|
||||
import { computed, onMounted, ref, useCssModule } from 'vue';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
@@ -21,18 +22,12 @@ const telemetry = useTelemetry();
|
||||
|
||||
const { resetWorkspace, initializeWorkspace } = useCanvasOperations({ router });
|
||||
|
||||
const eventBus = createEventBus<CanvasEventBusEvents>();
|
||||
const style = useCssModule();
|
||||
const uuid = crypto.randomUUID();
|
||||
const props = defineProps<{
|
||||
modelValue: Array<{ name: string; id: string }>;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: Array<{ name: string; id: string }>];
|
||||
}>();
|
||||
type PinnedNode = { name: string; id: string };
|
||||
const model = defineModel<PinnedNode[]>({ required: true });
|
||||
|
||||
const isLoading = ref(true);
|
||||
const isLoading = ref(false);
|
||||
|
||||
const workflowId = computed(() => route.params.name as string);
|
||||
const testId = computed(() => route.params.testId as string);
|
||||
@@ -40,10 +35,10 @@ const workflow = computed(() => workflowsStore.getWorkflowById(workflowId.value)
|
||||
const workflowObject = computed(() => workflowsStore.getCurrentWorkflow(true));
|
||||
const canvasId = computed(() => `${uuid}-${testId.value}`);
|
||||
|
||||
const { onNodesInitialized, fitView, zoomTo } = useVueFlow({ id: canvasId.value });
|
||||
const nodes = computed(() => {
|
||||
return workflow.value.nodes ?? [];
|
||||
const { onNodesInitialized, fitView, zoomTo, onNodeClick, viewport } = useVueFlow({
|
||||
id: canvasId.value,
|
||||
});
|
||||
const nodes = computed(() => workflow.value.nodes ?? []);
|
||||
const connections = computed(() => workflow.value.connections);
|
||||
|
||||
const { nodes: mappedNodes, connections: mappedConnections } = useCanvasMapping({
|
||||
@@ -51,144 +46,164 @@ const { nodes: mappedNodes, connections: mappedConnections } = useCanvasMapping(
|
||||
connections,
|
||||
workflowObject,
|
||||
});
|
||||
|
||||
async function loadData() {
|
||||
isLoading.value = true;
|
||||
workflowsStore.resetState();
|
||||
resetWorkspace();
|
||||
const loadingPromise = Promise.all([
|
||||
await Promise.all([
|
||||
nodeTypesStore.getNodeTypes(),
|
||||
workflowsStore.fetchWorkflow(workflowId.value),
|
||||
]);
|
||||
await loadingPromise;
|
||||
|
||||
// remove editor pinned data
|
||||
workflow.value.pinData = {};
|
||||
initializeWorkspace(workflow.value);
|
||||
disableAllNodes();
|
||||
}
|
||||
|
||||
function getNodeNameById(id: string) {
|
||||
return mappedNodes.value.find((node) => node.id === id)?.data?.name;
|
||||
}
|
||||
function updateNodeClasses(nodeIds: string[], isPinned: boolean) {
|
||||
eventBus.emit('nodes:action', {
|
||||
ids: nodeIds,
|
||||
action: 'update:node:class',
|
||||
payload: {
|
||||
className: style.pinnedNode,
|
||||
add: isPinned,
|
||||
},
|
||||
});
|
||||
eventBus.emit('nodes:action', {
|
||||
ids: nodeIds,
|
||||
action: 'update:node:class',
|
||||
payload: {
|
||||
className: style.notPinnedNode,
|
||||
add: !isPinned,
|
||||
},
|
||||
});
|
||||
}
|
||||
function disableAllNodes() {
|
||||
const ids = mappedNodes.value.map((node) => node.id);
|
||||
updateNodeClasses(ids, false);
|
||||
|
||||
const pinnedNodes = props.modelValue.map((node) => node.id).filter((id) => id !== null);
|
||||
|
||||
if (pinnedNodes.length > 0) {
|
||||
updateNodeClasses(pinnedNodes, true);
|
||||
}
|
||||
function isMocked(data: CanvasNodeData) {
|
||||
return model.value.some((node) => node.id === data.id);
|
||||
}
|
||||
function onPinButtonClick(data: CanvasNodeData) {
|
||||
|
||||
function canBeMocked(outputs: CanvasConnectionPort[], inputs: CanvasConnectionPort[]) {
|
||||
return outputs.length === 1 && inputs.length >= 1;
|
||||
}
|
||||
|
||||
function handleNodeClick(data: CanvasNodeData) {
|
||||
const nodeName = getNodeNameById(data.id);
|
||||
if (!nodeName) return;
|
||||
if (!nodeName || !canBeMocked(data.outputs, data.inputs)) return;
|
||||
|
||||
const isPinned = props.modelValue.some((node) => node.id === data.id);
|
||||
const updatedNodes = isPinned
|
||||
? props.modelValue.filter((node) => node.id !== data.id)
|
||||
: [...props.modelValue, { name: nodeName, id: data.id }];
|
||||
const mocked = isMocked(data);
|
||||
|
||||
emit('update:modelValue', updatedNodes);
|
||||
updateNodeClasses([data.id], !isPinned);
|
||||
model.value = mocked
|
||||
? model.value.filter((node) => node.id !== data.id)
|
||||
: model.value.concat({ name: nodeName, id: data.id });
|
||||
|
||||
if (!isPinned) {
|
||||
if (!mocked) {
|
||||
telemetry.track('User selected node to be mocked', {
|
||||
node_id: data.id,
|
||||
test_id: testId.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
function isPinButtonVisible(outputs: CanvasConnectionPort[], inputs: CanvasConnectionPort[]) {
|
||||
return outputs.length === 1 && inputs.length >= 1;
|
||||
|
||||
function tooltipContent(data: CanvasNodeData) {
|
||||
if (nodeTypesStore.isTriggerNode(data.type)) {
|
||||
return locale.baseText('testDefinition.edit.nodesPinning.triggerTooltip');
|
||||
}
|
||||
|
||||
if (!canBeMocked(data.outputs, data.inputs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMocked(data)) {
|
||||
return locale.baseText('testDefinition.edit.nodesPinning.pinButtonTooltip.pinned');
|
||||
} else {
|
||||
return locale.baseText('testDefinition.edit.nodesPinning.pinButtonTooltip');
|
||||
}
|
||||
}
|
||||
|
||||
const isPinned = (data: CanvasNodeData) => props.modelValue.some((node) => node.id === data.id);
|
||||
function tooltipOffset(data: CanvasNodeData) {
|
||||
if (nodeTypesStore.isTriggerNode(data.type)) return;
|
||||
|
||||
return 45 * viewport.value.zoom;
|
||||
}
|
||||
|
||||
function tooltipProps(data: CanvasNodeData) {
|
||||
const content = tooltipContent(data);
|
||||
return {
|
||||
disabled: !content,
|
||||
content,
|
||||
offset: tooltipOffset(data),
|
||||
};
|
||||
}
|
||||
|
||||
onNodeClick(({ node }) => handleNodeClick(node.data));
|
||||
|
||||
onNodesInitialized(async () => {
|
||||
await fitView();
|
||||
await zoomTo(0.7);
|
||||
// Wait for the zoom to be applied and the canvas edges to recompute
|
||||
await new Promise((resolve) => setTimeout(resolve, 400));
|
||||
isLoading.value = false;
|
||||
await zoomTo(0.7, { duration: 400 });
|
||||
});
|
||||
onMounted(loadData);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="mappedNodes.length === 0" :class="$style.noNodes">
|
||||
<N8nHeading size="large" :bold="true" :class="$style.noNodesTitle">{{
|
||||
locale.baseText('testDefinition.edit.pinNodes.noNodes.title')
|
||||
}}</N8nHeading>
|
||||
<N8nHeading size="large" :bold="true" :class="$style.noNodesTitle">
|
||||
{{ locale.baseText('testDefinition.edit.pinNodes.noNodes.title') }}
|
||||
</N8nHeading>
|
||||
<N8nText>{{ locale.baseText('testDefinition.edit.pinNodes.noNodes.description') }}</N8nText>
|
||||
</div>
|
||||
<div v-else :class="$style.container">
|
||||
<N8nSpinner v-if="isLoading" size="xlarge" type="dots" :class="$style.spinner" />
|
||||
<N8nSpinner v-if="isLoading" size="large" type="dots" :class="$style.spinner" />
|
||||
<Canvas
|
||||
:id="canvasId"
|
||||
:loading="isLoading"
|
||||
:class="{ [$style.canvas]: true }"
|
||||
:nodes="mappedNodes"
|
||||
:connections="mappedConnections"
|
||||
:show-bug-reporting-button="false"
|
||||
:read-only="true"
|
||||
:event-bus="eventBus"
|
||||
>
|
||||
<template #nodeToolbar="{ data, outputs, inputs }">
|
||||
<div
|
||||
v-if="isPinButtonVisible(outputs, inputs)"
|
||||
:class="{
|
||||
[$style.pinButtonContainer]: true,
|
||||
[$style.pinButtonContainerPinned]: isPinned(data),
|
||||
}"
|
||||
>
|
||||
<N8nTooltip placement="left">
|
||||
<template #content>
|
||||
{{ locale.baseText('testDefinition.edit.nodesPinning.pinButtonTooltip') }}
|
||||
<template #node="{ nodeProps }">
|
||||
<N8nTooltip placement="top" v-bind="tooltipProps(nodeProps.data)">
|
||||
<CanvasNode
|
||||
v-bind="nodeProps"
|
||||
:class="{
|
||||
[$style.isTrigger]: nodeTypesStore.isTriggerNode(nodeProps.data.type),
|
||||
[$style.mockNode]: true,
|
||||
}"
|
||||
>
|
||||
<template #toolbar="{ data, outputs, inputs }">
|
||||
<div
|
||||
v-if="canBeMocked(outputs, inputs)"
|
||||
:class="{
|
||||
[$style.pinButtonContainer]: true,
|
||||
[$style.pinButtonContainerPinned]: isMocked(data),
|
||||
}"
|
||||
>
|
||||
<N8nButton
|
||||
icon="thumbtack"
|
||||
block
|
||||
type="secondary"
|
||||
:class="{ [$style.customSecondary]: isMocked(data) }"
|
||||
data-test-id="node-pin-button"
|
||||
>
|
||||
<template v-if="isMocked(data)">
|
||||
{{ locale.baseText('contextMenu.unpin') }}
|
||||
</template>
|
||||
<template v-else> {{ locale.baseText('contextMenu.pin') }}</template>
|
||||
</N8nButton>
|
||||
</div>
|
||||
</template>
|
||||
<N8nButton
|
||||
v-if="isPinned(data)"
|
||||
icon="thumbtack"
|
||||
block
|
||||
type="secondary"
|
||||
:class="$style.customSecondary"
|
||||
data-test-id="node-pin-button"
|
||||
@click="onPinButtonClick(data)"
|
||||
>
|
||||
Un Mock
|
||||
</N8nButton>
|
||||
<N8nButton
|
||||
v-else
|
||||
icon="thumbtack"
|
||||
block
|
||||
type="secondary"
|
||||
data-test-id="node-pin-button"
|
||||
@click="onPinButtonClick(data)"
|
||||
>
|
||||
Mock
|
||||
</N8nButton>
|
||||
</N8nTooltip>
|
||||
</div>
|
||||
</CanvasNode>
|
||||
</N8nTooltip>
|
||||
</template>
|
||||
</Canvas>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.mockNode {
|
||||
// remove selection outline
|
||||
--color-canvas-selected-transparent: transparent;
|
||||
}
|
||||
|
||||
.isTrigger {
|
||||
--canvas-node--border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100vw;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid var(--color-foreground-light);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.pinButtonContainer {
|
||||
position: absolute;
|
||||
@@ -202,16 +217,16 @@ onMounted(loadData);
|
||||
transform: translateX(50%);
|
||||
|
||||
&.pinButtonContainerPinned {
|
||||
background-color: hsla(247, 49%, 55%, 1);
|
||||
background-color: var(--color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.customSecondary {
|
||||
--button-background-color: hsla(247, 49%, 55%, 1);
|
||||
--button-background-color: var(--color-secondary);
|
||||
--button-font-color: var(--color-button-primary-font);
|
||||
--button-border-color: hsla(247, 49%, 55%, 1);
|
||||
--button-border-color: var(--color-secondary);
|
||||
|
||||
--button-hover-background-color: hsla(247, 49%, 55%, 1);
|
||||
--button-hover-background-color: var(--color-secondary);
|
||||
--button-hover-border-color: var(--color-button-primary-font);
|
||||
--button-hover-font-color: var(--color-button-primary-font);
|
||||
}
|
||||
|
||||
@@ -3,15 +3,16 @@ import type { TestMetricRecord } from '@/api/testDefinition.ee';
|
||||
import BlockArrow from '@/components/TestDefinition/EditDefinition/BlockArrow.vue';
|
||||
import EvaluationStep from '@/components/TestDefinition/EditDefinition/EvaluationStep.vue';
|
||||
import MetricsInput from '@/components/TestDefinition/EditDefinition/MetricsInput.vue';
|
||||
import NodesPinning from '@/components/TestDefinition/EditDefinition/NodesPinning.vue';
|
||||
import WorkflowSelector from '@/components/TestDefinition/EditDefinition/WorkflowSelector.vue';
|
||||
import type { EditableFormState, EvaluationFormState } from '@/components/TestDefinition/types';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useMessage } from '@/composables/useMessage';
|
||||
import { NODE_PINNING_MODAL_KEY } from '@/constants';
|
||||
import type { ITag, ModalState } from '@/Interface';
|
||||
import { N8nButton, N8nTag, N8nText } from '@n8n/design-system';
|
||||
import type { ITag } from '@/Interface';
|
||||
import { N8nButton, N8nHeading, N8nTag, N8nText } from '@n8n/design-system';
|
||||
import type { IPinData } from 'n8n-workflow';
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
tagsById: Record<string, ITag>;
|
||||
@@ -68,8 +69,6 @@ const mockedNodes = defineModel<EvaluationFormState['mockedNodes']>('mockedNodes
|
||||
required: true,
|
||||
});
|
||||
|
||||
const nodePinningModal = ref<ModalState | null>(null);
|
||||
|
||||
const selectedTag = computed(() => props.tagsById[tags.value.value[0]] ?? {});
|
||||
|
||||
function openExecutionsView() {
|
||||
@@ -198,13 +197,17 @@ function openExecutionsView() {
|
||||
</template>
|
||||
</EvaluationStep>
|
||||
</div>
|
||||
<Modal ref="nodePinningModal" width="80vw" height="85vh" :name="NODE_PINNING_MODAL_KEY">
|
||||
<Modal
|
||||
width="calc(100% - (48px * 2))"
|
||||
height="calc(100% - (48px * 2))"
|
||||
:custom-class="$style.pinnigModal"
|
||||
:name="NODE_PINNING_MODAL_KEY"
|
||||
>
|
||||
<template #header>
|
||||
<N8nHeading size="large" :bold="true">
|
||||
<N8nHeading tag="h3" size="xlarge" color="text-dark" class="mb-2xs">
|
||||
{{ locale.baseText('testDefinition.edit.selectNodes') }}
|
||||
</N8nHeading>
|
||||
<br />
|
||||
<N8nText>
|
||||
<N8nText color="text-base">
|
||||
{{ locale.baseText('testDefinition.edit.modal.description') }}
|
||||
</N8nText>
|
||||
</template>
|
||||
@@ -216,6 +219,11 @@ function openExecutionsView() {
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.pinnigModal {
|
||||
--dialog-max-width: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.nestedSteps {
|
||||
display: grid;
|
||||
grid-template-columns: 20% 1fr;
|
||||
|
||||
@@ -94,21 +94,18 @@ describe('NodesPinning', () => {
|
||||
expect(container.querySelector('[data-node-name="Node 2"]')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should update node classes when pinning/unpinning nodes', async () => {
|
||||
const { container } = renderComponent();
|
||||
it('should update UI when pinning/unpinning nodes', async () => {
|
||||
const { container, getAllByTestId } = renderComponent();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector('[data-node-name="Node 1"]')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector('[data-node-name="Node 1"]')).toHaveClass(
|
||||
'canvasNode pinnedNode',
|
||||
);
|
||||
expect(container.querySelector('[data-node-name="Node 2"]')).toHaveClass(
|
||||
'canvasNode notPinnedNode',
|
||||
);
|
||||
});
|
||||
const buttons = getAllByTestId('node-pin-button');
|
||||
expect(buttons.length).toBe(2);
|
||||
|
||||
expect(buttons[0]).toHaveTextContent('Unpin');
|
||||
expect(buttons[1]).toHaveTextContent('Pin');
|
||||
});
|
||||
|
||||
it('should emit update:modelValue when pinning nodes', async () => {
|
||||
|
||||
@@ -1,23 +1,42 @@
|
||||
<script lang="ts" setup>
|
||||
import ContextMenu from '@/components/ContextMenu/ContextMenu.vue';
|
||||
import {
|
||||
type CanvasLayoutEvent,
|
||||
type CanvasLayoutSource,
|
||||
useCanvasLayout,
|
||||
} from '@/composables/useCanvasLayout';
|
||||
import { useCanvasNodeHover } from '@/composables/useCanvasNodeHover';
|
||||
import { useCanvasTraversal } from '@/composables/useCanvasTraversal';
|
||||
import { type ContextMenuAction, useContextMenu } from '@/composables/useContextMenu';
|
||||
import { useKeybindings } from '@/composables/useKeybindings';
|
||||
import type { PinDataSource } from '@/composables/usePinnedData';
|
||||
import { CanvasKey } from '@/constants';
|
||||
import type { NodeCreatorOpenSource } from '@/Interface';
|
||||
import {
|
||||
type CanvasConnection,
|
||||
type CanvasEventBusEvents,
|
||||
type CanvasNode,
|
||||
type CanvasNodeMoveEvent,
|
||||
type CanvasEventBusEvents,
|
||||
type ConnectStartEvent,
|
||||
CanvasNodeRenderType,
|
||||
} from '@/types';
|
||||
import { GRID_SIZE } from '@/utils/nodeViewUtils';
|
||||
import { isPresent } from '@/utils/typesUtils';
|
||||
import { useDeviceSupport } from '@n8n/composables/useDeviceSupport';
|
||||
import { useShortKeyPress } from '@n8n/composables/useShortKeyPress';
|
||||
import type { EventBus } from '@n8n/utils/event-bus';
|
||||
import { createEventBus } from '@n8n/utils/event-bus';
|
||||
import type {
|
||||
Connection,
|
||||
XYPosition,
|
||||
GraphNode,
|
||||
NodeDragEvent,
|
||||
NodeMouseEvent,
|
||||
GraphNode,
|
||||
XYPosition,
|
||||
} from '@vue-flow/core';
|
||||
import { useVueFlow, VueFlow, PanelPosition, MarkerType } from '@vue-flow/core';
|
||||
import { MarkerType, PanelPosition, useVueFlow, VueFlow } from '@vue-flow/core';
|
||||
import { MiniMap } from '@vue-flow/minimap';
|
||||
import Node from './elements/nodes/CanvasNode.vue';
|
||||
import Edge from './elements/edges/CanvasEdge.vue';
|
||||
import { onKeyDown, onKeyUp, useThrottleFn } from '@vueuse/core';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import {
|
||||
computed,
|
||||
nextTick,
|
||||
@@ -29,29 +48,10 @@ import {
|
||||
useCssModule,
|
||||
watch,
|
||||
} from 'vue';
|
||||
import type { EventBus } from '@n8n/utils/event-bus';
|
||||
import { createEventBus } from '@n8n/utils/event-bus';
|
||||
import { useDeviceSupport } from '@n8n/composables/useDeviceSupport';
|
||||
import { useShortKeyPress } from '@n8n/composables/useShortKeyPress';
|
||||
import { useContextMenu, type ContextMenuAction } from '@/composables/useContextMenu';
|
||||
import { useKeybindings } from '@/composables/useKeybindings';
|
||||
import ContextMenu from '@/components/ContextMenu/ContextMenu.vue';
|
||||
import type { NodeCreatorOpenSource } from '@/Interface';
|
||||
import type { PinDataSource } from '@/composables/usePinnedData';
|
||||
import { isPresent } from '@/utils/typesUtils';
|
||||
import { GRID_SIZE } from '@/utils/nodeViewUtils';
|
||||
import { CanvasKey } from '@/constants';
|
||||
import { onKeyDown, onKeyUp, useThrottleFn } from '@vueuse/core';
|
||||
import CanvasArrowHeadMarker from './elements/edges/CanvasArrowHeadMarker.vue';
|
||||
import CanvasBackground from './elements/background/CanvasBackground.vue';
|
||||
import { useCanvasTraversal } from '@/composables/useCanvasTraversal';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import { useCanvasNodeHover } from '@/composables/useCanvasNodeHover';
|
||||
import {
|
||||
type CanvasLayoutEvent,
|
||||
type CanvasLayoutSource,
|
||||
useCanvasLayout,
|
||||
} from '@/composables/useCanvasLayout';
|
||||
import CanvasArrowHeadMarker from './elements/edges/CanvasArrowHeadMarker.vue';
|
||||
import Edge from './elements/edges/CanvasEdge.vue';
|
||||
import Node from './elements/nodes/CanvasNode.vue';
|
||||
|
||||
const $style = useCssModule();
|
||||
|
||||
@@ -804,29 +804,31 @@ provide(CanvasKey, {
|
||||
@drop="onDrop"
|
||||
>
|
||||
<template #node-canvas-node="nodeProps">
|
||||
<Node
|
||||
v-bind="nodeProps"
|
||||
:read-only="readOnly"
|
||||
:event-bus="eventBus"
|
||||
:hovered="nodesHoveredById[nodeProps.id]"
|
||||
:nearby-hovered="nodeProps.id === hoveredTriggerNode.id.value"
|
||||
@delete="onDeleteNode"
|
||||
@run="onRunNode"
|
||||
@select="onSelectNode"
|
||||
@toggle="onToggleNodeEnabled"
|
||||
@activate="onSetNodeActivated"
|
||||
@deactivate="onSetNodeDeactivated"
|
||||
@open:contextmenu="onOpenNodeContextMenu"
|
||||
@update="onUpdateNodeParameters"
|
||||
@update:inputs="onUpdateNodeInputs"
|
||||
@update:outputs="onUpdateNodeOutputs"
|
||||
@move="onUpdateNodePosition"
|
||||
@add="onClickNodeAdd"
|
||||
>
|
||||
<template v-if="$slots.nodeToolbar" #toolbar="toolbarProps">
|
||||
<slot name="nodeToolbar" v-bind="toolbarProps" />
|
||||
</template>
|
||||
</Node>
|
||||
<slot name="node" v-bind="{ nodeProps }">
|
||||
<Node
|
||||
v-bind="nodeProps"
|
||||
:read-only="readOnly"
|
||||
:event-bus="eventBus"
|
||||
:hovered="nodesHoveredById[nodeProps.id]"
|
||||
:nearby-hovered="nodeProps.id === hoveredTriggerNode.id.value"
|
||||
@delete="onDeleteNode"
|
||||
@run="onRunNode"
|
||||
@select="onSelectNode"
|
||||
@toggle="onToggleNodeEnabled"
|
||||
@activate="onSetNodeActivated"
|
||||
@deactivate="onSetNodeDeactivated"
|
||||
@open:contextmenu="onOpenNodeContextMenu"
|
||||
@update="onUpdateNodeParameters"
|
||||
@update:inputs="onUpdateNodeInputs"
|
||||
@update:outputs="onUpdateNodeOutputs"
|
||||
@move="onUpdateNodePosition"
|
||||
@add="onClickNodeAdd"
|
||||
>
|
||||
<template v-if="$slots.nodeToolbar" #toolbar="toolbarProps">
|
||||
<slot name="nodeToolbar" v-bind="toolbarProps" />
|
||||
</template>
|
||||
</Node>
|
||||
</slot>
|
||||
</template>
|
||||
|
||||
<template #edge-canvas-edge="edgeProps">
|
||||
|
||||
Reference in New Issue
Block a user