mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-12 13:27:31 -08:00
feat: Add sticky notes support to the new canvas (no-changelog) (#10031)
This commit is contained in:
parent
9302e33d55
commit
cd24c71a9e
|
@ -0,0 +1,69 @@
|
||||||
|
import { action } from '@storybook/addon-actions';
|
||||||
|
import type { StoryFn } from '@storybook/vue3';
|
||||||
|
import N8nResizeableSticky from './ResizeableSticky.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Atoms/ResizeableSticky',
|
||||||
|
component: N8nResizeableSticky,
|
||||||
|
argTypes: {
|
||||||
|
content: {
|
||||||
|
control: {
|
||||||
|
control: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
control: {
|
||||||
|
control: 'number',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
minHeight: {
|
||||||
|
control: {
|
||||||
|
control: 'number',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
minWidth: {
|
||||||
|
control: {
|
||||||
|
control: 'number',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
readOnly: {
|
||||||
|
control: {
|
||||||
|
control: 'Boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
control: {
|
||||||
|
control: 'number',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const methods = {
|
||||||
|
onInput: action('update:modelValue'),
|
||||||
|
onResize: action('resize'),
|
||||||
|
onResizeEnd: action('resizeend'),
|
||||||
|
onResizeStart: action('resizestart'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const Template: StoryFn = (args, { argTypes }) => ({
|
||||||
|
setup: () => ({ args }),
|
||||||
|
props: Object.keys(argTypes),
|
||||||
|
components: {
|
||||||
|
N8nResizeableSticky,
|
||||||
|
},
|
||||||
|
template:
|
||||||
|
'<n8n-resizeable-sticky v-bind="args" @resize="onResize" @resizeend="onResizeEnd" @resizeStart="onResizeStart" @input="onInput"></n8n-resizeable-sticky>',
|
||||||
|
methods,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ResizeableSticky = Template.bind({});
|
||||||
|
ResizeableSticky.args = {
|
||||||
|
height: 160,
|
||||||
|
width: 150,
|
||||||
|
modelValue:
|
||||||
|
"## I'm a note \n**Double click** to edit me. [Guide](https://docs.n8n.io/workflows/sticky-notes/)",
|
||||||
|
minHeight: 80,
|
||||||
|
minWidth: 150,
|
||||||
|
readOnly: false,
|
||||||
|
};
|
|
@ -0,0 +1,61 @@
|
||||||
|
<template>
|
||||||
|
<N8nResizeWrapper
|
||||||
|
:is-resizing-enabled="!readOnly"
|
||||||
|
:height="height"
|
||||||
|
:width="width"
|
||||||
|
:min-height="minHeight"
|
||||||
|
:min-width="minWidth"
|
||||||
|
:scale="scale"
|
||||||
|
:grid-size="gridSize"
|
||||||
|
@resizeend="onResizeEnd"
|
||||||
|
@resize="onResize"
|
||||||
|
@resizestart="onResizeStart"
|
||||||
|
>
|
||||||
|
<N8nSticky v-bind="stickyBindings" />
|
||||||
|
</N8nResizeWrapper>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, ref, useAttrs } from 'vue';
|
||||||
|
import N8nResizeWrapper, { type ResizeData } from '../N8nResizeWrapper/ResizeWrapper.vue';
|
||||||
|
import N8nSticky from '../N8nSticky/Sticky.vue';
|
||||||
|
import type { StickyProps } from '../N8nSticky/types';
|
||||||
|
import { defaultStickyProps } from '../N8nSticky/constants';
|
||||||
|
|
||||||
|
type ResizeableStickyProps = StickyProps & {
|
||||||
|
scale?: number;
|
||||||
|
gridSize?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<ResizeableStickyProps>(), {
|
||||||
|
...defaultStickyProps,
|
||||||
|
scale: 1,
|
||||||
|
gridSize: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
resize: [values: ResizeData];
|
||||||
|
resizestart: [];
|
||||||
|
resizeend: [];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const attrs = useAttrs();
|
||||||
|
|
||||||
|
const stickyBindings = computed(() => ({ ...props, ...attrs }));
|
||||||
|
|
||||||
|
const isResizing = ref(false);
|
||||||
|
|
||||||
|
const onResize = (values: ResizeData) => {
|
||||||
|
emit('resize', values);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onResizeStart = () => {
|
||||||
|
isResizing.value = true;
|
||||||
|
emit('resizestart');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onResizeEnd = () => {
|
||||||
|
isResizing.value = false;
|
||||||
|
emit('resizeend');
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -0,0 +1,3 @@
|
||||||
|
import ResizeableSticky from './ResizeableSticky.vue';
|
||||||
|
|
||||||
|
export default ResizeableSticky;
|
|
@ -61,9 +61,7 @@ export const Sticky = Template.bind({});
|
||||||
Sticky.args = {
|
Sticky.args = {
|
||||||
height: 160,
|
height: 160,
|
||||||
width: 150,
|
width: 150,
|
||||||
content:
|
modelValue:
|
||||||
"## I'm a note \n**Double click** to edit me. [Guide](https://docs.n8n.io/workflows/sticky-notes/)",
|
|
||||||
defaultText:
|
|
||||||
"## I'm a note \n**Double click** to edit me. [Guide](https://docs.n8n.io/workflows/sticky-notes/)",
|
"## I'm a note \n**Double click** to edit me. [Guide](https://docs.n8n.io/workflows/sticky-notes/)",
|
||||||
minHeight: 80,
|
minHeight: 80,
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
|
|
|
@ -9,52 +9,40 @@
|
||||||
:style="styles"
|
:style="styles"
|
||||||
@keydown.prevent
|
@keydown.prevent
|
||||||
>
|
>
|
||||||
<N8nResizeWrapper
|
<div v-show="!editMode" :class="$style.wrapper" @dblclick.stop="onDoubleClick">
|
||||||
:is-resizing-enabled="!readOnly"
|
<N8nMarkdown
|
||||||
:height="height"
|
theme="sticky"
|
||||||
:width="width"
|
:content="modelValue"
|
||||||
:min-height="minHeight"
|
:with-multi-breaks="true"
|
||||||
:min-width="minWidth"
|
@markdown-click="onMarkdownClick"
|
||||||
:scale="scale"
|
@update-content="onUpdateModelValue"
|
||||||
:grid-size="gridSize"
|
/>
|
||||||
@resizeend="onResizeEnd"
|
</div>
|
||||||
@resize="onResize"
|
<div
|
||||||
@resizestart="onResizeStart"
|
v-show="editMode"
|
||||||
|
:class="{ 'full-height': !shouldShowFooter, 'sticky-textarea': true }"
|
||||||
|
@click.stop
|
||||||
|
@mousedown.stop
|
||||||
|
@mouseup.stop
|
||||||
|
@keydown.esc="onInputBlur"
|
||||||
|
@keydown.stop
|
||||||
>
|
>
|
||||||
<div v-show="!editMode" :class="$style.wrapper" @dblclick.stop="onDoubleClick">
|
<N8nInput
|
||||||
<N8nMarkdown
|
ref="input"
|
||||||
theme="sticky"
|
:model-value="modelValue"
|
||||||
:content="modelValue"
|
:name="inputName"
|
||||||
:with-multi-breaks="true"
|
type="textarea"
|
||||||
@markdown-click="onMarkdownClick"
|
:rows="5"
|
||||||
@update-content="onUpdateModelValue"
|
@blur="onInputBlur"
|
||||||
/>
|
@update:model-value="onUpdateModelValue"
|
||||||
</div>
|
@wheel="onInputScroll"
|
||||||
<div
|
/>
|
||||||
v-show="editMode"
|
</div>
|
||||||
:class="{ 'full-height': !shouldShowFooter, 'sticky-textarea': true }"
|
<div v-if="editMode && shouldShowFooter" :class="$style.footer">
|
||||||
@click.stop
|
<N8nText size="xsmall" align="right">
|
||||||
@mousedown.stop
|
<span v-html="t('sticky.markdownHint')"></span>
|
||||||
@mouseup.stop
|
</N8nText>
|
||||||
@keydown.esc="onInputBlur"
|
</div>
|
||||||
@keydown.stop
|
|
||||||
>
|
|
||||||
<N8nInput
|
|
||||||
ref="input"
|
|
||||||
:model-value="modelValue"
|
|
||||||
type="textarea"
|
|
||||||
:rows="5"
|
|
||||||
@blur="onInputBlur"
|
|
||||||
@update:model-value="onUpdateModelValue"
|
|
||||||
@wheel="onInputScroll"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-if="editMode && shouldShowFooter" :class="$style.footer">
|
|
||||||
<N8nText size="xsmall" align="right">
|
|
||||||
<span v-html="t('sticky.markdownHint')"></span>
|
|
||||||
</N8nText>
|
|
||||||
</div>
|
|
||||||
</N8nResizeWrapper>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -62,45 +50,17 @@
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import N8nInput from '../N8nInput';
|
import N8nInput from '../N8nInput';
|
||||||
import N8nMarkdown from '../N8nMarkdown';
|
import N8nMarkdown from '../N8nMarkdown';
|
||||||
import N8nResizeWrapper, { type ResizeData } from '../N8nResizeWrapper/ResizeWrapper.vue';
|
|
||||||
import N8nText from '../N8nText';
|
import N8nText from '../N8nText';
|
||||||
import { useI18n } from '../../composables/useI18n';
|
import { useI18n } from '../../composables/useI18n';
|
||||||
|
import { defaultStickyProps } from './constants';
|
||||||
|
import type { StickyProps } from './types';
|
||||||
|
|
||||||
interface StickyProps {
|
const props = withDefaults(defineProps<StickyProps>(), defaultStickyProps);
|
||||||
modelValue?: string;
|
|
||||||
height?: number;
|
|
||||||
width?: number;
|
|
||||||
minHeight?: number;
|
|
||||||
minWidth?: number;
|
|
||||||
scale?: number;
|
|
||||||
gridSize?: number;
|
|
||||||
id?: string;
|
|
||||||
defaultText?: string;
|
|
||||||
editMode?: boolean;
|
|
||||||
readOnly?: boolean;
|
|
||||||
backgroundColor?: number | string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<StickyProps>(), {
|
|
||||||
height: 180,
|
|
||||||
width: 240,
|
|
||||||
minHeight: 80,
|
|
||||||
minWidth: 150,
|
|
||||||
scale: 1,
|
|
||||||
gridSize: 20,
|
|
||||||
id: '0',
|
|
||||||
editMode: false,
|
|
||||||
readOnly: false,
|
|
||||||
backgroundColor: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
edit: [editing: boolean];
|
edit: [editing: boolean];
|
||||||
'update:modelValue': [value: string];
|
'update:modelValue': [value: string];
|
||||||
'markdown-click': [link: string, e: Event];
|
'markdown-click': [link: string, e: Event];
|
||||||
resize: [values: ResizeData];
|
|
||||||
resizestart: [];
|
|
||||||
resizeend: [];
|
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -115,6 +75,8 @@ const resWidth = computed((): number => {
|
||||||
return props.width < props.minWidth ? props.minWidth : props.width;
|
return props.width < props.minWidth ? props.minWidth : props.width;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const inputName = computed(() => (props.id ? `${props.id}-input` : undefined));
|
||||||
|
|
||||||
const styles = computed((): { height: string; width: string } => ({
|
const styles = computed((): { height: string; width: string } => ({
|
||||||
height: `${resHeight.value}px`,
|
height: `${resHeight.value}px`,
|
||||||
width: `${resWidth.value}px`,
|
width: `${resWidth.value}px`,
|
||||||
|
@ -152,20 +114,6 @@ const onMarkdownClick = (link: string, event: Event) => {
|
||||||
emit('markdown-click', link, event);
|
emit('markdown-click', link, event);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onResize = (values: ResizeData) => {
|
|
||||||
emit('resize', values);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onResizeStart = () => {
|
|
||||||
isResizing.value = true;
|
|
||||||
emit('resizestart');
|
|
||||||
};
|
|
||||||
|
|
||||||
const onResizeEnd = () => {
|
|
||||||
isResizing.value = false;
|
|
||||||
emit('resizeend');
|
|
||||||
};
|
|
||||||
|
|
||||||
const onInputScroll = (event: WheelEvent) => {
|
const onInputScroll = (event: WheelEvent) => {
|
||||||
// Pass through zoom events but hold regular scrolling
|
// Pass through zoom events but hold regular scrolling
|
||||||
if (!event.ctrlKey && !event.metaKey) {
|
if (!event.ctrlKey && !event.metaKey) {
|
||||||
|
|
10
packages/design-system/src/components/N8nSticky/constants.ts
Normal file
10
packages/design-system/src/components/N8nSticky/constants.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
export const defaultStickyProps = {
|
||||||
|
height: 180,
|
||||||
|
width: 240,
|
||||||
|
minHeight: 80,
|
||||||
|
minWidth: 150,
|
||||||
|
id: '0',
|
||||||
|
editMode: false,
|
||||||
|
readOnly: false,
|
||||||
|
backgroundColor: 1,
|
||||||
|
};
|
12
packages/design-system/src/components/N8nSticky/types.ts
Normal file
12
packages/design-system/src/components/N8nSticky/types.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
export interface StickyProps {
|
||||||
|
modelValue?: string;
|
||||||
|
height?: number;
|
||||||
|
width?: number;
|
||||||
|
minHeight?: number;
|
||||||
|
minWidth?: number;
|
||||||
|
id?: string;
|
||||||
|
defaultText?: string;
|
||||||
|
editMode?: boolean;
|
||||||
|
readOnly?: boolean;
|
||||||
|
backgroundColor?: number | string;
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ export { default as N8nResizeWrapper } from './N8nResizeWrapper';
|
||||||
export { default as N8nSelect } from './N8nSelect';
|
export { default as N8nSelect } from './N8nSelect';
|
||||||
export { default as N8nSpinner } from './N8nSpinner';
|
export { default as N8nSpinner } from './N8nSpinner';
|
||||||
export { default as N8nSticky } from './N8nSticky';
|
export { default as N8nSticky } from './N8nSticky';
|
||||||
|
export { default as N8nResizeableSticky } from './N8nResizeableSticky';
|
||||||
export { default as N8nTabs } from './N8nTabs';
|
export { default as N8nTabs } from './N8nTabs';
|
||||||
export { default as N8nTag } from './N8nTag';
|
export { default as N8nTag } from './N8nTag';
|
||||||
export { default as N8nTags } from './N8nTags';
|
export { default as N8nTags } from './N8nTags';
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
"@vue-flow/controls": "^1.1.1",
|
"@vue-flow/controls": "^1.1.1",
|
||||||
"@vue-flow/core": "^1.33.5",
|
"@vue-flow/core": "^1.33.5",
|
||||||
"@vue-flow/minimap": "^1.4.0",
|
"@vue-flow/minimap": "^1.4.0",
|
||||||
|
"@vue-flow/node-resizer": "^1.4.0",
|
||||||
"@vueuse/components": "^10.11.0",
|
"@vueuse/components": "^10.11.0",
|
||||||
"@vueuse/core": "^10.11.0",
|
"@vueuse/core": "^10.11.0",
|
||||||
"axios": "1.6.7",
|
"axios": "1.6.7",
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { CanvasNodeRenderType } from '@/types';
|
||||||
|
|
||||||
export function createCanvasNodeData({
|
export function createCanvasNodeData({
|
||||||
id = 'node',
|
id = 'node',
|
||||||
|
name = 'Test Node',
|
||||||
type = 'test',
|
type = 'test',
|
||||||
typeVersion = 1,
|
typeVersion = 1,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
@ -21,13 +22,14 @@ export function createCanvasNodeData({
|
||||||
},
|
},
|
||||||
}: Partial<CanvasNodeData> = {}): CanvasNodeData {
|
}: Partial<CanvasNodeData> = {}): CanvasNodeData {
|
||||||
return {
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
typeVersion,
|
||||||
execution,
|
execution,
|
||||||
issues,
|
issues,
|
||||||
pinnedData,
|
pinnedData,
|
||||||
runData,
|
runData,
|
||||||
id,
|
|
||||||
type,
|
|
||||||
typeVersion,
|
|
||||||
disabled,
|
disabled,
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
|
|
|
@ -23,6 +23,7 @@ import {
|
||||||
MANUAL_TRIGGER_NODE_TYPE,
|
MANUAL_TRIGGER_NODE_TYPE,
|
||||||
NO_OP_NODE_TYPE,
|
NO_OP_NODE_TYPE,
|
||||||
SET_NODE_TYPE,
|
SET_NODE_TYPE,
|
||||||
|
STICKY_NODE_TYPE,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
import type { INodeUi } from '@/Interface';
|
import type { INodeUi } from '@/Interface';
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ export const mockNode = ({
|
||||||
disabled = false,
|
disabled = false,
|
||||||
issues = undefined,
|
issues = undefined,
|
||||||
typeVersion = 1,
|
typeVersion = 1,
|
||||||
|
parameters = {},
|
||||||
}: {
|
}: {
|
||||||
id?: INodeUi['id'];
|
id?: INodeUi['id'];
|
||||||
name: INodeUi['name'];
|
name: INodeUi['name'];
|
||||||
|
@ -42,7 +44,8 @@ export const mockNode = ({
|
||||||
disabled?: INodeUi['disabled'];
|
disabled?: INodeUi['disabled'];
|
||||||
issues?: INodeIssues;
|
issues?: INodeIssues;
|
||||||
typeVersion?: INodeUi['typeVersion'];
|
typeVersion?: INodeUi['typeVersion'];
|
||||||
}) => mock<INodeUi>({ id, name, type, position, disabled, issues, typeVersion });
|
parameters?: INodeUi['parameters'];
|
||||||
|
}) => mock<INodeUi>({ id, name, type, position, disabled, issues, typeVersion, parameters });
|
||||||
|
|
||||||
export const mockNodeTypeDescription = ({
|
export const mockNodeTypeDescription = ({
|
||||||
name,
|
name,
|
||||||
|
@ -90,6 +93,7 @@ export const mockNodes = [
|
||||||
mockNode({ name: 'Rename', type: SET_NODE_TYPE }),
|
mockNode({ name: 'Rename', type: SET_NODE_TYPE }),
|
||||||
mockNode({ name: 'Chat Trigger', type: CHAT_TRIGGER_NODE_TYPE }),
|
mockNode({ name: 'Chat Trigger', type: CHAT_TRIGGER_NODE_TYPE }),
|
||||||
mockNode({ name: 'Agent', type: AGENT_NODE_TYPE }),
|
mockNode({ name: 'Agent', type: AGENT_NODE_TYPE }),
|
||||||
|
mockNode({ name: 'Sticky', type: STICKY_NODE_TYPE }),
|
||||||
mockNode({ name: 'End', type: NO_OP_NODE_TYPE }),
|
mockNode({ name: 'End', type: NO_OP_NODE_TYPE }),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
@click.left="mouseLeftClick"
|
@click.left="mouseLeftClick"
|
||||||
@contextmenu="onContextMenu"
|
@contextmenu="onContextMenu"
|
||||||
>
|
>
|
||||||
<n8n-sticky
|
<N8nResizeableSticky
|
||||||
v-if="node"
|
v-if="node"
|
||||||
:id="node.id"
|
:id="node.id"
|
||||||
:model-value="node.parameters.content"
|
:model-value="node.parameters.content"
|
||||||
|
|
|
@ -19,6 +19,7 @@ const emit = defineEmits<{
|
||||||
'update:node:active': [id: string];
|
'update:node:active': [id: string];
|
||||||
'update:node:enabled': [id: string];
|
'update:node:enabled': [id: string];
|
||||||
'update:node:selected': [id?: string];
|
'update:node:selected': [id?: string];
|
||||||
|
'update:node:parameters': [id: string, parameters: Record<string, unknown>];
|
||||||
'run:node': [id: string];
|
'run:node': [id: string];
|
||||||
'delete:node': [id: string];
|
'delete:node': [id: string];
|
||||||
'delete:connection': [connection: Connection];
|
'delete:connection': [connection: Connection];
|
||||||
|
@ -57,10 +58,14 @@ const { getSelectedEdges, getSelectedNodes, viewportRef, fitView, project } = us
|
||||||
|
|
||||||
function onNodeDragStop(e: NodeDragEvent) {
|
function onNodeDragStop(e: NodeDragEvent) {
|
||||||
e.nodes.forEach((node) => {
|
e.nodes.forEach((node) => {
|
||||||
emit('update:node:position', node.id, node.position);
|
onUpdateNodePosition(node.id, node.position);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onUpdateNodePosition(id: string, position: XYPosition) {
|
||||||
|
emit('update:node:position', id, position);
|
||||||
|
}
|
||||||
|
|
||||||
function onSelectionDragStop(e: NodeDragEvent) {
|
function onSelectionDragStop(e: NodeDragEvent) {
|
||||||
onNodeDragStop(e);
|
onNodeDragStop(e);
|
||||||
}
|
}
|
||||||
|
@ -82,6 +87,10 @@ function onDeleteNode(id: string) {
|
||||||
emit('delete:node', id);
|
emit('delete:node', id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onUpdateNodeParameters(id: string, parameters: Record<string, unknown>) {
|
||||||
|
emit('update:node:parameters', id, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connections
|
* Connections
|
||||||
*/
|
*/
|
||||||
|
@ -222,6 +231,8 @@ onUnmounted(() => {
|
||||||
@select="onSelectNode"
|
@select="onSelectNode"
|
||||||
@toggle="onToggleNodeEnabled"
|
@toggle="onToggleNodeEnabled"
|
||||||
@activate="onSetNodeActive"
|
@activate="onSetNodeActive"
|
||||||
|
@update="onUpdateNodeParameters"
|
||||||
|
@move="onUpdateNodePosition"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,8 @@ defineEmits<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
waitingForWebhook: boolean;
|
waitingForWebhook?: boolean;
|
||||||
executing: boolean;
|
executing?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Position } from '@vue-flow/core';
|
|
||||||
import { computed, provide, toRef, watch } from 'vue';
|
import { computed, provide, toRef, watch } from 'vue';
|
||||||
import type { CanvasNodeData, CanvasConnectionPort, CanvasElementPortWithPosition } from '@/types';
|
import type { CanvasNodeData, CanvasConnectionPort, CanvasElementPortWithPosition } from '@/types';
|
||||||
import NodeIcon from '@/components/NodeIcon.vue';
|
import NodeIcon from '@/components/NodeIcon.vue';
|
||||||
|
@ -9,7 +8,8 @@ import CanvasNodeRenderer from '@/components/canvas/elements/nodes/CanvasNodeRen
|
||||||
import HandleRenderer from '@/components/canvas/elements/handles/HandleRenderer.vue';
|
import HandleRenderer from '@/components/canvas/elements/handles/HandleRenderer.vue';
|
||||||
import { useNodeConnections } from '@/composables/useNodeConnections';
|
import { useNodeConnections } from '@/composables/useNodeConnections';
|
||||||
import { CanvasNodeKey } from '@/constants';
|
import { CanvasNodeKey } from '@/constants';
|
||||||
import type { NodeProps } from '@vue-flow/core';
|
import { Position } from '@vue-flow/core';
|
||||||
|
import type { XYPosition, NodeProps } from '@vue-flow/core';
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
delete: [id: string];
|
delete: [id: string];
|
||||||
|
@ -17,6 +17,8 @@ const emit = defineEmits<{
|
||||||
select: [id: string, selected: boolean];
|
select: [id: string, selected: boolean];
|
||||||
toggle: [id: string];
|
toggle: [id: string];
|
||||||
activate: [id: string];
|
activate: [id: string];
|
||||||
|
update: [id: string, parameters: Record<string, unknown>];
|
||||||
|
move: [id: string, position: XYPosition];
|
||||||
}>();
|
}>();
|
||||||
const props = defineProps<NodeProps<CanvasNodeData>>();
|
const props = defineProps<NodeProps<CanvasNodeData>>();
|
||||||
|
|
||||||
|
@ -103,7 +105,9 @@ provide(CanvasNodeKey, {
|
||||||
nodeType,
|
nodeType,
|
||||||
});
|
});
|
||||||
|
|
||||||
const nodeIconSize = computed(() => (data.value.render.options.configuration ? 30 : 40));
|
const nodeIconSize = computed(() =>
|
||||||
|
'configuration' in data.value.render.options && data.value.render.options.configuration ? 30 : 40,
|
||||||
|
);
|
||||||
|
|
||||||
function onDelete() {
|
function onDelete() {
|
||||||
emit('delete', props.id);
|
emit('delete', props.id);
|
||||||
|
@ -120,6 +124,14 @@ function onDisabledToggle() {
|
||||||
function onActivate() {
|
function onActivate() {
|
||||||
emit('activate', props.id);
|
emit('activate', props.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onUpdate(parameters: Record<string, unknown>) {
|
||||||
|
emit('update', props.id, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMove(position: XYPosition) {
|
||||||
|
emit('move', props.id, position);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -157,7 +169,7 @@ function onActivate() {
|
||||||
@run="onRun"
|
@run="onRun"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CanvasNodeRenderer @dblclick="onActivate">
|
<CanvasNodeRenderer @dblclick="onActivate" @move="onMove" @update="onUpdate">
|
||||||
<NodeIcon
|
<NodeIcon
|
||||||
v-if="nodeType"
|
v-if="nodeType"
|
||||||
:node-type="nodeType"
|
:node-type="nodeType"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { h, inject } from 'vue';
|
import { h, inject } from 'vue';
|
||||||
import CanvasNodeDefault from '@/components/canvas/elements/nodes/render-types/CanvasNodeDefault.vue';
|
import CanvasNodeDefault from '@/components/canvas/elements/nodes/render-types/CanvasNodeDefault.vue';
|
||||||
|
import CanvasNodeStickyNote from '@/components/canvas/elements/nodes/render-types/CanvasNodeStickyNote.vue';
|
||||||
import CanvasNodeAddNodes from '@/components/canvas/elements/nodes/render-types/CanvasNodeAddNodes.vue';
|
import CanvasNodeAddNodes from '@/components/canvas/elements/nodes/render-types/CanvasNodeAddNodes.vue';
|
||||||
import { CanvasNodeKey } from '@/constants';
|
import { CanvasNodeKey } from '@/constants';
|
||||||
import { CanvasNodeRenderType } from '@/types';
|
import { CanvasNodeRenderType } from '@/types';
|
||||||
|
@ -14,7 +15,9 @@ const slots = defineSlots<{
|
||||||
const Render = () => {
|
const Render = () => {
|
||||||
let Component;
|
let Component;
|
||||||
switch (node?.data.value.render.type) {
|
switch (node?.data.value.render.type) {
|
||||||
// @TODO Add support for sticky notes here
|
case CanvasNodeRenderType.StickyNote:
|
||||||
|
Component = CanvasNodeStickyNote;
|
||||||
|
break;
|
||||||
case CanvasNodeRenderType.AddNodes:
|
case CanvasNodeRenderType.AddNodes:
|
||||||
Component = CanvasNodeAddNodes;
|
Component = CanvasNodeAddNodes;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCssModule } from 'vue';
|
import { computed, useCssModule } from 'vue';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { useCanvasNode } from '@/composables/useCanvasNode';
|
import { useCanvasNode } from '@/composables/useCanvasNode';
|
||||||
|
import { CanvasNodeRenderType } from '@/types';
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
delete: [];
|
delete: [];
|
||||||
|
@ -12,7 +13,7 @@ const emit = defineEmits<{
|
||||||
const $style = useCssModule();
|
const $style = useCssModule();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
const { renderOptions } = useCanvasNode();
|
const { render } = useCanvasNode();
|
||||||
|
|
||||||
// @TODO
|
// @TODO
|
||||||
const workflowRunning = false;
|
const workflowRunning = false;
|
||||||
|
@ -20,6 +21,18 @@ const workflowRunning = false;
|
||||||
// @TODO
|
// @TODO
|
||||||
const nodeDisabledTitle = 'Test';
|
const nodeDisabledTitle = 'Test';
|
||||||
|
|
||||||
|
const isExecuteNodeVisible = computed(() => {
|
||||||
|
return (
|
||||||
|
render.value.type === CanvasNodeRenderType.Default &&
|
||||||
|
'configuration' in render.value.options &&
|
||||||
|
!render.value.options.configuration
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const isDisableNodeVisible = computed(() => {
|
||||||
|
return render.value.type === CanvasNodeRenderType.Default;
|
||||||
|
});
|
||||||
|
|
||||||
function executeNode() {
|
function executeNode() {
|
||||||
emit('run');
|
emit('run');
|
||||||
}
|
}
|
||||||
|
@ -40,7 +53,7 @@ function openContextMenu(_e: MouseEvent, _type: string) {}
|
||||||
<div :class="$style.canvasNodeToolbar">
|
<div :class="$style.canvasNodeToolbar">
|
||||||
<div :class="$style.canvasNodeToolbarItems">
|
<div :class="$style.canvasNodeToolbarItems">
|
||||||
<N8nIconButton
|
<N8nIconButton
|
||||||
v-if="!renderOptions.configuration"
|
v-if="isExecuteNodeVisible"
|
||||||
data-test-id="execute-node-button"
|
data-test-id="execute-node-button"
|
||||||
type="tertiary"
|
type="tertiary"
|
||||||
text
|
text
|
||||||
|
@ -51,6 +64,7 @@ function openContextMenu(_e: MouseEvent, _type: string) {}
|
||||||
@click="executeNode"
|
@click="executeNode"
|
||||||
/>
|
/>
|
||||||
<N8nIconButton
|
<N8nIconButton
|
||||||
|
v-if="isDisableNodeVisible"
|
||||||
data-test-id="disable-node-button"
|
data-test-id="disable-node-button"
|
||||||
type="tertiary"
|
type="tertiary"
|
||||||
text
|
text
|
||||||
|
|
|
@ -7,6 +7,7 @@ import CanvasNodeStatusIcons from '@/components/canvas/elements/nodes/render-typ
|
||||||
import { useCanvasNode } from '@/composables/useCanvasNode';
|
import { useCanvasNode } from '@/composables/useCanvasNode';
|
||||||
import { NODE_INSERT_SPACER_BETWEEN_INPUT_GROUPS } from '@/constants';
|
import { NODE_INSERT_SPACER_BETWEEN_INPUT_GROUPS } from '@/constants';
|
||||||
import { N8nTooltip } from 'n8n-design-system';
|
import { N8nTooltip } from 'n8n-design-system';
|
||||||
|
import type { CanvasNodeDefaultRender } from '@/types';
|
||||||
|
|
||||||
const $style = useCssModule();
|
const $style = useCssModule();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
@ -22,7 +23,7 @@ const {
|
||||||
executionRunning,
|
executionRunning,
|
||||||
hasRunData,
|
hasRunData,
|
||||||
hasIssues,
|
hasIssues,
|
||||||
renderOptions,
|
render,
|
||||||
} = useCanvasNode();
|
} = useCanvasNode();
|
||||||
const { mainOutputs, nonMainInputs, requiredNonMainInputs } = useNodeConnections({
|
const { mainOutputs, nonMainInputs, requiredNonMainInputs } = useNodeConnections({
|
||||||
inputs,
|
inputs,
|
||||||
|
@ -30,6 +31,8 @@ const { mainOutputs, nonMainInputs, requiredNonMainInputs } = useNodeConnections
|
||||||
connections,
|
connections,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const renderOptions = computed(() => render.value.options as CanvasNodeDefaultRender['options']);
|
||||||
|
|
||||||
const classes = computed(() => {
|
const classes = computed(() => {
|
||||||
return {
|
return {
|
||||||
[$style.node]: true,
|
[$style.node]: true,
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import CanvasNodeStickyNote from '@/components/canvas/elements/nodes/render-types/CanvasNodeStickyNote.vue';
|
||||||
|
import { createComponentRenderer } from '@/__tests__/render';
|
||||||
|
import { createCanvasNodeProvide } from '@/__tests__/data';
|
||||||
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
|
import { setActivePinia } from 'pinia';
|
||||||
|
|
||||||
|
const renderComponent = createComponentRenderer(CanvasNodeStickyNote);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const pinia = createTestingPinia();
|
||||||
|
setActivePinia(pinia);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('CanvasNodeStickyNote', () => {
|
||||||
|
it('should render node correctly', () => {
|
||||||
|
const { getByTestId } = renderComponent({
|
||||||
|
global: {
|
||||||
|
provide: {
|
||||||
|
...createCanvasNodeProvide({
|
||||||
|
id: 'sticky',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getByTestId('canvas-sticky-note-node')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,83 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
/* eslint-disable vue/no-multiple-template-root */
|
||||||
|
import { useCanvasNode } from '@/composables/useCanvasNode';
|
||||||
|
import type { CanvasNodeStickyNoteRender } from '@/types';
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { NodeResizer } from '@vue-flow/node-resizer';
|
||||||
|
import type { OnResize } from '@vue-flow/node-resizer/dist/types';
|
||||||
|
import type { XYPosition } from '@vue-flow/core';
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
update: [parameters: Record<string, unknown>];
|
||||||
|
move: [position: XYPosition];
|
||||||
|
dblclick: [event: MouseEvent];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { id, render } = useCanvasNode();
|
||||||
|
|
||||||
|
const renderOptions = computed(() => render.value.options as CanvasNodeStickyNoteRender['options']);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resizing
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onResize(event: OnResize) {
|
||||||
|
emit('move', {
|
||||||
|
x: event.params.x,
|
||||||
|
y: event.params.y,
|
||||||
|
});
|
||||||
|
|
||||||
|
emit('update', {
|
||||||
|
...(event.params.width ? { width: event.params.width } : {}),
|
||||||
|
...(event.params.height ? { height: event.params.height } : {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content change
|
||||||
|
*/
|
||||||
|
|
||||||
|
const isActive = ref(false);
|
||||||
|
|
||||||
|
function onInputChange(value: string) {
|
||||||
|
emit('update', {
|
||||||
|
content: value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEdit(edit: boolean) {
|
||||||
|
isActive.value = edit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDoubleClick(event: MouseEvent) {
|
||||||
|
emit('dblclick', event);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<NodeResizer
|
||||||
|
:min-height="80"
|
||||||
|
:min-width="150"
|
||||||
|
:height="renderOptions.height"
|
||||||
|
:width="renderOptions.width"
|
||||||
|
@resize="onResize"
|
||||||
|
/>
|
||||||
|
<N8nSticky
|
||||||
|
:id="id"
|
||||||
|
data-test-id="canvas-sticky-note-node"
|
||||||
|
:height="renderOptions.height"
|
||||||
|
:width="renderOptions.width"
|
||||||
|
:class="$style.sticky"
|
||||||
|
:model-value="renderOptions.content"
|
||||||
|
:background="renderOptions.color"
|
||||||
|
:edit-mode="isActive"
|
||||||
|
@edit="onEdit"
|
||||||
|
@dblclick="onDoubleClick"
|
||||||
|
@update:model-value="onInputChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.sticky {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`CanvasNodeStickyNote > should render node correctly 1`] = `
|
||||||
|
<div
|
||||||
|
class="n8n-sticky sticky clickable color-1 sticky"
|
||||||
|
data-test-id="canvas-sticky-note-node"
|
||||||
|
style="height: 180px; width: 240px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="wrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="n8n-markdown"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="sticky"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="sticky-textarea"
|
||||||
|
style="display: none;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="el-textarea el-input--large n8n-input"
|
||||||
|
>
|
||||||
|
<!-- input -->
|
||||||
|
|
||||||
|
<!-- textarea -->
|
||||||
|
<textarea
|
||||||
|
autocomplete="off"
|
||||||
|
class="el-textarea__inner"
|
||||||
|
name="sticky-input"
|
||||||
|
placeholder=""
|
||||||
|
rows="5"
|
||||||
|
tabindex="0"
|
||||||
|
title=""
|
||||||
|
/>
|
||||||
|
<!--v-if-->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--v-if-->
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -12,14 +12,14 @@ import {
|
||||||
mockNodes,
|
mockNodes,
|
||||||
mockNodeTypeDescription,
|
mockNodeTypeDescription,
|
||||||
} from '@/__tests__/mocks';
|
} from '@/__tests__/mocks';
|
||||||
import { MANUAL_TRIGGER_NODE_TYPE, SET_NODE_TYPE } from '@/constants';
|
import { MANUAL_TRIGGER_NODE_TYPE, SET_NODE_TYPE, STICKY_NODE_TYPE } from '@/constants';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import {
|
import {
|
||||||
createCanvasConnectionHandleString,
|
createCanvasConnectionHandleString,
|
||||||
createCanvasConnectionId,
|
createCanvasConnectionId,
|
||||||
} from '@/utils/canvasUtilsV2';
|
} from '@/utils/canvasUtilsV2';
|
||||||
import { CanvasConnectionMode } from '@/types';
|
import { CanvasConnectionMode, CanvasNodeRenderType } from '@/types';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
|
@ -86,6 +86,7 @@ describe('useCanvasMapping', () => {
|
||||||
position: expect.anything(),
|
position: expect.anything(),
|
||||||
data: {
|
data: {
|
||||||
id: manualTriggerNode.id,
|
id: manualTriggerNode.id,
|
||||||
|
name: manualTriggerNode.name,
|
||||||
type: manualTriggerNode.type,
|
type: manualTriggerNode.type,
|
||||||
typeVersion: expect.anything(),
|
typeVersion: expect.anything(),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
@ -224,6 +225,93 @@ describe('useCanvasMapping', () => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('render', () => {
|
||||||
|
it('should handle render options for default node type', () => {
|
||||||
|
const manualTriggerNode = mockNode({
|
||||||
|
name: 'Manual Trigger',
|
||||||
|
type: MANUAL_TRIGGER_NODE_TYPE,
|
||||||
|
disabled: false,
|
||||||
|
});
|
||||||
|
const nodes = [manualTriggerNode];
|
||||||
|
const connections = {};
|
||||||
|
const workflowObject = createTestWorkflowObject({
|
||||||
|
nodes,
|
||||||
|
connections,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { nodes: mappedNodes } = useCanvasMapping({
|
||||||
|
nodes: ref(nodes),
|
||||||
|
connections: ref(connections),
|
||||||
|
workflowObject: ref(workflowObject) as Ref<Workflow>,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mappedNodes.value[0]?.data?.render).toEqual({
|
||||||
|
type: CanvasNodeRenderType.Default,
|
||||||
|
options: {
|
||||||
|
configurable: false,
|
||||||
|
configuration: false,
|
||||||
|
trigger: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle render options for addNodes node type', () => {
|
||||||
|
const addNodesNode = mockNode({
|
||||||
|
name: CanvasNodeRenderType.AddNodes,
|
||||||
|
type: CanvasNodeRenderType.AddNodes,
|
||||||
|
disabled: false,
|
||||||
|
});
|
||||||
|
const nodes = [addNodesNode];
|
||||||
|
const connections = {};
|
||||||
|
const workflowObject = createTestWorkflowObject({
|
||||||
|
nodes: [],
|
||||||
|
connections,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { nodes: mappedNodes } = useCanvasMapping({
|
||||||
|
nodes: ref(nodes),
|
||||||
|
connections: ref(connections),
|
||||||
|
workflowObject: ref(workflowObject) as Ref<Workflow>,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mappedNodes.value[0]?.data?.render).toEqual({
|
||||||
|
type: CanvasNodeRenderType.AddNodes,
|
||||||
|
options: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle render options for stickyNote node type', () => {
|
||||||
|
const stickyNoteNode = mockNode({
|
||||||
|
name: 'Sticky',
|
||||||
|
type: STICKY_NODE_TYPE,
|
||||||
|
disabled: false,
|
||||||
|
parameters: {
|
||||||
|
width: 200,
|
||||||
|
height: 200,
|
||||||
|
color: 3,
|
||||||
|
content: '# Hello world',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const nodes = [stickyNoteNode];
|
||||||
|
const connections = {};
|
||||||
|
const workflowObject = createTestWorkflowObject({
|
||||||
|
nodes,
|
||||||
|
connections,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { nodes: mappedNodes } = useCanvasMapping({
|
||||||
|
nodes: ref(nodes),
|
||||||
|
connections: ref(connections),
|
||||||
|
workflowObject: ref(workflowObject) as Ref<Workflow>,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mappedNodes.value[0]?.data?.render).toEqual({
|
||||||
|
type: CanvasNodeRenderType.StickyNote,
|
||||||
|
options: stickyNoteNode.parameters,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('connections', () => {
|
describe('connections', () => {
|
||||||
|
|
|
@ -13,7 +13,10 @@ import type {
|
||||||
CanvasConnectionData,
|
CanvasConnectionData,
|
||||||
CanvasConnectionPort,
|
CanvasConnectionPort,
|
||||||
CanvasNode,
|
CanvasNode,
|
||||||
|
CanvasNodeAddNodesRender,
|
||||||
CanvasNodeData,
|
CanvasNodeData,
|
||||||
|
CanvasNodeDefaultRender,
|
||||||
|
CanvasNodeStickyNoteRender,
|
||||||
} from '@/types';
|
} from '@/types';
|
||||||
import { CanvasNodeRenderType } from '@/types';
|
import { CanvasNodeRenderType } from '@/types';
|
||||||
import {
|
import {
|
||||||
|
@ -30,7 +33,7 @@ import type {
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeHelpers } from 'n8n-workflow';
|
import { NodeHelpers } from 'n8n-workflow';
|
||||||
import type { INodeUi } from '@/Interface';
|
import type { INodeUi } from '@/Interface';
|
||||||
import { WAIT_TIME_UNLIMITED } from '@/constants';
|
import { STICKY_NODE_TYPE, WAIT_TIME_UNLIMITED } from '@/constants';
|
||||||
import { sanitizeHtml } from '@/utils/htmlUtils';
|
import { sanitizeHtml } from '@/utils/htmlUtils';
|
||||||
|
|
||||||
export function useCanvasMapping({
|
export function useCanvasMapping({
|
||||||
|
@ -46,30 +49,48 @@ export function useCanvasMapping({
|
||||||
const workflowsStore = useWorkflowsStore();
|
const workflowsStore = useWorkflowsStore();
|
||||||
const nodeTypesStore = useNodeTypesStore();
|
const nodeTypesStore = useNodeTypesStore();
|
||||||
|
|
||||||
const renderTypeByNodeType = computed(
|
function createStickyNoteRenderType(node: INodeUi): CanvasNodeStickyNoteRender {
|
||||||
|
return {
|
||||||
|
type: CanvasNodeRenderType.StickyNote,
|
||||||
|
options: {
|
||||||
|
width: node.parameters.width as number,
|
||||||
|
height: node.parameters.height as number,
|
||||||
|
color: node.parameters.color as number,
|
||||||
|
content: node.parameters.content as string,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAddNodesRenderType(): CanvasNodeAddNodesRender {
|
||||||
|
return {
|
||||||
|
type: CanvasNodeRenderType.AddNodes,
|
||||||
|
options: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDefaultNodeRenderType(node: INodeUi): CanvasNodeDefaultRender {
|
||||||
|
return {
|
||||||
|
type: CanvasNodeRenderType.Default,
|
||||||
|
options: {
|
||||||
|
trigger: nodeTypesStore.isTriggerNode(node.type),
|
||||||
|
configuration: nodeTypesStore.isConfigNode(workflowObject.value, node, node.type),
|
||||||
|
configurable: nodeTypesStore.isConfigurableNode(workflowObject.value, node, node.type),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderTypeByNodeId = computed(
|
||||||
() =>
|
() =>
|
||||||
nodes.value.reduce<Record<string, CanvasNodeData['render']>>((acc, node) => {
|
nodes.value.reduce<Record<string, CanvasNodeData['render']>>((acc, node) => {
|
||||||
// @TODO Add support for sticky notes here
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
|
case `${CanvasNodeRenderType.StickyNote}`:
|
||||||
|
acc[node.id] = createStickyNoteRenderType(node);
|
||||||
|
break;
|
||||||
case `${CanvasNodeRenderType.AddNodes}`:
|
case `${CanvasNodeRenderType.AddNodes}`:
|
||||||
acc[node.type] = {
|
acc[node.id] = createAddNodesRenderType();
|
||||||
type: CanvasNodeRenderType.AddNodes,
|
|
||||||
options: {},
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
acc[node.type] = {
|
acc[node.id] = createDefaultNodeRenderType(node);
|
||||||
type: CanvasNodeRenderType.Default,
|
|
||||||
options: {
|
|
||||||
trigger: nodeTypesStore.isTriggerNode(node.type),
|
|
||||||
configuration: nodeTypesStore.isConfigNode(workflowObject.value, node, node.type),
|
|
||||||
configurable: nodeTypesStore.isConfigurableNode(
|
|
||||||
workflowObject.value,
|
|
||||||
node,
|
|
||||||
node.type,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
|
@ -214,6 +235,20 @@ export function useCanvasMapping({
|
||||||
}, {}),
|
}, {}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const additionalNodePropertiesById = computed(() => {
|
||||||
|
return nodes.value.reduce<Record<string, Partial<CanvasNode>>>((acc, node) => {
|
||||||
|
if (node.type === STICKY_NODE_TYPE) {
|
||||||
|
acc[node.id] = {
|
||||||
|
style: {
|
||||||
|
zIndex: -1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
});
|
||||||
|
|
||||||
const mappedNodes = computed<CanvasNode[]>(() => [
|
const mappedNodes = computed<CanvasNode[]>(() => [
|
||||||
...nodes.value.map<CanvasNode>((node) => {
|
...nodes.value.map<CanvasNode>((node) => {
|
||||||
const inputConnections = workflowObject.value.connectionsByDestinationNode[node.name] ?? {};
|
const inputConnections = workflowObject.value.connectionsByDestinationNode[node.name] ?? {};
|
||||||
|
@ -221,6 +256,7 @@ export function useCanvasMapping({
|
||||||
|
|
||||||
const data: CanvasNodeData = {
|
const data: CanvasNodeData = {
|
||||||
id: node.id,
|
id: node.id,
|
||||||
|
name: node.name,
|
||||||
type: node.type,
|
type: node.type,
|
||||||
typeVersion: node.typeVersion,
|
typeVersion: node.typeVersion,
|
||||||
disabled: !!node.disabled,
|
disabled: !!node.disabled,
|
||||||
|
@ -247,7 +283,7 @@ export function useCanvasMapping({
|
||||||
count: nodeExecutionRunDataById.value[node.id]?.length ?? 0,
|
count: nodeExecutionRunDataById.value[node.id]?.length ?? 0,
|
||||||
visible: !!nodeExecutionRunDataById.value[node.id],
|
visible: !!nodeExecutionRunDataById.value[node.id],
|
||||||
},
|
},
|
||||||
render: renderTypeByNodeType.value[node.type] ?? { type: 'default', options: {} },
|
render: renderTypeByNodeId.value[node.id] ?? { type: 'default', options: {} },
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -256,6 +292,7 @@ export function useCanvasMapping({
|
||||||
type: 'canvas-node',
|
type: 'canvas-node',
|
||||||
position: { x: node.position[0], y: node.position[1] },
|
position: { x: node.position[0], y: node.position[1] },
|
||||||
data,
|
data,
|
||||||
|
...additionalNodePropertiesById.value[node.id],
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -30,13 +30,14 @@ describe('useCanvasNode', () => {
|
||||||
expect(result.executionStatus.value).toBeUndefined();
|
expect(result.executionStatus.value).toBeUndefined();
|
||||||
expect(result.executionWaiting.value).toBeUndefined();
|
expect(result.executionWaiting.value).toBeUndefined();
|
||||||
expect(result.executionRunning.value).toBe(false);
|
expect(result.executionRunning.value).toBe(false);
|
||||||
expect(result.renderOptions.value).toEqual({});
|
expect(result.render.value).toEqual({ type: CanvasNodeRenderType.Default, options: {} });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return node data when node is provided', () => {
|
it('should return node data when node is provided', () => {
|
||||||
const node = {
|
const node = {
|
||||||
data: ref({
|
data: ref({
|
||||||
id: 'node1',
|
id: 'node1',
|
||||||
|
name: 'Node 1',
|
||||||
type: 'nodeType1',
|
type: 'nodeType1',
|
||||||
typeVersion: 1,
|
typeVersion: 1,
|
||||||
disabled: true,
|
disabled: true,
|
||||||
|
@ -66,6 +67,7 @@ describe('useCanvasNode', () => {
|
||||||
const result = useCanvasNode();
|
const result = useCanvasNode();
|
||||||
|
|
||||||
expect(result.label.value).toBe('Node 1');
|
expect(result.label.value).toBe('Node 1');
|
||||||
|
expect(result.name.value).toBe('Node 1');
|
||||||
expect(result.inputs.value).toEqual([{ type: 'main', index: 0 }]);
|
expect(result.inputs.value).toEqual([{ type: 'main', index: 0 }]);
|
||||||
expect(result.outputs.value).toEqual([{ type: 'main', index: 0 }]);
|
expect(result.outputs.value).toEqual([{ type: 'main', index: 0 }]);
|
||||||
expect(result.connections.value).toEqual({ input: { '0': [] }, output: {} });
|
expect(result.connections.value).toEqual({ input: { '0': [] }, output: {} });
|
||||||
|
@ -80,6 +82,6 @@ describe('useCanvasNode', () => {
|
||||||
expect(result.executionStatus.value).toBe('running');
|
expect(result.executionStatus.value).toBe('running');
|
||||||
expect(result.executionWaiting.value).toBe('waiting');
|
expect(result.executionWaiting.value).toBe('waiting');
|
||||||
expect(result.executionRunning.value).toBe(true);
|
expect(result.executionRunning.value).toBe(true);
|
||||||
expect(result.renderOptions.value).toBe(node.data.value.render.options);
|
expect(result.render.value).toBe(node.data.value.render);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,6 +14,7 @@ export function useCanvasNode() {
|
||||||
() =>
|
() =>
|
||||||
node?.data.value ?? {
|
node?.data.value ?? {
|
||||||
id: '',
|
id: '',
|
||||||
|
name: '',
|
||||||
type: '',
|
type: '',
|
||||||
typeVersion: 1,
|
typeVersion: 1,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
@ -33,8 +34,10 @@ export function useCanvasNode() {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const id = computed(() => node?.id.value ?? '');
|
||||||
const label = computed(() => node?.label.value ?? '');
|
const label = computed(() => node?.label.value ?? '');
|
||||||
|
|
||||||
|
const name = computed(() => data.value.name);
|
||||||
const inputs = computed(() => data.value.inputs);
|
const inputs = computed(() => data.value.inputs);
|
||||||
const outputs = computed(() => data.value.outputs);
|
const outputs = computed(() => data.value.outputs);
|
||||||
const connections = computed(() => data.value.connections);
|
const connections = computed(() => data.value.connections);
|
||||||
|
@ -56,10 +59,12 @@ export function useCanvasNode() {
|
||||||
const runDataCount = computed(() => data.value.runData.count);
|
const runDataCount = computed(() => data.value.runData.count);
|
||||||
const hasRunData = computed(() => data.value.runData.visible);
|
const hasRunData = computed(() => data.value.runData.visible);
|
||||||
|
|
||||||
const renderOptions = computed(() => data.value.render.options);
|
const render = computed(() => data.value.render);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
node,
|
node,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
label,
|
label,
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
|
@ -75,6 +80,6 @@ export function useCanvasNode() {
|
||||||
executionStatus,
|
executionStatus,
|
||||||
executionWaiting,
|
executionWaiting,
|
||||||
executionRunning,
|
executionRunning,
|
||||||
renderOptions,
|
render,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ import type {
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
INodeTypeNameVersion,
|
INodeTypeNameVersion,
|
||||||
ITelemetryTrackProperties,
|
ITelemetryTrackProperties,
|
||||||
|
NodeParameterValueType,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeConnectionType, NodeHelpers } from 'n8n-workflow';
|
import { NodeConnectionType, NodeHelpers } from 'n8n-workflow';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
|
@ -229,6 +230,21 @@ export function useCanvasOperations({
|
||||||
ndvStore.activeNodeName = name;
|
ndvStore.activeNodeName = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setNodeParameters(id: string, parameters: Record<string, unknown>) {
|
||||||
|
const node = workflowsStore.getNodeById(id);
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowsStore.setNodeParameters(
|
||||||
|
{
|
||||||
|
name: node.name,
|
||||||
|
value: parameters as NodeParameterValueType,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function setNodeSelected(id?: string) {
|
function setNodeSelected(id?: string) {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
uiStore.lastSelectedNode = '';
|
uiStore.lastSelectedNode = '';
|
||||||
|
@ -443,7 +459,7 @@ export function useCanvasOperations({
|
||||||
const nodeType = nodeTypesStore.getNodeType(newNodeData.type, newNodeData.typeVersion);
|
const nodeType = nodeTypesStore.getNodeType(newNodeData.type, newNodeData.typeVersion);
|
||||||
const nodeParameters = NodeHelpers.getNodeParameters(
|
const nodeParameters = NodeHelpers.getNodeParameters(
|
||||||
nodeType?.properties ?? [],
|
nodeType?.properties ?? [],
|
||||||
{},
|
node.parameters ?? {},
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
newNodeData,
|
newNodeData,
|
||||||
|
@ -883,10 +899,16 @@ export function useCanvasOperations({
|
||||||
targetNode: INodeUi,
|
targetNode: INodeUi,
|
||||||
connectionType: NodeConnectionType,
|
connectionType: NodeConnectionType,
|
||||||
): boolean {
|
): boolean {
|
||||||
|
const blocklist = [STICKY_NODE_TYPE];
|
||||||
|
|
||||||
if (sourceNode.id === targetNode.id) {
|
if (sourceNode.id === targetNode.id) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (blocklist.includes(sourceNode.type) || blocklist.includes(targetNode.type)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const targetNodeType = nodeTypesStore.getNodeType(targetNode.type, targetNode.typeVersion);
|
const targetNodeType = nodeTypesStore.getNodeType(targetNode.type, targetNode.typeVersion);
|
||||||
if (targetNodeType?.inputs?.length) {
|
if (targetNodeType?.inputs?.length) {
|
||||||
const workflowNode = editableWorkflowObject.value.getNode(targetNode.name);
|
const workflowNode = editableWorkflowObject.value.getNode(targetNode.name);
|
||||||
|
@ -958,6 +980,7 @@ export function useCanvasOperations({
|
||||||
setNodeActive,
|
setNodeActive,
|
||||||
setNodeActiveByName,
|
setNodeActiveByName,
|
||||||
setNodeSelected,
|
setNodeSelected,
|
||||||
|
setNodeParameters,
|
||||||
toggleNodeDisabled,
|
toggleNodeDisabled,
|
||||||
renameNode,
|
renameNode,
|
||||||
revertRenameNode,
|
revertRenameNode,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import '@vue-flow/core/dist/style.css';
|
||||||
import '@vue-flow/core/dist/theme-default.css';
|
import '@vue-flow/core/dist/theme-default.css';
|
||||||
import '@vue-flow/controls/dist/style.css';
|
import '@vue-flow/controls/dist/style.css';
|
||||||
import '@vue-flow/minimap/dist/style.css';
|
import '@vue-flow/minimap/dist/style.css';
|
||||||
|
import '@vue-flow/node-resizer/dist/style.css';
|
||||||
|
|
||||||
import 'vue-json-pretty/lib/styles.css';
|
import 'vue-json-pretty/lib/styles.css';
|
||||||
import '@jsplumb/browser-ui/css/jsplumbtoolkit.css';
|
import '@jsplumb/browser-ui/css/jsplumbtoolkit.css';
|
||||||
|
|
7
packages/editor-ui/src/styles/plugins/_vueflow.scss
Normal file
7
packages/editor-ui/src/styles/plugins/_vueflow.scss
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
.vue-flow__resize-control.line {
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-flow__resize-control.handle {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
@import "codemirror";
|
@import "codemirror";
|
||||||
|
@import "vueflow";
|
||||||
|
|
|
@ -36,6 +36,7 @@ export interface CanvasElementPortWithPosition extends CanvasConnectionPort {
|
||||||
|
|
||||||
export const enum CanvasNodeRenderType {
|
export const enum CanvasNodeRenderType {
|
||||||
Default = 'default',
|
Default = 'default',
|
||||||
|
StickyNote = 'n8n-nodes-base.stickyNote',
|
||||||
AddNodes = 'n8n-nodes-internal.addNodes',
|
AddNodes = 'n8n-nodes-internal.addNodes',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,8 +54,19 @@ export type CanvasNodeAddNodesRender = {
|
||||||
options: Record<string, never>;
|
options: Record<string, never>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CanvasNodeStickyNoteRender = {
|
||||||
|
type: CanvasNodeRenderType.StickyNote;
|
||||||
|
options: Partial<{
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
color: number;
|
||||||
|
content: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
export interface CanvasNodeData {
|
export interface CanvasNodeData {
|
||||||
id: INodeUi['id'];
|
id: INodeUi['id'];
|
||||||
|
name: INodeUi['name'];
|
||||||
type: INodeUi['type'];
|
type: INodeUi['type'];
|
||||||
typeVersion: INodeUi['typeVersion'];
|
typeVersion: INodeUi['typeVersion'];
|
||||||
disabled: INodeUi['disabled'];
|
disabled: INodeUi['disabled'];
|
||||||
|
@ -81,7 +93,7 @@ export interface CanvasNodeData {
|
||||||
count: number;
|
count: number;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
};
|
};
|
||||||
render: CanvasNodeDefaultRender | CanvasNodeAddNodesRender;
|
render: CanvasNodeDefaultRender | CanvasNodeStickyNoteRender | CanvasNodeAddNodesRender;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CanvasNode = Node<CanvasNodeData>;
|
export type CanvasNode = Node<CanvasNodeData>;
|
||||||
|
|
|
@ -144,6 +144,7 @@ const {
|
||||||
setNodeActive,
|
setNodeActive,
|
||||||
setNodeSelected,
|
setNodeSelected,
|
||||||
toggleNodeDisabled,
|
toggleNodeDisabled,
|
||||||
|
setNodeParameters,
|
||||||
deleteNode,
|
deleteNode,
|
||||||
revertDeleteNode,
|
revertDeleteNode,
|
||||||
addNodes,
|
addNodes,
|
||||||
|
@ -310,6 +311,7 @@ async function initializeView() {
|
||||||
nodeHelpers.updateNodesParameterIssues();
|
nodeHelpers.updateNodesParameterIssues();
|
||||||
|
|
||||||
await loadCredentials();
|
await loadCredentials();
|
||||||
|
canvasEventBus.emit('fitView');
|
||||||
|
|
||||||
uiStore.nodeViewInitialized = true;
|
uiStore.nodeViewInitialized = true;
|
||||||
|
|
||||||
|
@ -553,7 +555,6 @@ function onSetNodeSelected(id?: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onRenameNode(parameterData: IUpdateInformation) {
|
function onRenameNode(parameterData: IUpdateInformation) {
|
||||||
// The name changed. Do not forget to change the connections as well
|
|
||||||
if (parameterData.name === 'name' && parameterData.oldValue) {
|
if (parameterData.name === 'name' && parameterData.oldValue) {
|
||||||
void renameNode(parameterData.oldValue as string, parameterData.value as string);
|
void renameNode(parameterData.oldValue as string, parameterData.value as string);
|
||||||
}
|
}
|
||||||
|
@ -569,6 +570,10 @@ async function onRevertRenameNode({
|
||||||
await revertRenameNode(currentName, newName);
|
await revertRenameNode(currentName, newName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onUpdateNodeParameters(id: string, parameters: Record<string, unknown>) {
|
||||||
|
setNodeParameters(id, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Credentials
|
* Credentials
|
||||||
*/
|
*/
|
||||||
|
@ -1157,6 +1162,7 @@ onBeforeUnmount(() => {
|
||||||
@update:node:active="onSetNodeActive"
|
@update:node:active="onSetNodeActive"
|
||||||
@update:node:selected="onSetNodeSelected"
|
@update:node:selected="onSetNodeSelected"
|
||||||
@update:node:enabled="onToggleNodeDisabled"
|
@update:node:enabled="onToggleNodeDisabled"
|
||||||
|
@update:node:parameters="onUpdateNodeParameters"
|
||||||
@run:node="onRunWorkflowToNode"
|
@run:node="onRunWorkflowToNode"
|
||||||
@delete:node="onDeleteNode"
|
@delete:node="onDeleteNode"
|
||||||
@create:connection="onCreateConnection"
|
@create:connection="onCreateConnection"
|
||||||
|
|
|
@ -1147,6 +1147,9 @@ importers:
|
||||||
'@vue-flow/minimap':
|
'@vue-flow/minimap':
|
||||||
specifier: ^1.4.0
|
specifier: ^1.4.0
|
||||||
version: 1.4.0(@vue-flow/core@1.33.5(vue@3.4.21(typescript@5.5.2)))(vue@3.4.21(typescript@5.5.2))
|
version: 1.4.0(@vue-flow/core@1.33.5(vue@3.4.21(typescript@5.5.2)))(vue@3.4.21(typescript@5.5.2))
|
||||||
|
'@vue-flow/node-resizer':
|
||||||
|
specifier: ^1.4.0
|
||||||
|
version: 1.4.0(@vue-flow/core@1.33.5(vue@3.4.21(typescript@5.5.2)))(vue@3.4.21(typescript@5.5.2))
|
||||||
'@vueuse/components':
|
'@vueuse/components':
|
||||||
specifier: ^10.11.0
|
specifier: ^10.11.0
|
||||||
version: 10.11.0(vue@3.4.21(typescript@5.5.2))
|
version: 10.11.0(vue@3.4.21(typescript@5.5.2))
|
||||||
|
@ -5696,6 +5699,12 @@ packages:
|
||||||
'@vue-flow/core': ^1.23.0
|
'@vue-flow/core': ^1.23.0
|
||||||
vue: ^3.3.0
|
vue: ^3.3.0
|
||||||
|
|
||||||
|
'@vue-flow/node-resizer@1.4.0':
|
||||||
|
resolution: {integrity: sha512-S52MRcSpd6asza8Cl0bKM2sHGrbq7vBydKHDuPdoTD+cvjNX6XF4LSiPZOuzExePI6b+O6dg2EZ1378oOLGFpA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@vue-flow/core': ^1.23.0
|
||||||
|
vue: ^3.3.0
|
||||||
|
|
||||||
'@vue/compiler-core@3.4.21':
|
'@vue/compiler-core@3.4.21':
|
||||||
resolution: {integrity: sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==}
|
resolution: {integrity: sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==}
|
||||||
|
|
||||||
|
@ -19069,6 +19078,13 @@ snapshots:
|
||||||
d3-zoom: 3.0.0
|
d3-zoom: 3.0.0
|
||||||
vue: 3.4.21(typescript@5.5.2)
|
vue: 3.4.21(typescript@5.5.2)
|
||||||
|
|
||||||
|
'@vue-flow/node-resizer@1.4.0(@vue-flow/core@1.33.5(vue@3.4.21(typescript@5.5.2)))(vue@3.4.21(typescript@5.5.2))':
|
||||||
|
dependencies:
|
||||||
|
'@vue-flow/core': 1.33.5(vue@3.4.21(typescript@5.5.2))
|
||||||
|
d3-drag: 3.0.0
|
||||||
|
d3-selection: 3.0.0
|
||||||
|
vue: 3.4.21(typescript@5.5.2)
|
||||||
|
|
||||||
'@vue/compiler-core@3.4.21':
|
'@vue/compiler-core@3.4.21':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/parser': 7.24.0
|
'@babel/parser': 7.24.0
|
||||||
|
@ -21201,7 +21217,7 @@ snapshots:
|
||||||
|
|
||||||
eslint-import-resolver-node@0.3.9:
|
eslint-import-resolver-node@0.3.9:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7(supports-color@5.5.0)
|
debug: 3.2.7(supports-color@8.1.1)
|
||||||
is-core-module: 2.13.1
|
is-core-module: 2.13.1
|
||||||
resolve: 1.22.8
|
resolve: 1.22.8
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
@ -21226,7 +21242,7 @@ snapshots:
|
||||||
|
|
||||||
eslint-module-utils@2.8.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.2))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0):
|
eslint-module-utils@2.8.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.2))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7(supports-color@5.5.0)
|
debug: 3.2.7(supports-color@8.1.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.5.2)
|
'@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.5.2)
|
||||||
eslint: 8.57.0
|
eslint: 8.57.0
|
||||||
|
@ -21246,7 +21262,7 @@ snapshots:
|
||||||
array.prototype.findlastindex: 1.2.3
|
array.prototype.findlastindex: 1.2.3
|
||||||
array.prototype.flat: 1.3.2
|
array.prototype.flat: 1.3.2
|
||||||
array.prototype.flatmap: 1.3.2
|
array.prototype.flatmap: 1.3.2
|
||||||
debug: 3.2.7(supports-color@5.5.0)
|
debug: 3.2.7(supports-color@8.1.1)
|
||||||
doctrine: 2.1.0
|
doctrine: 2.1.0
|
||||||
eslint: 8.57.0
|
eslint: 8.57.0
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
|
@ -21776,7 +21792,7 @@ snapshots:
|
||||||
|
|
||||||
follow-redirects@1.15.6(debug@3.2.7):
|
follow-redirects@1.15.6(debug@3.2.7):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
debug: 3.2.7(supports-color@5.5.0)
|
debug: 3.2.7(supports-color@8.1.1)
|
||||||
|
|
||||||
follow-redirects@1.15.6(debug@4.3.4):
|
follow-redirects@1.15.6(debug@4.3.4):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
|
@ -22117,7 +22133,7 @@ snapshots:
|
||||||
array-parallel: 0.1.3
|
array-parallel: 0.1.3
|
||||||
array-series: 0.1.5
|
array-series: 0.1.5
|
||||||
cross-spawn: 4.0.2
|
cross-spawn: 4.0.2
|
||||||
debug: 3.2.7(supports-color@5.5.0)
|
debug: 3.2.7(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
@ -24882,7 +24898,7 @@ snapshots:
|
||||||
|
|
||||||
pdf-parse@1.1.1:
|
pdf-parse@1.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7(supports-color@5.5.0)
|
debug: 3.2.7(supports-color@8.1.1)
|
||||||
node-ensure: 0.0.0
|
node-ensure: 0.0.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
@ -25764,7 +25780,7 @@ snapshots:
|
||||||
|
|
||||||
rhea@1.0.24:
|
rhea@1.0.24:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7(supports-color@5.5.0)
|
debug: 3.2.7(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
@ -26138,7 +26154,7 @@ snapshots:
|
||||||
binascii: 0.0.2
|
binascii: 0.0.2
|
||||||
bn.js: 5.2.1
|
bn.js: 5.2.1
|
||||||
browser-request: 0.3.3
|
browser-request: 0.3.3
|
||||||
debug: 3.2.7(supports-color@5.5.0)
|
debug: 3.2.7(supports-color@8.1.1)
|
||||||
expand-tilde: 2.0.2
|
expand-tilde: 2.0.2
|
||||||
extend: 3.0.2
|
extend: 3.0.2
|
||||||
fast-xml-parser: 4.2.7
|
fast-xml-parser: 4.2.7
|
||||||
|
|
Loading…
Reference in a new issue