mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
refactor(editor): Convert Sticky component to Options API (no-changelog) (#10975)
This commit is contained in:
parent
06d749ffa7
commit
b7951a071c
|
@ -136,7 +136,7 @@ const htmlContent = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'markdown-click': [link: string, e: MouseEvent];
|
'markdown-click': [link: HTMLAnchorElement, e: MouseEvent];
|
||||||
'update-content': [content: string];
|
'update-content': [content: string];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -154,7 +154,7 @@ const onClick = (event: MouseEvent) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (clickedLink) {
|
if (clickedLink) {
|
||||||
emit('markdown-click', clickedLink?.href, event);
|
emit('markdown-click', clickedLink, event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ const emit = defineEmits<{
|
||||||
resize: [values: ResizeData];
|
resize: [values: ResizeData];
|
||||||
resizestart: [];
|
resizestart: [];
|
||||||
resizeend: [];
|
resizeend: [];
|
||||||
|
'markdown-click': [link: HTMLAnchorElement, e: MouseEvent];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const attrs = useAttrs();
|
const attrs = useAttrs();
|
||||||
|
@ -42,6 +43,10 @@ const onResizeEnd = () => {
|
||||||
isResizing.value = false;
|
isResizing.value = false;
|
||||||
emit('resizeend');
|
emit('resizeend');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onMarkdownClick = (link: HTMLAnchorElement, event: MouseEvent) => {
|
||||||
|
emit('markdown-click', link, event);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -57,6 +62,6 @@ const onResizeEnd = () => {
|
||||||
@resize="onResize"
|
@resize="onResize"
|
||||||
@resizestart="onResizeStart"
|
@resizestart="onResizeStart"
|
||||||
>
|
>
|
||||||
<N8nSticky v-bind="stickyBindings" />
|
<N8nSticky v-bind="stickyBindings" @markdown-click="onMarkdownClick" />
|
||||||
</N8nResizeWrapper>
|
</N8nResizeWrapper>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -13,7 +13,7 @@ const props = withDefaults(defineProps<StickyProps>(), defaultStickyProps);
|
||||||
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: HTMLAnchorElement, e: MouseEvent];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -63,7 +63,7 @@ const onUpdateModelValue = (value: string) => {
|
||||||
emit('update:modelValue', value);
|
emit('update:modelValue', value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMarkdownClick = (link: string, event: Event) => {
|
const onMarkdownClick = (link: HTMLAnchorElement, event: MouseEvent) => {
|
||||||
emit('markdown-click', link, event);
|
emit('markdown-click', link, event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,12 @@
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent, ref } from 'vue';
|
import { ref, computed, onMounted, nextTick } from 'vue';
|
||||||
import type { PropType, StyleValue } from 'vue';
|
import type { StyleValue } from 'vue';
|
||||||
import { mapStores } from 'pinia';
|
|
||||||
|
|
||||||
import { onClickOutside } from '@vueuse/core';
|
import { onClickOutside } from '@vueuse/core';
|
||||||
|
import type { Workflow } from 'n8n-workflow';
|
||||||
|
|
||||||
import { isNumber, isString } from '@/utils/typeGuards';
|
import { isNumber, isString } from '@/utils/typeGuards';
|
||||||
import type {
|
import type { INodeUi, XYPosition } from '@/Interface';
|
||||||
INodeUi,
|
|
||||||
INodeUpdatePropertiesInformation,
|
|
||||||
IUpdateInformation,
|
|
||||||
XYPosition,
|
|
||||||
} from '@/Interface';
|
|
||||||
|
|
||||||
import type { INodeTypeDescription, Workflow } from 'n8n-workflow';
|
|
||||||
import { QUICKSTART_NOTE_NAME } from '@/constants';
|
import { QUICKSTART_NOTE_NAME } from '@/constants';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
|
@ -25,313 +18,280 @@ import { GRID_SIZE } from '@/utils/nodeViewUtils';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { assert } from '@/utils/assert';
|
import { assert } from '@/utils/assert';
|
||||||
import type { BrowserJsPlumbInstance } from '@jsplumb/browser-ui';
|
import type { BrowserJsPlumbInstance } from '@jsplumb/browser-ui';
|
||||||
import { useCanvasStore } from '@/stores/canvas.store';
|
|
||||||
import { useHistoryStore } from '@/stores/history.store';
|
|
||||||
import { useNodeBase } from '@/composables/useNodeBase';
|
import { useNodeBase } from '@/composables/useNodeBase';
|
||||||
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
|
|
||||||
export default defineComponent({
|
const props = withDefaults(
|
||||||
name: 'Sticky',
|
defineProps<{
|
||||||
props: {
|
nodeViewScale?: number;
|
||||||
nodeViewScale: {
|
gridSize?: number;
|
||||||
type: Number,
|
name: string;
|
||||||
default: 1,
|
instance: BrowserJsPlumbInstance;
|
||||||
},
|
isReadOnly?: boolean;
|
||||||
gridSize: {
|
isActive?: boolean;
|
||||||
type: Number,
|
hideActions?: boolean;
|
||||||
default: GRID_SIZE,
|
disableSelecting?: boolean;
|
||||||
},
|
showCustomTooltip?: boolean;
|
||||||
name: {
|
workflow: Workflow;
|
||||||
type: String,
|
}>(),
|
||||||
required: true,
|
{
|
||||||
},
|
nodeViewScale: 1,
|
||||||
instance: {
|
gridSize: GRID_SIZE,
|
||||||
type: Object as PropType<BrowserJsPlumbInstance>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
isReadOnly: {
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
isActive: {
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
hideActions: {
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
disableSelecting: {
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
showCustomTooltip: {
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
workflow: {
|
|
||||||
type: Object as PropType<Workflow>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
emits: { removeNode: null, nodeSelected: null },
|
);
|
||||||
setup(props, { emit }) {
|
|
||||||
const deviceSupport = useDeviceSupport();
|
|
||||||
const toast = useToast();
|
|
||||||
const forceActions = ref(false);
|
|
||||||
const isColorPopoverVisible = ref(false);
|
|
||||||
|
|
||||||
const stickOptions = ref<HTMLElement>();
|
defineOptions({ name: 'Sticky' });
|
||||||
|
|
||||||
const setForceActions = (value: boolean) => {
|
const emit = defineEmits<{
|
||||||
forceActions.value = value;
|
removeNode: [string];
|
||||||
};
|
nodeSelected: [string, boolean, boolean];
|
||||||
const setColorPopoverVisible = (value: boolean) => {
|
}>();
|
||||||
isColorPopoverVisible.value = value;
|
|
||||||
};
|
|
||||||
|
|
||||||
const contextMenu = useContextMenu((action) => {
|
const deviceSupport = useDeviceSupport();
|
||||||
if (action === 'change_color') {
|
const telemetry = useTelemetry();
|
||||||
setForceActions(true);
|
const toast = useToast();
|
||||||
setColorPopoverVisible(true);
|
const ndvStore = useNDVStore();
|
||||||
}
|
const nodeTypesStore = useNodeTypesStore();
|
||||||
});
|
const uiStore = useUIStore();
|
||||||
|
const workflowsStore = useWorkflowsStore();
|
||||||
|
|
||||||
const nodeBase = useNodeBase({
|
const isResizing = ref<boolean>(false);
|
||||||
name: props.name,
|
const isTouchActive = ref<boolean>(false);
|
||||||
instance: props.instance,
|
const forceActions = ref(false);
|
||||||
workflowObject: props.workflow,
|
const isColorPopoverVisible = ref(false);
|
||||||
isReadOnly: props.isReadOnly,
|
const stickOptions = ref<HTMLElement>();
|
||||||
emit: emit as (event: string, ...args: unknown[]) => void,
|
|
||||||
});
|
|
||||||
|
|
||||||
onClickOutside(stickOptions, () => setColorPopoverVisible(false));
|
const setForceActions = (value: boolean) => {
|
||||||
|
forceActions.value = value;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
const setColorPopoverVisible = (value: boolean) => {
|
||||||
deviceSupport,
|
isColorPopoverVisible.value = value;
|
||||||
toast,
|
};
|
||||||
contextMenu,
|
|
||||||
forceActions,
|
|
||||||
...nodeBase,
|
|
||||||
setForceActions,
|
|
||||||
isColorPopoverVisible,
|
|
||||||
setColorPopoverVisible,
|
|
||||||
stickOptions,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isResizing: false,
|
|
||||||
isTouchActive: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapStores(
|
|
||||||
useNodeTypesStore,
|
|
||||||
useUIStore,
|
|
||||||
useNDVStore,
|
|
||||||
useCanvasStore,
|
|
||||||
useWorkflowsStore,
|
|
||||||
useHistoryStore,
|
|
||||||
),
|
|
||||||
data(): INodeUi | null {
|
|
||||||
return this.workflowsStore.getNodeByName(this.name);
|
|
||||||
},
|
|
||||||
nodeId(): string {
|
|
||||||
return this.data?.id || '';
|
|
||||||
},
|
|
||||||
defaultText(): string {
|
|
||||||
if (!this.nodeType) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
const properties = this.nodeType.properties;
|
|
||||||
const content = properties.find((property) => property.name === 'content');
|
|
||||||
|
|
||||||
return content && isString(content.default) ? content.default : '';
|
const contextMenu = useContextMenu((action) => {
|
||||||
},
|
if (action === 'change_color') {
|
||||||
isSelected(): boolean {
|
setForceActions(true);
|
||||||
return (
|
setColorPopoverVisible(true);
|
||||||
this.uiStore.getSelectedNodes.find((node: INodeUi) => node.name === this.data?.name) !==
|
}
|
||||||
undefined
|
|
||||||
);
|
|
||||||
},
|
|
||||||
nodeType(): INodeTypeDescription | null {
|
|
||||||
return this.data && this.nodeTypesStore.getNodeType(this.data.type, this.data.typeVersion);
|
|
||||||
},
|
|
||||||
node(): INodeUi | null {
|
|
||||||
// same as this.data but reactive..
|
|
||||||
return this.workflowsStore.getNodeByName(this.name);
|
|
||||||
},
|
|
||||||
position(): XYPosition {
|
|
||||||
if (this.node) {
|
|
||||||
return this.node.position;
|
|
||||||
} else {
|
|
||||||
return [0, 0];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
height(): number {
|
|
||||||
return this.node && isNumber(this.node.parameters.height) ? this.node.parameters.height : 0;
|
|
||||||
},
|
|
||||||
width(): number {
|
|
||||||
return this.node && isNumber(this.node.parameters.width) ? this.node.parameters.width : 0;
|
|
||||||
},
|
|
||||||
stickySize(): StyleValue {
|
|
||||||
const returnStyles: {
|
|
||||||
[key: string]: string | number;
|
|
||||||
} = {
|
|
||||||
height: this.height + 'px',
|
|
||||||
width: this.width + 'px',
|
|
||||||
};
|
|
||||||
|
|
||||||
return returnStyles;
|
|
||||||
},
|
|
||||||
stickyPosition(): StyleValue {
|
|
||||||
const returnStyles: {
|
|
||||||
[key: string]: string | number;
|
|
||||||
} = {
|
|
||||||
left: this.position[0] + 'px',
|
|
||||||
top: this.position[1] + 'px',
|
|
||||||
zIndex: this.isActive ? 9999999 : -1 * Math.floor((this.height * this.width) / 1000),
|
|
||||||
};
|
|
||||||
|
|
||||||
return returnStyles;
|
|
||||||
},
|
|
||||||
showActions(): boolean {
|
|
||||||
return (
|
|
||||||
!(this.hideActions || this.isReadOnly || this.workflowRunning || this.isResizing) ||
|
|
||||||
this.forceActions
|
|
||||||
);
|
|
||||||
},
|
|
||||||
workflowRunning(): boolean {
|
|
||||||
return this.uiStore.isActionActive['workflowRunning'];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
// Initialize the node
|
|
||||||
if (this.data !== null) {
|
|
||||||
try {
|
|
||||||
this.addNode(this.data);
|
|
||||||
} catch (error) {
|
|
||||||
// This breaks when new nodes are loaded into store but workflow tab is not currently active
|
|
||||||
// Shouldn't affect anything
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onShowPopover() {
|
|
||||||
this.setForceActions(true);
|
|
||||||
},
|
|
||||||
onHidePopover() {
|
|
||||||
this.setForceActions(false);
|
|
||||||
},
|
|
||||||
async deleteNode() {
|
|
||||||
assert(this.data);
|
|
||||||
// Wait a tick else vue causes problems because the data is gone
|
|
||||||
await this.$nextTick();
|
|
||||||
this.$emit('removeNode', this.data.name);
|
|
||||||
},
|
|
||||||
changeColor(index: number) {
|
|
||||||
this.workflowsStore.updateNodeProperties({
|
|
||||||
name: this.name,
|
|
||||||
properties: {
|
|
||||||
parameters: {
|
|
||||||
...this.node?.parameters,
|
|
||||||
color: index,
|
|
||||||
},
|
|
||||||
position: this.node?.position ?? [0, 0],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onEdit(edit: boolean) {
|
|
||||||
if (edit && !this.isActive && this.node) {
|
|
||||||
this.ndvStore.activeNodeName = this.node.name;
|
|
||||||
} else if (this.isActive && !edit) {
|
|
||||||
this.ndvStore.activeNodeName = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onMarkdownClick(link: HTMLAnchorElement) {
|
|
||||||
if (link) {
|
|
||||||
const isOnboardingNote = this.name === QUICKSTART_NOTE_NAME;
|
|
||||||
const isWelcomeVideo = link.querySelector('img[alt="n8n quickstart video"]');
|
|
||||||
const type =
|
|
||||||
isOnboardingNote && isWelcomeVideo
|
|
||||||
? 'welcome_video'
|
|
||||||
: isOnboardingNote && link.getAttribute('href') === '/templates'
|
|
||||||
? 'templates'
|
|
||||||
: 'other';
|
|
||||||
|
|
||||||
this.$telemetry.track('User clicked note link', { type });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onInputChange(content: string) {
|
|
||||||
if (!this.node) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.node.parameters.content = content;
|
|
||||||
this.setParameters({ content });
|
|
||||||
},
|
|
||||||
onResizeStart() {
|
|
||||||
this.isResizing = true;
|
|
||||||
if (!this.isSelected && this.node) {
|
|
||||||
this.$emit('nodeSelected', this.node.name, false, true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onResize({ height, width, dX, dY }: { width: number; height: number; dX: number; dY: number }) {
|
|
||||||
if (!this.node) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (dX !== 0 || dY !== 0) {
|
|
||||||
this.setPosition([this.node.position[0] + (dX || 0), this.node.position[1] + (dY || 0)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setParameters({ height, width });
|
|
||||||
},
|
|
||||||
onResizeEnd() {
|
|
||||||
this.isResizing = false;
|
|
||||||
},
|
|
||||||
setParameters(params: { content?: string; height?: number; width?: number; color?: string }) {
|
|
||||||
if (this.node) {
|
|
||||||
const nodeParameters = {
|
|
||||||
content: isString(params.content) ? params.content : this.node.parameters.content,
|
|
||||||
height: isNumber(params.height) ? params.height : this.node.parameters.height,
|
|
||||||
width: isNumber(params.width) ? params.width : this.node.parameters.width,
|
|
||||||
color: isString(params.color) ? params.color : this.node.parameters.color,
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateInformation: IUpdateInformation = {
|
|
||||||
key: this.node.id,
|
|
||||||
name: this.node.name,
|
|
||||||
value: nodeParameters,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.workflowsStore.setNodeParameters(updateInformation);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setPosition(position: XYPosition) {
|
|
||||||
if (!this.node) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateInformation: INodeUpdatePropertiesInformation = {
|
|
||||||
name: this.node.name,
|
|
||||||
properties: {
|
|
||||||
position,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.workflowsStore.updateNodeProperties(updateInformation);
|
|
||||||
},
|
|
||||||
touchStart() {
|
|
||||||
if (this.deviceSupport.isTouchDevice && !this.deviceSupport.isMacOs && !this.isTouchActive) {
|
|
||||||
this.isTouchActive = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
this.isTouchActive = false;
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onContextMenu(e: MouseEvent): void {
|
|
||||||
if (this.node && !this.isActive) {
|
|
||||||
this.contextMenu.open(e, { source: 'node-right-click', nodeId: this.node.id });
|
|
||||||
} else {
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const nodeBase = useNodeBase({
|
||||||
|
name: props.name,
|
||||||
|
instance: props.instance,
|
||||||
|
workflowObject: props.workflow,
|
||||||
|
isReadOnly: props.isReadOnly,
|
||||||
|
emit: emit as (event: string, ...args: unknown[]) => void,
|
||||||
|
});
|
||||||
|
|
||||||
|
onClickOutside(stickOptions, () => setColorPopoverVisible(false));
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
deviceSupport,
|
||||||
|
toast,
|
||||||
|
contextMenu,
|
||||||
|
forceActions,
|
||||||
|
...nodeBase,
|
||||||
|
setForceActions,
|
||||||
|
isColorPopoverVisible,
|
||||||
|
setColorPopoverVisible,
|
||||||
|
stickOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = computed(() => workflowsStore.getNodeByName(props.name));
|
||||||
|
// TODO: remove either node or data
|
||||||
|
const node = computed(() => workflowsStore.getNodeByName(props.name));
|
||||||
|
const nodeId = computed(() => data.value?.id);
|
||||||
|
const nodeType = computed(() => {
|
||||||
|
return data.value && nodeTypesStore.getNodeType(data.value.type, data.value.typeVersion);
|
||||||
|
});
|
||||||
|
const defaultText = computed(() => {
|
||||||
|
if (!nodeType.value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const properties = nodeType.value.properties;
|
||||||
|
const content = properties.find((property) => property.name === 'content');
|
||||||
|
return content && isString(content.default) ? content.default : '';
|
||||||
|
});
|
||||||
|
const isSelected = computed(
|
||||||
|
() =>
|
||||||
|
uiStore.getSelectedNodes.find(({ name }: INodeUi) => name === data.value?.name) !== undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const position = computed<XYPosition>(() => (node.value ? node.value.position : [0, 0]));
|
||||||
|
|
||||||
|
const height = computed(() =>
|
||||||
|
node.value && isNumber(node.value.parameters.height) ? node.value.parameters.height : 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const width = computed(() =>
|
||||||
|
node.value && isNumber(node.value.parameters.width) ? node.value.parameters.width : 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const stickySize = computed<StyleValue>(() => ({
|
||||||
|
height: height.value + 'px',
|
||||||
|
width: width.value + 'px',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const stickyPosition = computed<StyleValue>(() => ({
|
||||||
|
left: position.value[0] + 'px',
|
||||||
|
top: position.value[1] + 'px',
|
||||||
|
zIndex: props.isActive ? 9999999 : -1 * Math.floor((height.value * width.value) / 1000),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const workflowRunning = computed(() => uiStore.isActionActive.workflowRunning);
|
||||||
|
|
||||||
|
const showActions = computed(
|
||||||
|
() =>
|
||||||
|
!(props.hideActions || props.isReadOnly || workflowRunning.value || isResizing.value) ||
|
||||||
|
forceActions.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// Initialize the node
|
||||||
|
if (data.value !== null) {
|
||||||
|
try {
|
||||||
|
nodeBase.addNode(data.value);
|
||||||
|
} catch (error) {
|
||||||
|
// This breaks when new nodes are loaded into store but workflow tab is not currently active
|
||||||
|
// Shouldn't affect anything
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onShowPopover = () => setForceActions(true);
|
||||||
|
const onHidePopover = () => setForceActions(false);
|
||||||
|
const deleteNode = async () => {
|
||||||
|
assert(data.value);
|
||||||
|
// Wait a tick else vue causes problems because the data is gone
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
emit('removeNode', data.value.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeColor = (index: number) => {
|
||||||
|
workflowsStore.updateNodeProperties({
|
||||||
|
name: props.name,
|
||||||
|
properties: {
|
||||||
|
parameters: {
|
||||||
|
...node.value?.parameters,
|
||||||
|
color: index,
|
||||||
|
},
|
||||||
|
position: node.value?.position ?? [0, 0],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEdit = (edit: boolean) => {
|
||||||
|
if (edit && !props.isActive && node.value) {
|
||||||
|
ndvStore.activeNodeName = node.value.name;
|
||||||
|
} else if (props.isActive && !edit) {
|
||||||
|
ndvStore.activeNodeName = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMarkdownClick = (link: HTMLAnchorElement) => {
|
||||||
|
if (link) {
|
||||||
|
const isOnboardingNote = props.name === QUICKSTART_NOTE_NAME;
|
||||||
|
const isWelcomeVideo = link.querySelector('img[alt="n8n quickstart video"]');
|
||||||
|
const type =
|
||||||
|
isOnboardingNote && isWelcomeVideo
|
||||||
|
? 'welcome_video'
|
||||||
|
: isOnboardingNote && link.getAttribute('href') === '/templates'
|
||||||
|
? 'templates'
|
||||||
|
: 'other';
|
||||||
|
|
||||||
|
telemetry.track('User clicked note link', { type });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setParameters = (params: {
|
||||||
|
content?: string;
|
||||||
|
height?: number;
|
||||||
|
width?: number;
|
||||||
|
color?: string;
|
||||||
|
}) => {
|
||||||
|
if (node.value) {
|
||||||
|
const nodeParameters = {
|
||||||
|
content: isString(params.content) ? params.content : node.value.parameters.content,
|
||||||
|
height: isNumber(params.height) ? params.height : node.value.parameters.height,
|
||||||
|
width: isNumber(params.width) ? params.width : node.value.parameters.width,
|
||||||
|
color: isString(params.color) ? params.color : node.value.parameters.color,
|
||||||
|
};
|
||||||
|
|
||||||
|
workflowsStore.setNodeParameters({
|
||||||
|
key: node.value.id,
|
||||||
|
name: node.value.name,
|
||||||
|
value: nodeParameters,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onInputChange = (content: string) => {
|
||||||
|
if (!node.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
node.value.parameters.content = content;
|
||||||
|
setParameters({ content });
|
||||||
|
};
|
||||||
|
|
||||||
|
const setPosition = (newPosition: XYPosition) => {
|
||||||
|
if (!node.value) return;
|
||||||
|
|
||||||
|
workflowsStore.updateNodeProperties({
|
||||||
|
name: node.value.name,
|
||||||
|
properties: { position: newPosition },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onResizeStart = () => {
|
||||||
|
isResizing.value = true;
|
||||||
|
if (!isSelected.value && node.value) {
|
||||||
|
emit('nodeSelected', node.value.name, false, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onResize = ({
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
dX,
|
||||||
|
dY,
|
||||||
|
}: {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
dX: number;
|
||||||
|
dY: number;
|
||||||
|
}) => {
|
||||||
|
if (!node.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (dX !== 0 || dY !== 0) {
|
||||||
|
setPosition([node.value.position[0] + (dX || 0), node.value.position[1] + (dY || 0)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setParameters({ height, width });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onResizeEnd = () => {
|
||||||
|
isResizing.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const touchStart = () => {
|
||||||
|
if (deviceSupport.isTouchDevice && !deviceSupport.isMacOs && !isTouchActive.value) {
|
||||||
|
isTouchActive.value = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
isTouchActive.value = false;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onContextMenu = (e: MouseEvent): void => {
|
||||||
|
if (node.value && !props.isActive) {
|
||||||
|
contextMenu.open(e, { source: 'node-right-click', nodeId: node.value.id });
|
||||||
|
} else {
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -355,9 +315,9 @@ export default defineComponent({
|
||||||
<div v-show="isSelected" class="select-sticky-background" />
|
<div v-show="isSelected" class="select-sticky-background" />
|
||||||
<div
|
<div
|
||||||
v-touch:start="touchStart"
|
v-touch:start="touchStart"
|
||||||
v-touch:end="touchEnd"
|
v-touch:end="nodeBase.touchEnd"
|
||||||
class="sticky-box"
|
class="sticky-box"
|
||||||
@click.left="mouseLeftClick"
|
@click.left="nodeBase.mouseLeftClick"
|
||||||
@contextmenu="onContextMenu"
|
@contextmenu="onContextMenu"
|
||||||
>
|
>
|
||||||
<N8nResizeableSticky
|
<N8nResizeableSticky
|
||||||
|
|
Loading…
Reference in a new issue