mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-09 22:24:05 -08:00
refactor(editor): Fix types issues in src/components/Node/*
(no-changelog) (#9444)
Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
This commit is contained in:
parent
aac19d3285
commit
69bb745cac
|
@ -16,7 +16,11 @@ export const retry = async (
|
|||
try {
|
||||
resolve(assertion());
|
||||
} catch (err) {
|
||||
Date.now() - startTime > timeout ? reject(err) : tryAgain();
|
||||
if (Date.now() - startTime > timeout) {
|
||||
reject(err);
|
||||
} else {
|
||||
tryAgain();
|
||||
}
|
||||
}
|
||||
}, interval);
|
||||
};
|
||||
|
@ -62,7 +66,10 @@ export const SETTINGS_STORE_DEFAULT_STATE: ISettingsState = {
|
|||
saveDataErrorExecution: 'all',
|
||||
saveDataSuccessExecution: 'all',
|
||||
saveManualExecutions: false,
|
||||
binaryDataMode: 'default',
|
||||
initialized: false,
|
||||
mfa: {
|
||||
enabled: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const getDropdownItems = async (dropdownTriggerParent: HTMLElement) => {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="data"
|
||||
:id="nodeId"
|
||||
:ref="data.name"
|
||||
:class="nodeWrapperClass"
|
||||
|
@ -262,6 +263,16 @@ export default defineComponent({
|
|||
callDebounced,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isTouchActive: false,
|
||||
nodeSubtitle: '',
|
||||
showTriggerNodeTooltip: false,
|
||||
pinDataDiscoveryTooltipVisible: false,
|
||||
dragging: false,
|
||||
unwatchWorkflowDataItems: () => {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useNodeTypesStore, useNDVStore, useUIStore, useWorkflowsStore),
|
||||
showPinnedDataInfo(): boolean {
|
||||
|
@ -325,6 +336,7 @@ export default defineComponent({
|
|||
return !!this.nodeType?.polling;
|
||||
},
|
||||
isExecuting(): boolean {
|
||||
if (!this.data) return false;
|
||||
return this.workflowsStore.isNodeExecuting(this.data.name);
|
||||
},
|
||||
isSingleActiveTriggerNode(): boolean {
|
||||
|
@ -336,12 +348,15 @@ export default defineComponent({
|
|||
return nodes.length === 1;
|
||||
},
|
||||
isManualTypeNode(): boolean {
|
||||
return this.data.type === MANUAL_TRIGGER_NODE_TYPE;
|
||||
return this.data?.type === MANUAL_TRIGGER_NODE_TYPE;
|
||||
},
|
||||
isConfigNode(): boolean {
|
||||
return this.nodeTypesStore.isConfigNode(this.workflow, this.data, this.data?.type ?? '');
|
||||
if (!this.data) return false;
|
||||
return this.nodeTypesStore.isConfigNode(this.workflow, this.data, this.data.type ?? '');
|
||||
},
|
||||
isConfigurableNode(): boolean {
|
||||
if (!this.data) return false;
|
||||
|
||||
return this.nodeTypesStore.isConfigurableNode(
|
||||
this.workflow,
|
||||
this.data,
|
||||
|
@ -365,10 +380,10 @@ export default defineComponent({
|
|||
return this.workflowsStore.nodesByName[this.name] as INodeUi | undefined;
|
||||
},
|
||||
sameTypeNodes(): INodeUi[] {
|
||||
return this.workflowsStore.allNodes.filter((node: INodeUi) => node.type === this.data.type);
|
||||
return this.workflowsStore.allNodes.filter((node: INodeUi) => node.type === this.data?.type);
|
||||
},
|
||||
nodeWrapperClass(): object {
|
||||
const classes = {
|
||||
nodeWrapperClass() {
|
||||
const classes: Record<string, boolean> = {
|
||||
'node-wrapper': true,
|
||||
'node-wrapper--trigger': this.isTriggerNode,
|
||||
'node-wrapper--configurable': this.isConfigurableNode,
|
||||
|
@ -389,7 +404,7 @@ export default defineComponent({
|
|||
|
||||
return classes;
|
||||
},
|
||||
nodeWrapperStyles(): object {
|
||||
nodeWrapperStyles() {
|
||||
const styles: {
|
||||
[key: string]: string | number;
|
||||
} = {
|
||||
|
@ -435,7 +450,7 @@ export default defineComponent({
|
|||
nodeClass(): object {
|
||||
return {
|
||||
'node-box': true,
|
||||
disabled: this.data.disabled,
|
||||
disabled: this.data?.disabled,
|
||||
executing: this.isExecuting,
|
||||
};
|
||||
},
|
||||
|
@ -466,7 +481,7 @@ export default defineComponent({
|
|||
return issues;
|
||||
},
|
||||
nodeDisabledTitle(): string {
|
||||
return this.data.disabled
|
||||
return this.data?.disabled
|
||||
? this.$locale.baseText('node.enable')
|
||||
: this.$locale.baseText('node.disable');
|
||||
},
|
||||
|
@ -476,21 +491,21 @@ export default defineComponent({
|
|||
showDisabledLinethrough(): boolean {
|
||||
return (
|
||||
!this.isConfigurableNode &&
|
||||
!!(this.data.disabled && this.inputs.length === 1 && this.outputs.length === 1)
|
||||
!!(this.data?.disabled && this.inputs.length === 1 && this.outputs.length === 1)
|
||||
);
|
||||
},
|
||||
shortNodeType(): string {
|
||||
return this.$locale.shortNodeType(this.data.type);
|
||||
return this.$locale.shortNodeType(this.data?.type ?? '');
|
||||
},
|
||||
nodeTitle(): string {
|
||||
if (this.data.name === 'Start') {
|
||||
if (this.data?.name === 'Start') {
|
||||
return this.$locale.headerText({
|
||||
key: 'headers.start.displayName',
|
||||
fallback: 'Start',
|
||||
});
|
||||
}
|
||||
|
||||
return this.data.name;
|
||||
return this.data?.name ?? '';
|
||||
},
|
||||
waiting(): string | undefined {
|
||||
const workflowExecution = this.workflowsStore.getWorkflowExecution as ExecutionSummary;
|
||||
|
@ -518,7 +533,7 @@ export default defineComponent({
|
|||
workflowRunning(): boolean {
|
||||
return this.uiStore.isActionActive('workflowRunning');
|
||||
},
|
||||
nodeStyle(): object {
|
||||
nodeStyle() {
|
||||
const returnStyles: {
|
||||
[key: string]: string;
|
||||
} = {};
|
||||
|
@ -529,7 +544,7 @@ export default defineComponent({
|
|||
borderColor = '--color-foreground-dark';
|
||||
}
|
||||
|
||||
if (this.data.disabled) {
|
||||
if (this.data?.disabled) {
|
||||
borderColor = '--color-foreground-base';
|
||||
} else if (!this.isExecuting) {
|
||||
if (this.hasIssues && !this.hideNodeIssues) {
|
||||
|
@ -559,7 +574,7 @@ export default defineComponent({
|
|||
},
|
||||
isSelected(): boolean {
|
||||
return (
|
||||
this.uiStore.getSelectedNodes.find((node: INodeUi) => node.name === this.data.name) !==
|
||||
this.uiStore.getSelectedNodes.find((node: INodeUi) => node.name === this.data?.name) !==
|
||||
undefined
|
||||
);
|
||||
},
|
||||
|
@ -668,23 +683,13 @@ export default defineComponent({
|
|||
}, 0);
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isTouchActive: false,
|
||||
nodeSubtitle: '',
|
||||
showTriggerNodeTooltip: false,
|
||||
pinDataDiscoveryTooltipVisible: false,
|
||||
dragging: false,
|
||||
unwatchWorkflowDataItems: () => {},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
showPinDataDiscoveryTooltip(dataItemsCount: number): void {
|
||||
if (
|
||||
!this.isTriggerNode ||
|
||||
this.isManualTypeNode ||
|
||||
this.isScheduledGroup ||
|
||||
this.uiStore.isModalActive ||
|
||||
this.uiStore.isAnyModalOpen ||
|
||||
dataItemsCount === 0
|
||||
)
|
||||
return;
|
||||
|
@ -695,13 +700,14 @@ export default defineComponent({
|
|||
this.unwatchWorkflowDataItems();
|
||||
},
|
||||
setSubtitle() {
|
||||
if (!this.data || !this.nodeType) return;
|
||||
// why is this not a computed property? because it's a very expensive operation
|
||||
// it requires expressions to resolve each subtitle...
|
||||
// and ends up bogging down the UI with big workflows, for example when pasting a workflow or even opening a node...
|
||||
// so we only update it when necessary (when node is mounted and when it's opened and closed (isActive))
|
||||
try {
|
||||
const nodeSubtitle =
|
||||
this.nodeHelpers.getNodeSubtitle(this.data, this.nodeType, this.workflow) || '';
|
||||
this.nodeHelpers.getNodeSubtitle(this.data, this.nodeType, this.workflow) ?? '';
|
||||
|
||||
this.nodeSubtitle = nodeSubtitle.includes(CUSTOM_API_CALL_KEY) ? '' : nodeSubtitle;
|
||||
} catch (e) {
|
||||
|
@ -727,9 +733,9 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
executeNode() {
|
||||
this.$emit('runWorkflow', this.data.name, 'Node.executeNode');
|
||||
this.$emit('runWorkflow', this.data?.name, 'Node.executeNode');
|
||||
this.$telemetry.track('User clicked node hover button', {
|
||||
node_type: this.data.type,
|
||||
node_type: this.data?.type,
|
||||
button_name: 'execute',
|
||||
workflow_id: this.workflowsStore.workflowId,
|
||||
});
|
||||
|
@ -737,18 +743,18 @@ export default defineComponent({
|
|||
|
||||
deleteNode() {
|
||||
this.$telemetry.track('User clicked node hover button', {
|
||||
node_type: this.data.type,
|
||||
node_type: this.data?.type,
|
||||
button_name: 'delete',
|
||||
workflow_id: this.workflowsStore.workflowId,
|
||||
});
|
||||
|
||||
this.$emit('removeNode', this.data.name);
|
||||
this.$emit('removeNode', this.data?.name);
|
||||
},
|
||||
|
||||
toggleDisableNode(event: MouseEvent) {
|
||||
(event.currentTarget as HTMLButtonElement).blur();
|
||||
this.$telemetry.track('User clicked node hover button', {
|
||||
node_type: this.data.type,
|
||||
node_type: this.data?.type,
|
||||
button_name: 'disable',
|
||||
workflow_id: this.workflowsStore.workflowId,
|
||||
});
|
||||
|
@ -759,7 +765,8 @@ export default defineComponent({
|
|||
void this.callDebounced(this.onClickDebounced, { debounceTime: 50, trailing: true }, event);
|
||||
},
|
||||
|
||||
onClickDebounced(event: MouseEvent) {
|
||||
onClickDebounced(...args: unknown[]) {
|
||||
const event = args[0] as MouseEvent;
|
||||
const isDoubleClick = event.detail >= 2;
|
||||
if (isDoubleClick) {
|
||||
this.setNodeActive();
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, computed, toRefs, getCurrentInstance } from 'vue';
|
||||
import { reactive, computed, toRefs } from 'vue';
|
||||
import type { ActionTypeDescription, SimplifiedNodeType } from '@/Interface';
|
||||
import { WEBHOOK_NODE_TYPE, DRAG_EVENT_DATA_KEY } from '@/constants';
|
||||
|
||||
|
@ -30,6 +30,7 @@ import NodeIcon from '@/components/NodeIcon.vue';
|
|||
|
||||
import { useViewStacks } from '../composables/useViewStacks';
|
||||
import { useActions } from '../composables/useActions';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
|
||||
export interface Props {
|
||||
nodeType: SimplifiedNodeType;
|
||||
|
@ -37,9 +38,7 @@ export interface Props {
|
|||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const instance = getCurrentInstance();
|
||||
const telemetry = instance?.proxy.$telemetry;
|
||||
const telemetry = useTelemetry();
|
||||
|
||||
const { getActionData, getAddedNodesAndConnections, setAddedNodeActionParameters } = useActions();
|
||||
const { activeViewStack } = useViewStacks();
|
||||
|
@ -104,7 +103,7 @@ function onDragOver(event: DragEvent): void {
|
|||
state.draggablePosition = { x, y };
|
||||
}
|
||||
|
||||
function onDragEnd(event: DragEvent): void {
|
||||
function onDragEnd(): void {
|
||||
if (state.storeWatcher) state.storeWatcher();
|
||||
document.body.removeEventListener('dragend', onDragEnd);
|
||||
document.body.removeEventListener('dragover', onDragOver);
|
||||
|
|
|
@ -67,6 +67,7 @@ export interface Props {
|
|||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
active: false,
|
||||
subcategory: undefined,
|
||||
});
|
||||
|
||||
const i18n = useI18n();
|
||||
|
@ -105,8 +106,7 @@ const hasActions = computed(() => {
|
|||
});
|
||||
|
||||
const nodeActions = computed(() => {
|
||||
const nodeActions = actions[props.nodeType.name] || [];
|
||||
return nodeActions;
|
||||
return actions[props.nodeType.name] || [];
|
||||
});
|
||||
|
||||
const shortNodeType = computed<string>(() => i18n.shortNodeType(props.nodeType.name) || '');
|
||||
|
@ -119,11 +119,11 @@ const draggableStyle = computed<{ top: string; left: string }>(() => ({
|
|||
const isCommunityNode = computed<boolean>(() => isCommunityPackageName(props.nodeType.name));
|
||||
|
||||
const displayName = computed<string>(() => {
|
||||
const displayName = props.nodeType.displayName.trimEnd();
|
||||
const trimmedDisplayName = props.nodeType.displayName.trimEnd();
|
||||
|
||||
return i18n.headerText({
|
||||
key: `headers.${shortNodeType.value}.displayName`,
|
||||
fallback: hasActions.value ? displayName.replace('Trigger', '') : displayName,
|
||||
fallback: hasActions.value ? trimmedDisplayName.replace('Trigger', '') : trimmedDisplayName,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -165,7 +165,7 @@ function onDragOver(event: DragEvent): void {
|
|||
draggablePosition.value = { x, y };
|
||||
}
|
||||
|
||||
function onDragEnd(event: DragEvent): void {
|
||||
function onDragEnd(): void {
|
||||
document.body.removeEventListener('dragover', onDragOver);
|
||||
|
||||
dragging.value = false;
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
<template>
|
||||
<n8n-node-creator-node
|
||||
:class="$style.subCategory"
|
||||
:title="i18n.baseText(`nodeCreator.subcategoryNames.${subcategoryName}`)"
|
||||
:title="i18n.baseText(`nodeCreator.subcategoryNames.${subcategoryName}` as BaseTextKey)"
|
||||
:is-trigger="false"
|
||||
:description="i18n.baseText(`nodeCreator.subcategoryDescriptions.${subcategoryName}`)"
|
||||
:description="
|
||||
i18n.baseText(`nodeCreator.subcategoryDescriptions.${subcategoryName}` as BaseTextKey)
|
||||
"
|
||||
:show-action-arrow="true"
|
||||
>
|
||||
<template #icon>
|
||||
|
@ -23,6 +25,7 @@ import type { SubcategoryItemProps } from '@/Interface';
|
|||
import { camelCase } from 'lodash-es';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import type { BaseTextKey } from '@/plugins/i18n';
|
||||
|
||||
export interface Props {
|
||||
item: SubcategoryItemProps;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, getCurrentInstance, onMounted, defineComponent, h } from 'vue';
|
||||
import { computed, onMounted, defineComponent, h } from 'vue';
|
||||
import type { PropType } from 'vue';
|
||||
import type {
|
||||
INodeCreateElement,
|
||||
ActionTypeDescription,
|
||||
NodeFilterType,
|
||||
IUpdateInformation,
|
||||
ActionCreateElement,
|
||||
NodeCreateElement,
|
||||
} from '@/Interface';
|
||||
import {
|
||||
HTTP_REQUEST_NODE_TYPE,
|
||||
|
@ -27,18 +27,17 @@ import { useViewStacks } from '../composables/useViewStacks';
|
|||
import ItemsRenderer from '../Renderers/ItemsRenderer.vue';
|
||||
import CategorizedItemsRenderer from '../Renderers/CategorizedItemsRenderer.vue';
|
||||
import type { IDataObject } from 'n8n-workflow';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
|
||||
const emit = defineEmits({
|
||||
nodeTypeSelected: (nodeTypes: string[]) => true,
|
||||
nodeTypeSelected: (_nodeTypes: string[]) => true,
|
||||
});
|
||||
const instance = getCurrentInstance();
|
||||
const telemetry = instance?.proxy.$telemetry;
|
||||
const telemetry = useTelemetry();
|
||||
|
||||
const { userActivated } = useUsersStore();
|
||||
const { popViewStack, updateCurrentViewStack } = useViewStacks();
|
||||
const { registerKeyHook } = useKeyboardNavigation();
|
||||
const {
|
||||
getNodeTypesWithManualTrigger,
|
||||
setAddedNodeActionParameters,
|
||||
getActionData,
|
||||
getPlaceholderTriggerActions,
|
||||
|
@ -133,13 +132,15 @@ function arrowLeft() {
|
|||
|
||||
function onKeySelect(activeItemId: string) {
|
||||
const mergedActions = [...actions.value, ...placeholderTriggerActions];
|
||||
const activeAction = mergedActions.find((a) => a.uuid === activeItemId);
|
||||
const activeAction = mergedActions.find((a): a is NodeCreateElement => a.uuid === activeItemId);
|
||||
|
||||
if (activeAction) onSelected(activeAction);
|
||||
}
|
||||
|
||||
function onSelected(actionCreateElement: INodeCreateElement) {
|
||||
const actionData = getActionData(actionCreateElement.properties as ActionTypeDescription);
|
||||
if (actionCreateElement.type !== 'action') return;
|
||||
|
||||
const actionData = getActionData(actionCreateElement.properties);
|
||||
const isPlaceholderTriggerAction = placeholderTriggerActions.some(
|
||||
(p) => p.key === actionCreateElement.key,
|
||||
);
|
||||
|
@ -211,6 +212,7 @@ const OrderSwitcher = defineComponent({
|
|||
props: {
|
||||
rootView: {
|
||||
type: String as PropType<NodeFilterType>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
|
@ -232,7 +234,7 @@ onMounted(() => {
|
|||
|
||||
<template>
|
||||
<div :class="$style.container">
|
||||
<OrderSwitcher :root-view="rootView">
|
||||
<OrderSwitcher v-if="rootView" :root-view="rootView">
|
||||
<template v-if="isTriggerRootView || parsedTriggerActionsBaseline.length !== 0" #triggers>
|
||||
<!-- Triggers Category -->
|
||||
<CategorizedItemsRenderer
|
||||
|
@ -255,7 +257,7 @@ onMounted(() => {
|
|||
<span
|
||||
v-html="
|
||||
$locale.baseText('nodeCreator.actionsCallout.noTriggerItems', {
|
||||
interpolate: { nodeName: subcategory },
|
||||
interpolate: { nodeName: subcategory ?? '' },
|
||||
})
|
||||
"
|
||||
/>
|
||||
|
@ -295,7 +297,7 @@ onMounted(() => {
|
|||
<span
|
||||
v-html="
|
||||
$locale.baseText('nodeCreator.actionsCallout.noActionItems', {
|
||||
interpolate: { nodeName: subcategory },
|
||||
interpolate: { nodeName: subcategory ?? '' },
|
||||
})
|
||||
"
|
||||
/>
|
||||
|
@ -316,7 +318,7 @@ onMounted(() => {
|
|||
@click.prevent="addHttpNode"
|
||||
v-html="
|
||||
$locale.baseText('nodeCreator.actionsList.apiCall', {
|
||||
interpolate: { node: subcategory },
|
||||
interpolate: { node: subcategory ?? '' },
|
||||
})
|
||||
"
|
||||
/>
|
||||
|
|
|
@ -30,7 +30,7 @@ export interface Props {
|
|||
}
|
||||
|
||||
const emit = defineEmits({
|
||||
nodeTypeSelected: (nodeTypes: string[]) => true,
|
||||
nodeTypeSelected: (_nodeTypes: string[]) => true,
|
||||
});
|
||||
|
||||
const i18n = useI18n();
|
||||
|
@ -98,7 +98,7 @@ function onSelected(item: INodeCreateElement) {
|
|||
subcategory: item.properties.displayName,
|
||||
title: item.properties.displayName,
|
||||
nodeIcon: {
|
||||
color: item.properties.defaults?.color || '',
|
||||
color: item.properties.defaults?.color?.toString(),
|
||||
icon,
|
||||
iconType: item.properties.iconUrl ? 'file' : 'icon',
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, watch, ref, getCurrentInstance } from 'vue';
|
||||
import { computed, watch, ref } from 'vue';
|
||||
import type { INodeCreateElement } from '@/Interface';
|
||||
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
|
@ -8,6 +8,7 @@ import { useKeyboardNavigation } from '../composables/useKeyboardNavigation';
|
|||
import { useViewStacks } from '../composables/useViewStacks';
|
||||
import ItemsRenderer from './ItemsRenderer.vue';
|
||||
import CategoryItem from '../ItemTypes/CategoryItem.vue';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
|
||||
export interface Props {
|
||||
elements: INodeCreateElement[];
|
||||
|
@ -23,8 +24,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
elements: () => [],
|
||||
});
|
||||
|
||||
const instance = getCurrentInstance();
|
||||
|
||||
const telemetry = useTelemetry();
|
||||
const { popViewStack } = useViewStacks();
|
||||
const { registerKeyHook } = useKeyboardNavigation();
|
||||
const { workflowId } = useWorkflowsStore();
|
||||
|
@ -41,7 +41,7 @@ function setExpanded(isExpanded: boolean) {
|
|||
expanded.value = isExpanded;
|
||||
|
||||
if (expanded.value) {
|
||||
instance?.proxy.$telemetry.trackNodesPanel('nodeCreateList.onCategoryExpanded', {
|
||||
telemetry.trackNodesPanel('nodeCreateList.onCategoryExpanded', {
|
||||
category_name: props.category,
|
||||
workflow_id: workflowId,
|
||||
});
|
||||
|
|
|
@ -25,9 +25,9 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'selected', element: INodeCreateElement, $e?: Event): void;
|
||||
(event: 'dragstart', element: INodeCreateElement, $e: Event): void;
|
||||
(event: 'dragend', element: INodeCreateElement, $e: Event): void;
|
||||
selected: [element: INodeCreateElement, $e?: Event];
|
||||
dragstart: [element: INodeCreateElement, $e: Event];
|
||||
dragend: [element: INodeCreateElement, $e: Event];
|
||||
}>();
|
||||
|
||||
const renderedItems = ref<INodeCreateElement[]>([]);
|
||||
|
@ -61,7 +61,23 @@ function wrappedEmit(
|
|||
) {
|
||||
if (props.disabled) return;
|
||||
|
||||
emit(event, element, $e);
|
||||
switch (event) {
|
||||
case 'dragstart':
|
||||
if ($e) {
|
||||
emit('dragstart', element, $e);
|
||||
break;
|
||||
}
|
||||
case 'dragend':
|
||||
if ($e) {
|
||||
emit('dragend', element, $e);
|
||||
break;
|
||||
}
|
||||
case 'selected':
|
||||
emit('selected', element, $e);
|
||||
break;
|
||||
default:
|
||||
emit(event, element, $e);
|
||||
}
|
||||
}
|
||||
|
||||
function beforeEnter(el: HTMLElement) {
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
mockViewCreateElement,
|
||||
mockSectionCreateElement,
|
||||
} from './utils';
|
||||
import ItemsRenderer from '../Renderers/ItemsRenderer.vue';
|
||||
import ItemsRenderer from '@/components/Node/NodeCreator/Renderers/ItemsRenderer.vue';
|
||||
import { createComponentRenderer } from '@/__tests__/render';
|
||||
|
||||
const renderComponent = createComponentRenderer(ItemsRenderer);
|
||||
|
@ -88,8 +88,21 @@ describe('ItemsRenderer', () => {
|
|||
|
||||
for (const [index, itemType] of Object.keys(itemTypes).entries()) {
|
||||
const itemElement = itemTypes[itemType as keyof typeof itemTypes];
|
||||
await fireEvent.click(itemElement!);
|
||||
expect(emitted().selected[index][0].type).toBe(itemType);
|
||||
if (itemElement) {
|
||||
await fireEvent.click(itemElement);
|
||||
const emittedEvent = emitted().selected[index];
|
||||
// Use a type guard to check if emittedEvent is an array and if its first element has a 'type' property
|
||||
if (
|
||||
Array.isArray(emittedEvent) &&
|
||||
emittedEvent.length > 0 &&
|
||||
typeof emittedEvent[0] === 'object' &&
|
||||
'type' in emittedEvent[0]
|
||||
) {
|
||||
expect(emittedEvent[0].type).toBe(itemType);
|
||||
} else {
|
||||
fail('Emitted event is not an array or does not have a type property');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -74,7 +74,7 @@ describe('useActionsGenerator', () => {
|
|||
],
|
||||
};
|
||||
|
||||
const { actions } = generateMergedNodesAndActions([node]);
|
||||
const { actions } = generateMergedNodesAndActions([node], []);
|
||||
expect(actions).toEqual({
|
||||
[NODE_NAME]: [
|
||||
expect.objectContaining({
|
||||
|
@ -117,7 +117,7 @@ describe('useActionsGenerator', () => {
|
|||
],
|
||||
};
|
||||
|
||||
const { actions } = generateMergedNodesAndActions([node]);
|
||||
const { actions } = generateMergedNodesAndActions([node], []);
|
||||
expect(actions).toEqual({
|
||||
[NODE_NAME]: [
|
||||
expect.objectContaining({
|
||||
|
@ -156,7 +156,7 @@ describe('useActionsGenerator', () => {
|
|||
],
|
||||
};
|
||||
|
||||
const { actions } = generateMergedNodesAndActions([node]);
|
||||
const { actions } = generateMergedNodesAndActions([node], []);
|
||||
expect(actions).toEqual({
|
||||
[NODE_NAME]: [],
|
||||
});
|
||||
|
@ -189,7 +189,7 @@ describe('useActionsGenerator', () => {
|
|||
],
|
||||
};
|
||||
|
||||
const { actions } = generateMergedNodesAndActions([node]);
|
||||
const { actions } = generateMergedNodesAndActions([node], []);
|
||||
expect(actions).toEqual({
|
||||
[NODE_NAME]: [
|
||||
expect.objectContaining({
|
||||
|
@ -251,7 +251,7 @@ describe('useActionsGenerator', () => {
|
|||
],
|
||||
};
|
||||
|
||||
const { actions } = generateMergedNodesAndActions([node]);
|
||||
const { actions } = generateMergedNodesAndActions([node], []);
|
||||
expect(actions).toEqual({
|
||||
[NODE_NAME]: [
|
||||
expect.objectContaining({
|
||||
|
@ -326,7 +326,7 @@ describe('useActionsGenerator', () => {
|
|||
],
|
||||
};
|
||||
|
||||
const { actions } = generateMergedNodesAndActions([node]);
|
||||
const { actions } = generateMergedNodesAndActions([node], []);
|
||||
expect(actions).toEqual({
|
||||
[NODE_NAME]: [
|
||||
expect.objectContaining({
|
||||
|
@ -371,7 +371,7 @@ describe('useActionsGenerator', () => {
|
|||
],
|
||||
};
|
||||
|
||||
const { actions } = generateMergedNodesAndActions([node]);
|
||||
const { actions } = generateMergedNodesAndActions([node], []);
|
||||
expect(actions).toEqual({
|
||||
[NODE_NAME]: [
|
||||
expect.objectContaining({
|
||||
|
|
|
@ -33,6 +33,7 @@ export const mockSimplifiedNodeType = (
|
|||
defaults: {
|
||||
color: '#ffffff',
|
||||
},
|
||||
outputs: [],
|
||||
...overrides,
|
||||
});
|
||||
|
||||
|
@ -123,7 +124,7 @@ export const mockLabelCreateElement = (
|
|||
uuid: uuidv4(),
|
||||
key: uuidv4(),
|
||||
type: 'label',
|
||||
subcategory: subcategory || 'sampleSubcategory',
|
||||
subcategory: subcategory ?? 'sampleSubcategory',
|
||||
properties: mockLabelItemProps(overrides),
|
||||
});
|
||||
|
||||
|
@ -134,6 +135,6 @@ export const mockActionCreateElement = (
|
|||
uuid: uuidv4(),
|
||||
key: uuidv4(),
|
||||
type: 'action',
|
||||
subcategory: subcategory || 'sampleSubcategory',
|
||||
subcategory: subcategory ?? 'sampleSubcategory',
|
||||
properties: mockActionTypeDescription(overrides),
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { getCurrentInstance, computed } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import type { IDataObject, INodeParameters } from 'n8n-workflow';
|
||||
import type {
|
||||
ActionTypeDescription,
|
||||
|
@ -27,7 +27,6 @@ import {
|
|||
TRIGGER_NODE_CREATOR_VIEW,
|
||||
WEBHOOK_NODE_TYPE,
|
||||
} from '@/constants';
|
||||
import { i18n } from '@/plugins/i18n';
|
||||
|
||||
import type { BaseTextKey } from '@/plugins/i18n';
|
||||
import type { Telemetry } from '@/plugins/telemetry';
|
||||
|
@ -37,10 +36,11 @@ import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
|||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||
|
||||
import { sortNodeCreateElements, transformNodeType } from '../utils';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
|
||||
export const useActions = () => {
|
||||
const nodeCreatorStore = useNodeCreatorStore();
|
||||
const instance = getCurrentInstance();
|
||||
const i18n = useI18n();
|
||||
|
||||
const singleNodeOpenSources = [
|
||||
NODE_CREATOR_OPEN_SOURCES.PLUS_ENDPOINT,
|
||||
|
@ -50,8 +50,8 @@ export const useActions = () => {
|
|||
|
||||
const actionsCategoryLocales = computed(() => {
|
||||
return {
|
||||
actions: instance?.proxy.$locale.baseText('nodeCreator.actionsCategory.actions') ?? '',
|
||||
triggers: instance?.proxy.$locale.baseText('nodeCreator.actionsCategory.triggers') ?? '',
|
||||
actions: i18n.baseText('nodeCreator.actionsCategory.actions') ?? '',
|
||||
triggers: i18n.baseText('nodeCreator.actionsCategory.triggers') ?? '',
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -66,7 +66,7 @@ export const useActions = () => {
|
|||
if (transformed.type === 'action') {
|
||||
const nameBase = node.name.replace('n8n-nodes-base.', '');
|
||||
const localeKey = `nodeCreator.actionsPlaceholderNode.${nameBase}` as BaseTextKey;
|
||||
const overwriteLocale = instance?.proxy.$locale.baseText(localeKey) as string;
|
||||
const overwriteLocale = i18n.baseText(localeKey);
|
||||
|
||||
// If the locale key is not the same as the node name, it means it contain a translation
|
||||
// and we should use it
|
||||
|
@ -127,9 +127,11 @@ export const useActions = () => {
|
|||
},
|
||||
};
|
||||
|
||||
const insertIndex = firstIndexMap.get(label)! + insertedLabels;
|
||||
extendedActions.splice(insertIndex, 0, newLabel);
|
||||
insertedLabels++;
|
||||
const insertIndex = firstIndexMap.get(label);
|
||||
if (insertIndex !== undefined) {
|
||||
extendedActions.splice(insertIndex + insertedLabels, 0, newLabel);
|
||||
insertedLabels++;
|
||||
}
|
||||
}
|
||||
|
||||
return extendedActions;
|
||||
|
@ -148,7 +150,7 @@ export const useActions = () => {
|
|||
function getActionData(actionItem: ActionTypeDescription): IUpdateInformation {
|
||||
const displayOptions = actionItem.displayOptions;
|
||||
|
||||
const displayConditions = Object.keys(displayOptions?.show || {}).reduce(
|
||||
const displayConditions = Object.keys(displayOptions?.show ?? {}).reduce(
|
||||
(acc: IDataObject, showCondition: string) => {
|
||||
acc[showCondition] = displayOptions?.show?.[showCondition]?.[0];
|
||||
return acc;
|
||||
|
|
|
@ -39,7 +39,7 @@ const customNodeActionsParsers: {
|
|||
displayName: cachedBaseText('nodeCreator.actionsCategory.onEvent', {
|
||||
interpolate: { event: startCase(categoryItem.name) },
|
||||
}),
|
||||
description: categoryItem.description || '',
|
||||
description: categoryItem.description ?? '',
|
||||
displayOptions: matchedProperty.displayOptions,
|
||||
values: { eventsUi: { eventValues: [{ name: categoryItem.value }] } },
|
||||
}),
|
||||
|
@ -56,7 +56,7 @@ function getNodeTypeBase(nodeTypeDescription: INodeTypeDescription, label?: stri
|
|||
name: nodeTypeDescription.name,
|
||||
group: nodeTypeDescription.group,
|
||||
codex: {
|
||||
label: label || '',
|
||||
label: label ?? '',
|
||||
categories: [category],
|
||||
},
|
||||
iconUrl: nodeTypeDescription.iconUrl,
|
||||
|
@ -139,7 +139,7 @@ function triggersCategory(nodeTypeDescription: INodeTypeDescription): ActionType
|
|||
cachedBaseText('nodeCreator.actionsCategory.onEvent', {
|
||||
interpolate: { event: startCase(categoryItem.name) },
|
||||
}),
|
||||
description: categoryItem.description || '',
|
||||
description: categoryItem.description ?? '',
|
||||
displayOptions: matchedProperty.displayOptions,
|
||||
values: {
|
||||
[matchedProperty.name]:
|
||||
|
@ -159,14 +159,14 @@ function resourceCategories(nodeTypeDescription: INodeTypeDescription): ActionTy
|
|||
matchedProperties.forEach((property) => {
|
||||
((property.options as INodePropertyOptions[]) || [])
|
||||
.filter((option) => option.value !== CUSTOM_API_CALL_KEY)
|
||||
.forEach((resourceOption, i, options) => {
|
||||
.forEach((resourceOption, _i, options) => {
|
||||
const isSingleResource = options.length === 1;
|
||||
|
||||
// Match operations for the resource by checking if displayOptions matches or contains the resource name
|
||||
const operations = nodeTypeDescription.properties.find((operation) => {
|
||||
const isOperation = operation.name === 'operation';
|
||||
const isMatchingResource =
|
||||
operation.displayOptions?.show?.resource?.includes(resourceOption.value) ||
|
||||
operation.displayOptions?.show?.resource?.includes(resourceOption.value) ??
|
||||
isSingleResource;
|
||||
|
||||
// If the operation doesn't have a version defined, it should be
|
||||
|
@ -178,7 +178,9 @@ function resourceCategories(nodeTypeDescription: INodeTypeDescription): ActionTy
|
|||
: [nodeTypeDescription.version];
|
||||
|
||||
const isMatchingVersion = operationVersions
|
||||
? operationVersions.some((version) => nodeTypeVersions.includes(version))
|
||||
? operationVersions.some(
|
||||
(version) => typeof version === 'number' && nodeTypeVersions.includes(version),
|
||||
)
|
||||
: true;
|
||||
|
||||
return isOperation && isMatchingResource && isMatchingVersion;
|
||||
|
@ -286,10 +288,17 @@ export function useActionsGenerator() {
|
|||
actions[app.name] = appActions;
|
||||
|
||||
if (app.name === HTTP_REQUEST_NODE_TYPE) {
|
||||
const credentialOnlyNodes = httpOnlyCredentials.map((credentialType) =>
|
||||
getSimplifiedNodeType(getCredentialOnlyNodeType(app, credentialType)),
|
||||
const credentialOnlyNodes = httpOnlyCredentials.map((credentialType) => {
|
||||
const credsOnlyNode = getCredentialOnlyNodeType(app, credentialType);
|
||||
if (credsOnlyNode) return getSimplifiedNodeType(credsOnlyNode);
|
||||
return null;
|
||||
});
|
||||
|
||||
const filteredNodes = credentialOnlyNodes.filter(
|
||||
(node): node is SimplifiedNodeType => node !== null,
|
||||
);
|
||||
mergedNodes.push(...credentialOnlyNodes);
|
||||
|
||||
mergedNodes.push(...filteredNodes);
|
||||
}
|
||||
|
||||
mergedNodes.push(getSimplifiedNodeType(app));
|
||||
|
|
|
@ -54,7 +54,7 @@ interface ViewStack {
|
|||
baseFilter?: (item: INodeCreateElement) => boolean;
|
||||
itemsMapper?: (item: INodeCreateElement) => INodeCreateElement;
|
||||
panelClass?: string;
|
||||
sections?: NodeViewItemSection[];
|
||||
sections?: string[] | NodeViewItemSection[];
|
||||
}
|
||||
|
||||
export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
|
||||
|
@ -128,7 +128,7 @@ export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
|
|||
const i18n = useI18n();
|
||||
|
||||
let nodesByConnectionType: { [key: string]: string[] };
|
||||
let relatedAIView: NodeViewItem | { properties: { title: string; icon: string } } | undefined;
|
||||
let relatedAIView: { properties: NodeViewItem['properties'] } | undefined;
|
||||
|
||||
if (isOutput === true) {
|
||||
nodesByConnectionType = useNodeTypesStore().visibleNodeTypesByInputConnectionTypeNames;
|
||||
|
|
|
@ -86,16 +86,20 @@ export function flattenCreateElements(items: INodeCreateElement[]): INodeCreateE
|
|||
|
||||
export function groupItemsInSections(
|
||||
items: INodeCreateElement[],
|
||||
sections: NodeViewItemSection[],
|
||||
sections: string[] | NodeViewItemSection[],
|
||||
): INodeCreateElement[] {
|
||||
const filteredSections = sections.filter(
|
||||
(section): section is NodeViewItemSection => typeof section === 'object',
|
||||
);
|
||||
|
||||
const itemsBySection = items.reduce((acc: Record<string, INodeCreateElement[]>, item) => {
|
||||
const section = sections.find((s) => s.items.includes(item.key));
|
||||
const section = filteredSections.find((s) => s.items.includes(item.key));
|
||||
const key = section?.key ?? 'other';
|
||||
acc[key] = [...(acc[key] ?? []), item];
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const result: SectionCreateElement[] = sections
|
||||
const result: SectionCreateElement[] = filteredSections
|
||||
.map(
|
||||
(section): SectionCreateElement => ({
|
||||
type: 'section',
|
||||
|
|
|
@ -71,8 +71,8 @@ export interface NodeViewItem {
|
|||
type: string;
|
||||
properties: {
|
||||
name?: string;
|
||||
title: string;
|
||||
icon: string;
|
||||
title?: string;
|
||||
icon?: string;
|
||||
iconProps?: {
|
||||
color?: string;
|
||||
};
|
||||
|
@ -81,8 +81,14 @@ export interface NodeViewItem {
|
|||
group?: string[];
|
||||
sections?: NodeViewItemSection[];
|
||||
description?: string;
|
||||
displayName?: string;
|
||||
tag?: string;
|
||||
forceIncludeNodes?: string[];
|
||||
iconData?: {
|
||||
type: string;
|
||||
icon?: string;
|
||||
fileBuffer?: string;
|
||||
};
|
||||
};
|
||||
category?: string | string[];
|
||||
}
|
||||
|
@ -264,7 +270,7 @@ export function AINodesView(_nodes: SimplifiedNodeType[]): NodeView {
|
|||
};
|
||||
}
|
||||
|
||||
export function TriggerView(nodes: SimplifiedNodeType[]) {
|
||||
export function TriggerView() {
|
||||
const i18n = useI18n();
|
||||
|
||||
const view: NodeView = {
|
||||
|
|
|
@ -11,12 +11,12 @@
|
|||
:show-tooltip="showTooltip"
|
||||
:tooltip-position="tooltipPosition"
|
||||
:badge="badge"
|
||||
@click="(e) => $emit('click')"
|
||||
@click="() => $emit('click')"
|
||||
></n8n-node-icon>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { IVersionNode } from '@/Interface';
|
||||
import type { ActionTypeDescription, IVersionNode, SimplifiedNodeType } from '@/Interface';
|
||||
import { useRootStore } from '@/stores/n8nRoot.store';
|
||||
import type { INodeTypeDescription } from 'n8n-workflow';
|
||||
import { mapStores } from 'pinia';
|
||||
|
@ -32,7 +32,10 @@ export default defineComponent({
|
|||
name: 'NodeIcon',
|
||||
props: {
|
||||
nodeType: {
|
||||
type: Object as PropType<INodeTypeDescription | IVersionNode | null>,
|
||||
type: Object as PropType<
|
||||
INodeTypeDescription | IVersionNode | SimplifiedNodeType | ActionTypeDescription | null
|
||||
>,
|
||||
required: true,
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
|
|
Loading…
Reference in a new issue