refactor(editor): Apply Prettier (no-changelog) (#4920)
* ⚡ Adjust `format` script * 🔥 Remove exemption for `editor-ui` * 🎨 Prettify * 👕 Fix lint
This commit is contained in:
@@ -10,12 +10,8 @@
|
||||
:isTrigger="isTriggerAction(action)"
|
||||
>
|
||||
<template #dragContent>
|
||||
<div :class="$style.draggableDataTransfer" ref="draggableDataTransfer"/>
|
||||
<div
|
||||
:class="$style.draggable"
|
||||
:style="draggableStyle"
|
||||
v-show="dragging"
|
||||
>
|
||||
<div :class="$style.draggableDataTransfer" ref="draggableDataTransfer" />
|
||||
<div :class="$style.draggable" :style="draggableStyle" v-show="dragging">
|
||||
<node-icon :nodeType="nodeType" @click.capture.stop :size="40" :shrink="false" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -34,14 +30,15 @@ import NodeIcon from '@/components/NodeIcon.vue';
|
||||
import { useNodeCreatorStore } from '@/stores/nodeCreator';
|
||||
|
||||
export interface Props {
|
||||
nodeType: INodeTypeDescription,
|
||||
action: INodeActionTypeDescription,
|
||||
nodeType: INodeTypeDescription;
|
||||
action: INodeActionTypeDescription;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const instance = getCurrentInstance();
|
||||
const telemetry = instance?.proxy.$telemetry;
|
||||
const { getActionData, getNodeTypesWithManualTrigger, setAddedNodeActionParameters } = useNodeCreatorStore();
|
||||
const { getActionData, getNodeTypesWithManualTrigger, setAddedNodeActionParameters } =
|
||||
useNodeCreatorStore();
|
||||
|
||||
const state = reactive({
|
||||
dragging: false,
|
||||
@@ -53,19 +50,20 @@ const state = reactive({
|
||||
draggableDataTransfer: null as Element | null,
|
||||
});
|
||||
const emit = defineEmits<{
|
||||
(event: 'actionSelected', action: IUpdateInformation): void,
|
||||
(event: 'dragstart', $e: DragEvent): void,
|
||||
(event: 'dragend', $e: DragEvent): void,
|
||||
(event: 'actionSelected', action: IUpdateInformation): void;
|
||||
(event: 'dragstart', $e: DragEvent): void;
|
||||
(event: 'dragend', $e: DragEvent): void;
|
||||
}>();
|
||||
|
||||
const draggableStyle = computed<{ top: string; left: string; }>(() => ({
|
||||
const draggableStyle = computed<{ top: string; left: string }>(() => ({
|
||||
top: `${state.draggablePosition.y}px`,
|
||||
left: `${state.draggablePosition.x}px`,
|
||||
}));
|
||||
|
||||
const actionData = computed(() => getActionData(props.action));
|
||||
|
||||
const isTriggerAction = (action: INodeActionTypeDescription) => action.name?.toLowerCase().includes('trigger');
|
||||
const isTriggerAction = (action: INodeActionTypeDescription) =>
|
||||
action.name?.toLowerCase().includes('trigger');
|
||||
function onActionClick(actionItem: INodeActionTypeDescription) {
|
||||
emit('actionSelected', getActionData(actionItem));
|
||||
}
|
||||
@@ -76,16 +74,19 @@ function onDragStart(event: DragEvent): void {
|
||||
* All browsers attach the correct page coordinates to the "dragover" event.
|
||||
* @bug https://bugzilla.mozilla.org/show_bug.cgi?id=505521
|
||||
*/
|
||||
document.body.addEventListener("dragover", onDragOver);
|
||||
document.body.addEventListener('dragover', onDragOver);
|
||||
const { pageX: x, pageY: y } = event;
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.effectAllowed = "copy";
|
||||
event.dataTransfer.dropEffect = "copy";
|
||||
event.dataTransfer.effectAllowed = 'copy';
|
||||
event.dataTransfer.dropEffect = 'copy';
|
||||
event.dataTransfer.setDragImage(state.draggableDataTransfer as Element, 0, 0);
|
||||
event.dataTransfer.setData('nodeTypeName', getNodeTypesWithManualTrigger(actionData.value?.key).join(','));
|
||||
event.dataTransfer.setData(
|
||||
'nodeTypeName',
|
||||
getNodeTypesWithManualTrigger(actionData.value?.key).join(','),
|
||||
);
|
||||
|
||||
state.storeWatcher = setAddedNodeActionParameters(actionData.value, telemetry);
|
||||
document.body.addEventListener("dragend", onDragEnd);
|
||||
document.body.addEventListener('dragend', onDragEnd);
|
||||
}
|
||||
|
||||
state.dragging = true;
|
||||
@@ -94,19 +95,19 @@ function onDragStart(event: DragEvent): void {
|
||||
}
|
||||
|
||||
function onDragOver(event: DragEvent): void {
|
||||
if (!state.dragging || event.pageX === 0 && event.pageY === 0) {
|
||||
if (!state.dragging || (event.pageX === 0 && event.pageY === 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [x,y] = getNewNodePosition([], [event.pageX - NODE_SIZE / 2, event.pageY - NODE_SIZE / 2]);
|
||||
const [x, y] = getNewNodePosition([], [event.pageX - NODE_SIZE / 2, event.pageY - NODE_SIZE / 2]);
|
||||
|
||||
state.draggablePosition = { x, y };
|
||||
}
|
||||
|
||||
function onDragEnd(event: DragEvent): void {
|
||||
if(state.storeWatcher) state.storeWatcher();
|
||||
document.body.removeEventListener("dragend", onDragEnd);
|
||||
document.body.removeEventListener("dragover", onDragOver);
|
||||
if (state.storeWatcher) state.storeWatcher();
|
||||
document.body.removeEventListener('dragend', onDragEnd);
|
||||
document.body.removeEventListener('dragover', onDragOver);
|
||||
|
||||
emit('dragend', event);
|
||||
|
||||
@@ -135,7 +136,7 @@ const { draggableDataTransfer, dragging } = toRefs(state);
|
||||
color: var(--color-text-base);
|
||||
padding-top: var(--spacing-s);
|
||||
line-height: var(--font-line-height-regular);
|
||||
border-top: 1px solid #DBDFE7;
|
||||
border-top: 1px solid #dbdfe7;
|
||||
z-index: 1;
|
||||
// Prevent double borders when the last category is collapsed
|
||||
margin-top: -1px;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<transition :name="`panel-slide-${state.transitionDirection}`" >
|
||||
<transition :name="`panel-slide-${state.transitionDirection}`">
|
||||
<div
|
||||
ref="mainPanelContainer"
|
||||
tabindex="0"
|
||||
@@ -12,12 +12,16 @@
|
||||
<slot name="header" />
|
||||
</div>
|
||||
|
||||
<div :class="$style.subcategoryHeader" v-if="activeSubcategory" data-test-id="categorized-items-subcategory">
|
||||
<div
|
||||
:class="$style.subcategoryHeader"
|
||||
v-if="activeSubcategory"
|
||||
data-test-id="categorized-items-subcategory"
|
||||
>
|
||||
<button :class="$style.subcategoryBackButton" @click="onSubcategoryClose">
|
||||
<font-awesome-icon :class="$style.subcategoryBackIcon" icon="arrow-left" size="2x" />
|
||||
</button>
|
||||
<node-icon
|
||||
v-if="(showSubcategoryIcon && activeSubcategory.properties.nodeType)"
|
||||
v-if="showSubcategoryIcon && activeSubcategory.properties.nodeType"
|
||||
:class="$style.nodeIcon"
|
||||
:nodeType="activeSubcategory.properties.nodeType"
|
||||
:size="16"
|
||||
@@ -30,16 +34,24 @@
|
||||
v-if="alwaysShowSearch || isSearchVisible"
|
||||
:key="nodeCreatorStore.selectedType"
|
||||
:value="nodeCreatorStore.itemsFilter"
|
||||
:placeholder="searchPlaceholder ? searchPlaceholder : $locale.baseText('nodeCreator.searchBar.searchNodes')"
|
||||
:placeholder="
|
||||
searchPlaceholder
|
||||
? searchPlaceholder
|
||||
: $locale.baseText('nodeCreator.searchBar.searchNodes')
|
||||
"
|
||||
ref="searchBar"
|
||||
@input="onNodeFilterChange"
|
||||
/>
|
||||
|
||||
<template v-if="(searchFilter.length > 0 && filteredNodeTypes.length === 0)">
|
||||
<template v-if="searchFilter.length > 0 && filteredNodeTypes.length === 0">
|
||||
<no-results
|
||||
data-test-id="categorized-no-results"
|
||||
:showRequest="(!$slots.noResultsTitle && !$slots.noResultsAction) && filteredAllNodeTypes.length === 0"
|
||||
:show-icon="(!$slots.noResultsTitle && !$slots.noResultsAction) && filteredAllNodeTypes.length === 0"
|
||||
:showRequest="
|
||||
!$slots.noResultsTitle && !$slots.noResultsAction && filteredAllNodeTypes.length === 0
|
||||
"
|
||||
:show-icon="
|
||||
!$slots.noResultsTitle && !$slots.noResultsAction && filteredAllNodeTypes.length === 0
|
||||
"
|
||||
>
|
||||
<template v-if="$slots.noResultsTitle" #title>
|
||||
<slot name="noResultsTitle" />
|
||||
@@ -49,7 +61,7 @@
|
||||
<p v-html="$locale.baseText('nodeCreator.noResults.clickToSeeResults')" />
|
||||
</template>
|
||||
<template v-else #title>
|
||||
<p v-text="$locale.baseText('nodeCreator.noResults.weDidntMakeThatYet')"/>
|
||||
<p v-text="$locale.baseText('nodeCreator.noResults.weDidntMakeThatYet')" />
|
||||
</template>
|
||||
|
||||
<template v-if="$slots.noResultsAction" #action>
|
||||
@@ -57,14 +69,20 @@
|
||||
</template>
|
||||
<template v-else-if="filteredAllNodeTypes.length === 0" #action>
|
||||
{{ $locale.baseText('nodeCreator.noResults.dontWorryYouCanProbablyDoItWithThe') }}
|
||||
<n8n-link @click="selectHttpRequest" v-if="[REGULAR_NODE_FILTER, ALL_NODE_FILTER].includes(nodeCreatorStore.selectedType)">
|
||||
<n8n-link
|
||||
@click="selectHttpRequest"
|
||||
v-if="[REGULAR_NODE_FILTER, ALL_NODE_FILTER].includes(nodeCreatorStore.selectedType)"
|
||||
>
|
||||
{{ $locale.baseText('nodeCreator.noResults.httpRequest') }}
|
||||
</n8n-link>
|
||||
<template v-if="nodeCreatorStore.selectedType === ALL_NODE_FILTER">
|
||||
{{ $locale.baseText('nodeCreator.noResults.or') }}
|
||||
</template>
|
||||
|
||||
<n8n-link @click="selectWebhook" v-if="[TRIGGER_NODE_FILTER, ALL_NODE_FILTER].includes(nodeCreatorStore.selectedType)">
|
||||
<n8n-link
|
||||
@click="selectWebhook"
|
||||
v-if="[TRIGGER_NODE_FILTER, ALL_NODE_FILTER].includes(nodeCreatorStore.selectedType)"
|
||||
>
|
||||
{{ $locale.baseText('nodeCreator.noResults.webhook') }}
|
||||
</n8n-link>
|
||||
{{ $locale.baseText('nodeCreator.noResults.node') }}
|
||||
@@ -81,7 +99,7 @@
|
||||
</template>
|
||||
<div :class="$style.scrollable" ref="scrollableContainer" v-else>
|
||||
<item-iterator
|
||||
:elements="searchFilter.length === 0 ? renderedItems :filteredNodeTypes"
|
||||
:elements="searchFilter.length === 0 ? renderedItems : filteredNodeTypes"
|
||||
:activeIndex="activeSubcategory ? activeSubcategoryIndex : activeIndex"
|
||||
:with-actions-getter="withActionsGetter"
|
||||
:lazyRender="lazyRender"
|
||||
@@ -90,7 +108,6 @@
|
||||
@actionsOpen="$listeners.actionsOpen"
|
||||
@nodeTypeSelected="$listeners.nodeTypeSelected"
|
||||
>
|
||||
|
||||
</item-iterator>
|
||||
<div :class="$style.footer" v-if="$slots.footer">
|
||||
<slot name="footer" />
|
||||
@@ -101,7 +118,17 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, onMounted, watch, getCurrentInstance, toRefs, ref, onUnmounted, nextTick } from 'vue';
|
||||
import {
|
||||
computed,
|
||||
reactive,
|
||||
onMounted,
|
||||
watch,
|
||||
getCurrentInstance,
|
||||
toRefs,
|
||||
ref,
|
||||
onUnmounted,
|
||||
nextTick,
|
||||
} from 'vue';
|
||||
import camelcase from 'lodash.camelcase';
|
||||
|
||||
import { externalHooks } from '@/mixins/externalHooks';
|
||||
@@ -111,10 +138,24 @@ import ItemIterator from './ItemIterator.vue';
|
||||
import NoResults from './NoResults.vue';
|
||||
import SearchBar from './SearchBar.vue';
|
||||
import NodeIcon from '@/components/NodeIcon.vue';
|
||||
import { INodeCreateElement, ISubcategoryItemProps, ICategoryItemProps, ICategoriesWithNodes, SubcategoryCreateElement, NodeCreateElement } from '@/Interface';
|
||||
import { WEBHOOK_NODE_TYPE, HTTP_REQUEST_NODE_TYPE, ALL_NODE_FILTER, TRIGGER_NODE_FILTER, REGULAR_NODE_FILTER, NODE_TYPE_COUNT_MAPPER } from '@/constants';
|
||||
import {
|
||||
INodeCreateElement,
|
||||
ISubcategoryItemProps,
|
||||
ICategoryItemProps,
|
||||
ICategoriesWithNodes,
|
||||
SubcategoryCreateElement,
|
||||
NodeCreateElement,
|
||||
} from '@/Interface';
|
||||
import {
|
||||
WEBHOOK_NODE_TYPE,
|
||||
HTTP_REQUEST_NODE_TYPE,
|
||||
ALL_NODE_FILTER,
|
||||
TRIGGER_NODE_FILTER,
|
||||
REGULAR_NODE_FILTER,
|
||||
NODE_TYPE_COUNT_MAPPER,
|
||||
} from '@/constants';
|
||||
import { BaseTextKey } from '@/plugins/i18n';
|
||||
import { sublimeSearch, matchesNodeType, matchesSelectType } from '@/utils';
|
||||
import { sublimeSearch, matchesNodeType, matchesSelectType } from '@/utils';
|
||||
import { useWorkflowsStore } from '@/stores/workflows';
|
||||
import { useRootStore } from '@/stores/n8nRootStore';
|
||||
import { useNodeCreatorStore } from '@/stores/nodeCreator';
|
||||
@@ -149,11 +190,11 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'subcategoryClose', value: INodeCreateElement[]): void,
|
||||
(event: 'onSubcategorySelected', value: INodeCreateElement): void,
|
||||
(event: 'nodeTypeSelected', value: string[]): void,
|
||||
(event: 'actionSelected', value: INodeCreateElement): void,
|
||||
(event: 'actionsOpen', value: INodeTypeDescription): void,
|
||||
(event: 'subcategoryClose', value: INodeCreateElement[]): void;
|
||||
(event: 'onSubcategorySelected', value: INodeCreateElement): void;
|
||||
(event: 'nodeTypeSelected', value: string[]): void;
|
||||
(event: 'actionSelected', value: INodeCreateElement): void;
|
||||
(event: 'actionsOpen', value: INodeTypeDescription): void;
|
||||
}>();
|
||||
|
||||
const instance = getCurrentInstance();
|
||||
@@ -168,7 +209,12 @@ const nodeCreatorStore = useNodeCreatorStore();
|
||||
const state = reactive({
|
||||
activeCategory: props.initialActiveCategories,
|
||||
// Keep track of activated subcategories so we could traverse back more than one level
|
||||
activeSubcategoryHistory: [] as Array<{scrollPosition: number, subcategory: INodeCreateElement, activeIndex: number, filter: string}>,
|
||||
activeSubcategoryHistory: [] as Array<{
|
||||
scrollPosition: number;
|
||||
subcategory: INodeCreateElement;
|
||||
activeIndex: number;
|
||||
filter: string;
|
||||
}>,
|
||||
activeIndex: props.initialActiveIndex || 0,
|
||||
activeSubcategoryIndex: 0,
|
||||
ALL_NODE_FILTER,
|
||||
@@ -180,151 +226,159 @@ const state = reactive({
|
||||
const searchBar = ref<InstanceType<typeof SearchBar>>();
|
||||
const scrollableContainer = ref<InstanceType<typeof HTMLElement>>();
|
||||
|
||||
const activeSubcategory = computed<INodeCreateElement | null> (
|
||||
() => state.activeSubcategoryHistory[state.activeSubcategoryHistory.length - 1]?.subcategory || null,
|
||||
const activeSubcategory = computed<INodeCreateElement | null>(
|
||||
() =>
|
||||
state.activeSubcategoryHistory[state.activeSubcategoryHistory.length - 1]?.subcategory || null,
|
||||
);
|
||||
|
||||
const activeSubcategoryTitle = computed<string> (() => {
|
||||
if(!activeSubcategory.value || !activeSubcategory.value.properties) return '';
|
||||
const activeSubcategoryTitle = computed<string>(() => {
|
||||
if (!activeSubcategory.value || !activeSubcategory.value.properties) return '';
|
||||
|
||||
const subcategory = (activeSubcategory.value.properties as ISubcategoryItemProps).subcategory;
|
||||
const subcategoryName = camelcase(subcategory);
|
||||
const subcategoryName = camelcase(subcategory);
|
||||
|
||||
const titleLocaleKey = `nodeCreator.subcategoryTitles.${subcategoryName}` as BaseTextKey;
|
||||
const nameLocaleKey = `nodeCreator.subcategoryNames.${subcategoryName}` as BaseTextKey;
|
||||
const titleLocaleKey = `nodeCreator.subcategoryTitles.${subcategoryName}` as BaseTextKey;
|
||||
const nameLocaleKey = `nodeCreator.subcategoryNames.${subcategoryName}` as BaseTextKey;
|
||||
|
||||
const titleLocale = instance?.proxy?.$locale.baseText(titleLocaleKey) as string;
|
||||
const nameLocale = instance?.proxy?.$locale.baseText(nameLocaleKey) as string;
|
||||
// If resolved title locale is same as the locale key it means it doesn't exist
|
||||
// so we fallback to the subcategoryName
|
||||
if(titleLocale === titleLocaleKey) return nameLocale === nameLocaleKey ? subcategory : nameLocale;
|
||||
const titleLocale = instance?.proxy?.$locale.baseText(titleLocaleKey) as string;
|
||||
const nameLocale = instance?.proxy?.$locale.baseText(nameLocaleKey) as string;
|
||||
// If resolved title locale is same as the locale key it means it doesn't exist
|
||||
// so we fallback to the subcategoryName
|
||||
if (titleLocale === titleLocaleKey)
|
||||
return nameLocale === nameLocaleKey ? subcategory : nameLocale;
|
||||
|
||||
return titleLocale;
|
||||
return titleLocale;
|
||||
});
|
||||
|
||||
const searchFilter = computed<string> (() => nodeCreatorStore.itemsFilter.toLowerCase().trim());
|
||||
const searchFilter = computed<string>(() => nodeCreatorStore.itemsFilter.toLowerCase().trim());
|
||||
|
||||
|
||||
const matchedTypeNodes = computed<INodeCreateElement[]> (() => {
|
||||
if(!props.filterByType) return props.searchItems;
|
||||
return props.searchItems.filter((el: INodeCreateElement) => matchesSelectType(el, nodeCreatorStore.selectedType));
|
||||
const matchedTypeNodes = computed<INodeCreateElement[]>(() => {
|
||||
if (!props.filterByType) return props.searchItems;
|
||||
return props.searchItems.filter((el: INodeCreateElement) =>
|
||||
matchesSelectType(el, nodeCreatorStore.selectedType),
|
||||
);
|
||||
});
|
||||
|
||||
const filteredNodeTypes = computed<INodeCreateElement[]> (() => {
|
||||
const filter = searchFilter.value;
|
||||
const filteredNodeTypes = computed<INodeCreateElement[]>(() => {
|
||||
const filter = searchFilter.value;
|
||||
|
||||
let returnItems: INodeCreateElement[] = [];
|
||||
if (defaultLocale !== 'en') {
|
||||
returnItems = props.searchItems.filter((el: INodeCreateElement) => {
|
||||
return filter && matchesSelectType(el, nodeCreatorStore.selectedType) && matchesNodeType(el, filter);
|
||||
});
|
||||
}
|
||||
else {
|
||||
const matchingNodes = props.filterByType
|
||||
let returnItems: INodeCreateElement[] = [];
|
||||
if (defaultLocale !== 'en') {
|
||||
returnItems = props.searchItems.filter((el: INodeCreateElement) => {
|
||||
return (
|
||||
filter &&
|
||||
matchesSelectType(el, nodeCreatorStore.selectedType) &&
|
||||
matchesNodeType(el, filter)
|
||||
);
|
||||
});
|
||||
} else {
|
||||
const matchingNodes = props.filterByType
|
||||
? props.searchItems.filter((el) => matchesSelectType(el, nodeCreatorStore.selectedType))
|
||||
: props.searchItems;
|
||||
|
||||
const matchedCategorizedNodes = sublimeSearch<INodeCreateElement>(filter, matchingNodes, [
|
||||
{key: 'properties.nodeType.displayName', weight: 2},
|
||||
{key: 'properties.nodeType.codex.alias', weight: 1},
|
||||
const matchedCategorizedNodes = sublimeSearch<INodeCreateElement>(filter, matchingNodes, [
|
||||
{ key: 'properties.nodeType.displayName', weight: 2 },
|
||||
{ key: 'properties.nodeType.codex.alias', weight: 1 },
|
||||
]);
|
||||
returnItems = matchedCategorizedNodes.map(({item}) => item);
|
||||
}
|
||||
returnItems = matchedCategorizedNodes.map(({ item }) => item);
|
||||
}
|
||||
|
||||
return returnItems;
|
||||
return returnItems;
|
||||
});
|
||||
|
||||
const filteredAllNodeTypes = computed<INodeCreateElement[]> (() => {
|
||||
if(filteredNodeTypes.value.length > 0) return [];
|
||||
const filteredAllNodeTypes = computed<INodeCreateElement[]>(() => {
|
||||
if (filteredNodeTypes.value.length > 0) return [];
|
||||
|
||||
const matchedAllNodex = props.allItems.filter((el: INodeCreateElement) => {
|
||||
return searchFilter.value && el.type === 'node' && matchesNodeType(el, searchFilter.value);
|
||||
});
|
||||
const matchedAllNodex = props.allItems.filter((el: INodeCreateElement) => {
|
||||
return searchFilter.value && el.type === 'node' && matchesNodeType(el, searchFilter.value);
|
||||
});
|
||||
|
||||
return matchedAllNodex;
|
||||
return matchedAllNodex;
|
||||
});
|
||||
|
||||
const categorized = computed<INodeCreateElement[]> (() => {
|
||||
return props.categorizedItems
|
||||
.reduce((accu: INodeCreateElement[], el: INodeCreateElement) => {
|
||||
if(
|
||||
el.type === 'subcategory' &&
|
||||
(props.excludedSubcategories || []).includes((el.properties as ISubcategoryItemProps).subcategory)
|
||||
) {
|
||||
return accu;
|
||||
}
|
||||
const categorized = computed<INodeCreateElement[]>(() => {
|
||||
return props.categorizedItems.reduce((accu: INodeCreateElement[], el: INodeCreateElement) => {
|
||||
if (
|
||||
el.type === 'subcategory' &&
|
||||
(props.excludedSubcategories || []).includes(
|
||||
(el.properties as ISubcategoryItemProps).subcategory,
|
||||
)
|
||||
) {
|
||||
return accu;
|
||||
}
|
||||
|
||||
if (
|
||||
el.type !== 'category' &&
|
||||
!state.activeCategory.includes(el.category)
|
||||
) {
|
||||
return accu;
|
||||
}
|
||||
if (el.type !== 'category' && !state.activeCategory.includes(el.category)) {
|
||||
return accu;
|
||||
}
|
||||
|
||||
if (!matchesSelectType(el, nodeCreatorStore.selectedType)) {
|
||||
return accu;
|
||||
}
|
||||
if (!matchesSelectType(el, nodeCreatorStore.selectedType)) {
|
||||
return accu;
|
||||
}
|
||||
|
||||
if (el.type === 'category') {
|
||||
accu.push({
|
||||
...el,
|
||||
properties: {
|
||||
expanded: state.activeCategory.includes(el.category),
|
||||
},
|
||||
} as INodeCreateElement);
|
||||
return accu;
|
||||
}
|
||||
if (el.type === 'category') {
|
||||
accu.push({
|
||||
...el,
|
||||
properties: {
|
||||
expanded: state.activeCategory.includes(el.category),
|
||||
},
|
||||
} as INodeCreateElement);
|
||||
return accu;
|
||||
}
|
||||
|
||||
accu.push(el);
|
||||
return accu;
|
||||
}, []);
|
||||
accu.push(el);
|
||||
return accu;
|
||||
}, []);
|
||||
});
|
||||
|
||||
const subcategorizedItems = computed<INodeCreateElement[]> (() => {
|
||||
if(!activeSubcategory.value) return [];
|
||||
const subcategorizedItems = computed<INodeCreateElement[]>(() => {
|
||||
if (!activeSubcategory.value) return [];
|
||||
|
||||
const category = activeSubcategory.value.category;
|
||||
const subcategory = (activeSubcategory.value.properties as ISubcategoryItemProps).subcategory;
|
||||
const category = activeSubcategory.value.category;
|
||||
const subcategory = (activeSubcategory.value.properties as ISubcategoryItemProps).subcategory;
|
||||
|
||||
// If no category is set, we use all categorized nodes
|
||||
const nodes = category
|
||||
? props.categoriesWithNodes[category][subcategory].nodes
|
||||
: categorized.value;
|
||||
// If no category is set, we use all categorized nodes
|
||||
const nodes = category
|
||||
? props.categoriesWithNodes[category][subcategory].nodes
|
||||
: categorized.value;
|
||||
|
||||
return nodes.filter((el: INodeCreateElement) => matchesSelectType(el, nodeCreatorStore.selectedType));
|
||||
return nodes.filter((el: INodeCreateElement) =>
|
||||
matchesSelectType(el, nodeCreatorStore.selectedType),
|
||||
);
|
||||
});
|
||||
|
||||
const renderedItems = computed<INodeCreateElement[]> (() => {
|
||||
if(props.firstLevelItems.length > 0 && activeSubcategory.value === null) return props.firstLevelItems;
|
||||
if(props.flatten) return matchedTypeNodes.value;
|
||||
if(subcategorizedItems.value.length === 0) return categorized.value;
|
||||
const renderedItems = computed<INodeCreateElement[]>(() => {
|
||||
if (props.firstLevelItems.length > 0 && activeSubcategory.value === null)
|
||||
return props.firstLevelItems;
|
||||
if (props.flatten) return matchedTypeNodes.value;
|
||||
if (subcategorizedItems.value.length === 0) return categorized.value;
|
||||
|
||||
const isSingleCategory = subcategorizedItems.value.filter((item) => item.type === 'category').length === 1;
|
||||
return isSingleCategory ? subcategorizedItems.value.slice(1) : subcategorizedItems.value;
|
||||
const isSingleCategory =
|
||||
subcategorizedItems.value.filter((item) => item.type === 'category').length === 1;
|
||||
return isSingleCategory ? subcategorizedItems.value.slice(1) : subcategorizedItems.value;
|
||||
});
|
||||
|
||||
const isSearchVisible = computed<boolean> (() => {
|
||||
if(subcategorizedItems.value.length === 0) return true;
|
||||
const isSearchVisible = computed<boolean>(() => {
|
||||
if (subcategorizedItems.value.length === 0) return true;
|
||||
|
||||
let totalItems = 0;
|
||||
for (const item of subcategorizedItems.value) {
|
||||
// Category contains many nodes so we need to count all of them
|
||||
// for the current selectedType
|
||||
if(item.type === 'category') {
|
||||
const categoryItems = props.categoriesWithNodes[item.key];
|
||||
const categoryItemsCount = Object.values(categoryItems)?.[0];
|
||||
const countKeys = NODE_TYPE_COUNT_MAPPER[nodeCreatorStore.selectedType];
|
||||
let totalItems = 0;
|
||||
for (const item of subcategorizedItems.value) {
|
||||
// Category contains many nodes so we need to count all of them
|
||||
// for the current selectedType
|
||||
if (item.type === 'category') {
|
||||
const categoryItems = props.categoriesWithNodes[item.key];
|
||||
const categoryItemsCount = Object.values(categoryItems)?.[0];
|
||||
const countKeys = NODE_TYPE_COUNT_MAPPER[nodeCreatorStore.selectedType];
|
||||
|
||||
for (const countKey of countKeys) {
|
||||
totalItems += categoryItemsCount[(countKey as "triggerCount" | "regularCount")];
|
||||
}
|
||||
for (const countKey of countKeys) {
|
||||
totalItems += categoryItemsCount[countKey as 'triggerCount' | 'regularCount'];
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
// If it's not category, it must be just a node item so we count it as 1
|
||||
totalItems += 1;
|
||||
}
|
||||
return totalItems > 9;
|
||||
continue;
|
||||
}
|
||||
// If it's not category, it must be just a node item so we count it as 1
|
||||
totalItems += 1;
|
||||
}
|
||||
return totalItems > 9;
|
||||
});
|
||||
|
||||
// Methods
|
||||
@@ -332,107 +386,116 @@ function getScrollTop() {
|
||||
return scrollableContainer.value?.scrollTop || 0;
|
||||
}
|
||||
function setScrollTop(scrollTop: number) {
|
||||
if(scrollableContainer.value) {
|
||||
if (scrollableContainer.value) {
|
||||
scrollableContainer.value.scrollTop = scrollTop;
|
||||
}
|
||||
}
|
||||
function switchToAllTabAndFilter() {
|
||||
const currentFilter = nodeCreatorStore.itemsFilter;
|
||||
nodeCreatorStore.setShowTabs(true);
|
||||
nodeCreatorStore.setSelectedType(ALL_NODE_FILTER);
|
||||
state.activeSubcategoryHistory = [];
|
||||
nodeCreatorStore.setShowTabs(true);
|
||||
nodeCreatorStore.setSelectedType(ALL_NODE_FILTER);
|
||||
state.activeSubcategoryHistory = [];
|
||||
|
||||
nextTick(() => onNodeFilterChange(currentFilter));
|
||||
nextTick(() => onNodeFilterChange(currentFilter));
|
||||
}
|
||||
|
||||
function onNodeFilterChange(filter: string) {
|
||||
nodeCreatorStore.setFilter(filter);
|
||||
nodeCreatorStore.setFilter(filter);
|
||||
}
|
||||
|
||||
function selectWebhook() {
|
||||
emit('nodeTypeSelected', [WEBHOOK_NODE_TYPE]);
|
||||
emit('nodeTypeSelected', [WEBHOOK_NODE_TYPE]);
|
||||
}
|
||||
|
||||
function selectHttpRequest() {
|
||||
emit('nodeTypeSelected', [HTTP_REQUEST_NODE_TYPE]);
|
||||
emit('nodeTypeSelected', [HTTP_REQUEST_NODE_TYPE]);
|
||||
}
|
||||
function nodeFilterKeyDown(e: KeyboardEvent) {
|
||||
// We only want to propagate 'Escape' as it closes the node-creator and
|
||||
// 'Tab' which toggles it
|
||||
if (!['Escape', 'Tab'].includes(e.key)) e.stopPropagation();
|
||||
// We only want to propagate 'Escape' as it closes the node-creator and
|
||||
// 'Tab' which toggles it
|
||||
if (!['Escape', 'Tab'].includes(e.key)) e.stopPropagation();
|
||||
|
||||
// Prevent cursors position change
|
||||
if(['ArrowUp', 'ArrowDown'].includes(e.key)) e.preventDefault();
|
||||
// Prevent cursors position change
|
||||
if (['ArrowUp', 'ArrowDown'].includes(e.key)) e.preventDefault();
|
||||
|
||||
if (activeSubcategory.value) {
|
||||
const activeList = searchFilter.value.length > 0 ? filteredNodeTypes.value : renderedItems.value;;
|
||||
if (activeSubcategory.value) {
|
||||
const activeList =
|
||||
searchFilter.value.length > 0 ? filteredNodeTypes.value : renderedItems.value;
|
||||
const activeNodeType = activeList[state.activeSubcategoryIndex];
|
||||
|
||||
if (e.key === 'ArrowDown' && activeSubcategory.value) {
|
||||
state.activeSubcategoryIndex++;
|
||||
state.activeSubcategoryIndex = Math.min(
|
||||
state.activeSubcategoryIndex,
|
||||
activeList.length - 1,
|
||||
);
|
||||
}
|
||||
else if (e.key === 'ArrowUp' && activeSubcategory.value) {
|
||||
state.activeSubcategoryIndex--;
|
||||
state.activeSubcategoryIndex = Math.max(state.activeSubcategoryIndex, 0);
|
||||
}
|
||||
else if (e.key === 'Enter') {
|
||||
selected(activeNodeType);
|
||||
} else if (e.key === 'ArrowLeft' && activeNodeType?.type === 'category' && (activeNodeType.properties as ICategoryItemProps).expanded) {
|
||||
selected(activeNodeType);
|
||||
} else if (e.key === 'ArrowLeft') {
|
||||
onSubcategoryClose();
|
||||
} else if (e.key === 'ArrowRight' && activeNodeType?.type === 'category' && !(activeNodeType.properties as ICategoryItemProps).expanded) {
|
||||
selected(activeNodeType);
|
||||
} else if (e.key === 'ArrowRight' && (['node','action'].includes(activeNodeType?.type))) {
|
||||
if (e.key === 'ArrowDown' && activeSubcategory.value) {
|
||||
state.activeSubcategoryIndex++;
|
||||
state.activeSubcategoryIndex = Math.min(state.activeSubcategoryIndex, activeList.length - 1);
|
||||
} else if (e.key === 'ArrowUp' && activeSubcategory.value) {
|
||||
state.activeSubcategoryIndex--;
|
||||
state.activeSubcategoryIndex = Math.max(state.activeSubcategoryIndex, 0);
|
||||
} else if (e.key === 'Enter') {
|
||||
selected(activeNodeType);
|
||||
} else if (
|
||||
e.key === 'ArrowLeft' &&
|
||||
activeNodeType?.type === 'category' &&
|
||||
(activeNodeType.properties as ICategoryItemProps).expanded
|
||||
) {
|
||||
selected(activeNodeType);
|
||||
} else if (e.key === 'ArrowLeft') {
|
||||
onSubcategoryClose();
|
||||
} else if (
|
||||
e.key === 'ArrowRight' &&
|
||||
activeNodeType?.type === 'category' &&
|
||||
!(activeNodeType.properties as ICategoryItemProps).expanded
|
||||
) {
|
||||
selected(activeNodeType);
|
||||
} else if (e.key === 'ArrowRight' && ['node', 'action'].includes(activeNodeType?.type)) {
|
||||
selected(activeNodeType);
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const activeList = searchFilter.value.length > 0 ? filteredNodeTypes.value : renderedItems.value;
|
||||
const activeNodeType = activeList[state.activeIndex];
|
||||
const activeList = searchFilter.value.length > 0 ? filteredNodeTypes.value : renderedItems.value;
|
||||
const activeNodeType = activeList[state.activeIndex];
|
||||
|
||||
if (e.key === 'ArrowDown') {
|
||||
state.activeIndex++;
|
||||
// Make sure that we stop at the last nodeType
|
||||
state.activeIndex = Math.min(
|
||||
state.activeIndex,
|
||||
activeList.length - 1,
|
||||
);
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
state.activeIndex--;
|
||||
// Make sure that we do not get before the first nodeType
|
||||
state.activeIndex = Math.max(state.activeIndex, 0);
|
||||
} else if (e.key === 'Enter' && activeNodeType) {
|
||||
selected(activeNodeType);
|
||||
} else if (e.key === 'ArrowRight' && activeNodeType?.type === 'subcategory') {
|
||||
selected(activeNodeType);
|
||||
} else if (e.key === 'ArrowRight' && activeNodeType?.type === 'category' && !(activeNodeType.properties as ICategoryItemProps).expanded) {
|
||||
selected(activeNodeType);
|
||||
} else if (e.key === 'ArrowLeft' && activeNodeType?.type === 'category' && (activeNodeType.properties as ICategoryItemProps).expanded) {
|
||||
selected(activeNodeType);
|
||||
} else if (e.key === 'ArrowRight' && (['node','action'].includes(activeNodeType?.type))) {
|
||||
if (e.key === 'ArrowDown') {
|
||||
state.activeIndex++;
|
||||
// Make sure that we stop at the last nodeType
|
||||
state.activeIndex = Math.min(state.activeIndex, activeList.length - 1);
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
state.activeIndex--;
|
||||
// Make sure that we do not get before the first nodeType
|
||||
state.activeIndex = Math.max(state.activeIndex, 0);
|
||||
} else if (e.key === 'Enter' && activeNodeType) {
|
||||
selected(activeNodeType);
|
||||
} else if (e.key === 'ArrowRight' && activeNodeType?.type === 'subcategory') {
|
||||
selected(activeNodeType);
|
||||
} else if (
|
||||
e.key === 'ArrowRight' &&
|
||||
activeNodeType?.type === 'category' &&
|
||||
!(activeNodeType.properties as ICategoryItemProps).expanded
|
||||
) {
|
||||
selected(activeNodeType);
|
||||
} else if (
|
||||
e.key === 'ArrowLeft' &&
|
||||
activeNodeType?.type === 'category' &&
|
||||
(activeNodeType.properties as ICategoryItemProps).expanded
|
||||
) {
|
||||
selected(activeNodeType);
|
||||
} else if (e.key === 'ArrowRight' && ['node', 'action'].includes(activeNodeType?.type)) {
|
||||
selected(activeNodeType);
|
||||
}
|
||||
}
|
||||
function selected(element: INodeCreateElement) {
|
||||
const typeHandler = {
|
||||
category: () => onCategorySelected(element.category),
|
||||
subcategory: () => onSubcategorySelected(element),
|
||||
const typeHandler = {
|
||||
category: () => onCategorySelected(element.category),
|
||||
subcategory: () => onSubcategorySelected(element),
|
||||
node: () => onNodeSelected(element as NodeCreateElement),
|
||||
action: () => onActionSelected(element),
|
||||
};
|
||||
};
|
||||
|
||||
typeHandler[element.type]();
|
||||
typeHandler[element.type]();
|
||||
}
|
||||
|
||||
function onNodeSelected(element: NodeCreateElement) {
|
||||
const hasActions = (element.properties.nodeType?.actions?.length || 0) > 0;
|
||||
if(props.withActionsGetter && props.withActionsGetter(element) === true && hasActions) {
|
||||
if (props.withActionsGetter && props.withActionsGetter(element) === true && hasActions) {
|
||||
emit('actionsOpen', element.properties.nodeType);
|
||||
return;
|
||||
}
|
||||
@@ -440,18 +503,19 @@ function onNodeSelected(element: NodeCreateElement) {
|
||||
}
|
||||
|
||||
function onCategorySelected(category: string) {
|
||||
if (state.activeCategory.includes(category)) {
|
||||
state.activeCategory = state.activeCategory.filter(
|
||||
(active: string) => active !== category,
|
||||
);
|
||||
} else {
|
||||
state.activeCategory = [...state.activeCategory, category];
|
||||
instance?.proxy.$telemetry.trackNodesPanel('nodeCreateList.onCategoryExpanded', { category_name: category, workflow_id: workflowId });
|
||||
}
|
||||
if (state.activeCategory.includes(category)) {
|
||||
state.activeCategory = state.activeCategory.filter((active: string) => active !== category);
|
||||
} else {
|
||||
state.activeCategory = [...state.activeCategory, category];
|
||||
instance?.proxy.$telemetry.trackNodesPanel('nodeCreateList.onCategoryExpanded', {
|
||||
category_name: category,
|
||||
workflow_id: workflowId,
|
||||
});
|
||||
}
|
||||
|
||||
state.activeIndex = categorized.value.findIndex(
|
||||
(el: INodeCreateElement) => el.category === category,
|
||||
);
|
||||
state.activeIndex = categorized.value.findIndex(
|
||||
(el: INodeCreateElement) => el.category === category,
|
||||
);
|
||||
}
|
||||
function onActionSelected(element: INodeCreateElement) {
|
||||
emit('actionSelected', element);
|
||||
@@ -461,19 +525,22 @@ function onSubcategorySelected(selected: INodeCreateElement, track = true) {
|
||||
state.transitionDirection = 'in';
|
||||
// Store the current subcategory UI details in the state
|
||||
// so we could revert it when the user closes the subcategory
|
||||
state.activeSubcategoryHistory.push({
|
||||
state.activeSubcategoryHistory.push({
|
||||
subcategory: selected,
|
||||
activeIndex: state.activeSubcategoryIndex,
|
||||
scrollPosition: getScrollTop(),
|
||||
filter: nodeCreatorStore.itemsFilter,
|
||||
});
|
||||
nodeCreatorStore.setFilter('');
|
||||
emit('onSubcategorySelected', selected);
|
||||
nodeCreatorStore.setShowTabs(false);
|
||||
state.activeSubcategoryIndex = 0;
|
||||
emit('onSubcategorySelected', selected);
|
||||
nodeCreatorStore.setShowTabs(false);
|
||||
state.activeSubcategoryIndex = 0;
|
||||
|
||||
if(track) {
|
||||
instance?.proxy.$telemetry.trackNodesPanel('nodeCreateList.onSubcategorySelected', { selected, workflow_id: workflowId });
|
||||
if (track) {
|
||||
instance?.proxy.$telemetry.trackNodesPanel('nodeCreateList.onSubcategorySelected', {
|
||||
selected,
|
||||
workflow_id: workflowId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,23 +549,32 @@ async function onSubcategoryClose() {
|
||||
const poppedSubCategory = state.activeSubcategoryHistory.pop();
|
||||
onNodeFilterChange(poppedSubCategory?.filter || '');
|
||||
await nextTick();
|
||||
emit('subcategoryClose', state.activeSubcategoryHistory.map((el) => el.subcategory));
|
||||
emit(
|
||||
'subcategoryClose',
|
||||
state.activeSubcategoryHistory.map((el) => el.subcategory),
|
||||
);
|
||||
await nextTick();
|
||||
setScrollTop(poppedSubCategory?.scrollPosition || 0);
|
||||
state.activeSubcategoryIndex = poppedSubCategory?.activeIndex || 0;
|
||||
|
||||
if(!nodeCreatorStore.showScrim && state.activeSubcategoryHistory.length === 0) {
|
||||
if (!nodeCreatorStore.showScrim && state.activeSubcategoryHistory.length === 0) {
|
||||
nodeCreatorStore.setShowTabs(true);
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.expandAllCategories, (expandAll) => {
|
||||
if (expandAll) state.activeCategory = Object.keys(props.categoriesWithNodes);
|
||||
});
|
||||
watch(
|
||||
() => props.expandAllCategories,
|
||||
(expandAll) => {
|
||||
if (expandAll) state.activeCategory = Object.keys(props.categoriesWithNodes);
|
||||
},
|
||||
);
|
||||
|
||||
watch(() => props.subcategoryOverride, (subcategory) => {
|
||||
if (subcategory) onSubcategorySelected(subcategory, false);
|
||||
});
|
||||
watch(
|
||||
() => props.subcategoryOverride,
|
||||
(subcategory) => {
|
||||
if (subcategory) onSubcategorySelected(subcategory, false);
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
registerCustomAction('showAllNodeCreatorNodes', switchToAllTabAndFilter);
|
||||
@@ -518,30 +594,33 @@ watch(filteredNodeTypes, (returnItems) => {
|
||||
});
|
||||
|
||||
watch(isSearchVisible, (isVisible) => {
|
||||
if(isVisible === false) {
|
||||
if (isVisible === false) {
|
||||
// Focus the root container when search is hidden to make sure
|
||||
// keyboard navigation still works
|
||||
nextTick(() => state.mainPanelContainer?.focus());
|
||||
}
|
||||
});
|
||||
watch(() => nodeCreatorStore.itemsFilter, (newValue, oldValue) => {
|
||||
// Reset the index whenver the filter-value changes
|
||||
state.activeIndex = 0;
|
||||
state.activeSubcategoryIndex = 0;
|
||||
$externalHooks().run('nodeCreateList.nodeFilterChanged', {
|
||||
oldValue,
|
||||
newValue,
|
||||
selectedType: nodeCreatorStore.selectedType,
|
||||
filteredNodes: filteredNodeTypes.value,
|
||||
});
|
||||
instance?.proxy.$telemetry.trackNodesPanel('nodeCreateList.nodeFilterChanged', {
|
||||
oldValue,
|
||||
newValue,
|
||||
selectedType: nodeCreatorStore.selectedType,
|
||||
filteredNodes: filteredNodeTypes.value,
|
||||
workflow_id: workflowId,
|
||||
});
|
||||
});
|
||||
watch(
|
||||
() => nodeCreatorStore.itemsFilter,
|
||||
(newValue, oldValue) => {
|
||||
// Reset the index whenver the filter-value changes
|
||||
state.activeIndex = 0;
|
||||
state.activeSubcategoryIndex = 0;
|
||||
$externalHooks().run('nodeCreateList.nodeFilterChanged', {
|
||||
oldValue,
|
||||
newValue,
|
||||
selectedType: nodeCreatorStore.selectedType,
|
||||
filteredNodes: filteredNodeTypes.value,
|
||||
});
|
||||
instance?.proxy.$telemetry.trackNodesPanel('nodeCreateList.nodeFilterChanged', {
|
||||
oldValue,
|
||||
newValue,
|
||||
selectedType: nodeCreatorStore.selectedType,
|
||||
filteredNodes: filteredNodeTypes.value,
|
||||
workflow_id: workflowId,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const { activeSubcategoryIndex, activeIndex, mainPanelContainer } = toRefs(state);
|
||||
</script>
|
||||
@@ -557,7 +636,6 @@ const { activeSubcategoryIndex, activeIndex, mainPanelContainer } = toRefs(state
|
||||
right: 0;
|
||||
}
|
||||
|
||||
|
||||
:global(.panel-slide-out-enter),
|
||||
:global(.panel-slide-in-leave-to) {
|
||||
transform: translateX(0);
|
||||
@@ -593,7 +671,7 @@ const { activeSubcategoryIndex, activeIndex, mainPanelContainer } = toRefs(state
|
||||
margin: 0 var(--spacing-xs) 0;
|
||||
padding: var(--spacing-4xs) 0;
|
||||
line-height: var(--font-line-height-regular);
|
||||
border-top: 1px solid #DBDFE7;
|
||||
border-top: 1px solid #dbdfe7;
|
||||
z-index: 1;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
<template>
|
||||
<div :class="$style.category">
|
||||
<span :class="$style.name">
|
||||
{{ renderCategoryName(item.category) }}{{ count !== undefined ? ` (${count})` : ''}}
|
||||
{{ renderCategoryName(item.category) }}{{ count !== undefined ? ` (${count})` : '' }}
|
||||
</span>
|
||||
<font-awesome-icon
|
||||
v-if="isExpanded"
|
||||
icon="chevron-down"
|
||||
:class="$style.arrow"
|
||||
/>
|
||||
<font-awesome-icon v-if="isExpanded" icon="chevron-down" :class="$style.arrow" />
|
||||
<font-awesome-icon :class="$style.arrow" icon="chevron-up" v-else />
|
||||
</div>
|
||||
</template>
|
||||
@@ -25,15 +21,13 @@ export interface Props {
|
||||
const props = defineProps<Props>();
|
||||
const instance = getCurrentInstance();
|
||||
|
||||
const isExpanded = computed<boolean>(() =>(props.item.properties as ICategoryItemProps).expanded);
|
||||
const isExpanded = computed<boolean>(() => (props.item.properties as ICategoryItemProps).expanded);
|
||||
|
||||
function renderCategoryName(categoryName: string) {
|
||||
const camelCasedCategoryName = camelcase(categoryName) as CategoryName;
|
||||
const key = `nodeCreator.categoryNames.${camelCasedCategoryName}` as const;
|
||||
|
||||
return instance?.proxy.$locale.exists(key)
|
||||
? instance?.proxy.$locale.baseText(key)
|
||||
: categoryName;
|
||||
return instance?.proxy.$locale.exists(key) ? instance?.proxy.$locale.baseText(key) : categoryName;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
:key="`${item.key}-${index}`"
|
||||
data-test-id="item-iterator-item"
|
||||
:class="{
|
||||
'clickable': !disabled,
|
||||
clickable: !disabled,
|
||||
[$style[item.type]]: true,
|
||||
[$style.active]: activeIndex === index && !disabled,
|
||||
[$style.iteratorItem]: true
|
||||
[$style.iteratorItem]: true,
|
||||
}"
|
||||
ref="iteratorItems"
|
||||
@click="wrappedEmit('selected', item)"
|
||||
@@ -26,10 +26,7 @@
|
||||
:count="enableGlobalCategoriesCounter ? getCategoryCount(item) : undefined"
|
||||
/>
|
||||
|
||||
<subcategory-item
|
||||
v-else-if="item.type === 'subcategory'"
|
||||
:item="item"
|
||||
/>
|
||||
<subcategory-item v-else-if="item.type === 'subcategory'" :item="item" />
|
||||
|
||||
<node-item
|
||||
v-else-if="item.type === 'node'"
|
||||
@@ -51,7 +48,7 @@
|
||||
</div>
|
||||
<aside
|
||||
v-for="item in elements.length"
|
||||
v-show="(renderedItems.length < item)"
|
||||
v-show="renderedItems.length < item"
|
||||
:key="item"
|
||||
:class="$style.loadingItem"
|
||||
>
|
||||
@@ -85,9 +82,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,
|
||||
(event: 'selected', element: INodeCreateElement, $e?: Event): void;
|
||||
(event: 'dragstart', element: INodeCreateElement, $e: Event): void;
|
||||
(event: 'dragend', element: INodeCreateElement, $e: Event): void;
|
||||
}>();
|
||||
|
||||
const state = reactive({
|
||||
@@ -96,10 +93,14 @@ const state = reactive({
|
||||
});
|
||||
const iteratorItems = ref<HTMLElement[]>([]);
|
||||
|
||||
function wrappedEmit(event: 'selected' | 'dragstart' | 'dragend', element: INodeCreateElement, $e?: Event) {
|
||||
function wrappedEmit(
|
||||
event: 'selected' | 'dragstart' | 'dragend',
|
||||
element: INodeCreateElement,
|
||||
$e?: Event,
|
||||
) {
|
||||
if (props.disabled) return;
|
||||
|
||||
emit((event as 'selected' || 'dragstart' || 'dragend'), element, $e);
|
||||
emit((event as 'selected') || 'dragstart' || 'dragend', element, $e);
|
||||
}
|
||||
function getCategoryCount(item: CategoryCreateElement) {
|
||||
const { categoriesWithNodes } = useNodeTypesStore();
|
||||
@@ -113,7 +114,7 @@ function getCategoryCount(item: CategoryCreateElement) {
|
||||
const countKeys = NODE_TYPE_COUNT_MAPPER[useNodeCreatorStore().selectedType];
|
||||
|
||||
for (const countKey of countKeys) {
|
||||
accu += currentCategory[subcategory][(countKey as "triggerCount" | "regularCount")];
|
||||
accu += currentCategory[subcategory][countKey as 'triggerCount' | 'regularCount'];
|
||||
}
|
||||
|
||||
return accu;
|
||||
@@ -124,13 +125,15 @@ function getCategoryCount(item: CategoryCreateElement) {
|
||||
// Lazy render large items lists to prevent the browser from freezing
|
||||
// when loading many items.
|
||||
function renderItems() {
|
||||
if(props.elements.length <= 20 || props.lazyRender === false) {
|
||||
if (props.elements.length <= 20 || props.lazyRender === false) {
|
||||
state.renderedItems = props.elements;
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
if (state.renderedItems.length < props.elements.length) {
|
||||
state.renderedItems.push(...props.elements.slice(state.renderedItems.length, state.renderedItems.length + 10));
|
||||
state.renderedItems.push(
|
||||
...props.elements.slice(state.renderedItems.length, state.renderedItems.length + 10),
|
||||
);
|
||||
state.renderAnimationRequest = window.requestAnimationFrame(renderItems);
|
||||
}
|
||||
}
|
||||
@@ -139,7 +142,6 @@ function beforeEnter(el: HTMLElement) {
|
||||
el.style.height = '0';
|
||||
}
|
||||
|
||||
|
||||
function enter(el: HTMLElement) {
|
||||
el.style.height = `${el.scrollHeight}px`;
|
||||
}
|
||||
@@ -163,17 +165,23 @@ onUnmounted(() => {
|
||||
|
||||
// Make sure the active item is always visible
|
||||
// scroll if needed
|
||||
watch(() => props.activeIndex, async () => {
|
||||
if(props.activeIndex === undefined) return;
|
||||
iteratorItems.value[props.activeIndex]?.scrollIntoView({ block: 'nearest' });
|
||||
});
|
||||
watch(
|
||||
() => props.activeIndex,
|
||||
async () => {
|
||||
if (props.activeIndex === undefined) return;
|
||||
iteratorItems.value[props.activeIndex]?.scrollIntoView({ block: 'nearest' });
|
||||
},
|
||||
);
|
||||
|
||||
// Trigger elements re-render when they change
|
||||
watch(() => props.elements, async () => {
|
||||
window.cancelAnimationFrame(state.renderAnimationRequest);
|
||||
state.renderedItems = [];
|
||||
renderItems();
|
||||
});
|
||||
watch(
|
||||
() => props.elements,
|
||||
async () => {
|
||||
window.cancelAnimationFrame(state.renderAnimationRequest);
|
||||
state.renderedItems = [];
|
||||
renderItems();
|
||||
},
|
||||
);
|
||||
|
||||
const { renderedItems } = toRefs(state);
|
||||
</script>
|
||||
@@ -188,7 +196,7 @@ const { renderedItems } = toRefs(state);
|
||||
margin-left: 1px;
|
||||
position: relative;
|
||||
&::before {
|
||||
content: "";
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -1px;
|
||||
top: 0;
|
||||
@@ -199,14 +207,13 @@ const { renderedItems } = toRefs(state);
|
||||
border-color: $node-creator-item-hover-border-color;
|
||||
}
|
||||
|
||||
&.active::before {
|
||||
&.active::before {
|
||||
border-color: $color-primary !important;
|
||||
}
|
||||
|
||||
&.category.singleCategory {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
.itemIterator {
|
||||
> *:last-child {
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
<template>
|
||||
<div
|
||||
class="container"
|
||||
ref="mainPanelContainer"
|
||||
>
|
||||
<div class="container" ref="mainPanelContainer">
|
||||
<div class="main-panel">
|
||||
<trigger-helper-panel
|
||||
v-if="nodeCreatorStore.selectedType === TRIGGER_NODE_FILTER"
|
||||
@nodeTypeSelected="$listeners.nodeTypeSelected"
|
||||
>
|
||||
<template #header>
|
||||
<type-selector/>
|
||||
<type-selector />
|
||||
</template>
|
||||
</trigger-helper-panel>
|
||||
<categorized-items
|
||||
@@ -36,7 +33,12 @@
|
||||
import { watch, getCurrentInstance, onMounted, onUnmounted } from 'vue';
|
||||
import { externalHooks } from '@/mixins/externalHooks';
|
||||
import TriggerHelperPanel from './TriggerHelperPanel.vue';
|
||||
import { ALL_NODE_FILTER, TRIGGER_NODE_FILTER, OTHER_TRIGGER_NODES_SUBCATEGORY, CORE_NODES_CATEGORY } from '@/constants';
|
||||
import {
|
||||
ALL_NODE_FILTER,
|
||||
TRIGGER_NODE_FILTER,
|
||||
OTHER_TRIGGER_NODES_SUBCATEGORY,
|
||||
CORE_NODES_CATEGORY,
|
||||
} from '@/constants';
|
||||
import CategorizedItems from './CategorizedItems.vue';
|
||||
import TypeSelector from './TypeSelector.vue';
|
||||
import { INodeCreateElement } from '@/Interface';
|
||||
@@ -58,17 +60,20 @@ const { workflowId } = useWorkflowsStore();
|
||||
const nodeCreatorStore = useNodeCreatorStore();
|
||||
const { categorizedItems, categoriesWithNodes } = useNodeTypesStore();
|
||||
|
||||
watch(() => nodeCreatorStore.selectedType, (newValue, oldValue) => {
|
||||
$externalHooks().run('nodeCreateList.selectedTypeChanged', {
|
||||
oldValue,
|
||||
newValue,
|
||||
});
|
||||
instance?.proxy.$telemetry.trackNodesPanel('nodeCreateList.selectedTypeChanged', {
|
||||
old_filter: oldValue,
|
||||
new_filter: newValue,
|
||||
workflow_id: workflowId,
|
||||
});
|
||||
});
|
||||
watch(
|
||||
() => nodeCreatorStore.selectedType,
|
||||
(newValue, oldValue) => {
|
||||
$externalHooks().run('nodeCreateList.selectedTypeChanged', {
|
||||
oldValue,
|
||||
newValue,
|
||||
});
|
||||
instance?.proxy.$telemetry.trackNodesPanel('nodeCreateList.selectedTypeChanged', {
|
||||
old_filter: oldValue,
|
||||
new_filter: newValue,
|
||||
workflow_id: workflowId,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
$externalHooks().run('nodeCreateList.mounted');
|
||||
@@ -79,9 +84,10 @@ onMounted(() => {
|
||||
onUnmounted(() => {
|
||||
nodeCreatorStore.setSelectedType(ALL_NODE_FILTER);
|
||||
$externalHooks().run('nodeCreateList.destroyed');
|
||||
instance?.proxy.$telemetry.trackNodesPanel('nodeCreateList.destroyed', { workflow_id: workflowId });
|
||||
instance?.proxy.$telemetry.trackNodesPanel('nodeCreateList.destroyed', {
|
||||
workflow_id: workflowId,
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div :class="{[$style.noResults]: true, [$style.iconless]: !showIcon}">
|
||||
<div :class="{ [$style.noResults]: true, [$style.iconless]: !showIcon }">
|
||||
<div :class="$style.icon" v-if="showIcon">
|
||||
<no-results-icon />
|
||||
</div>
|
||||
@@ -14,7 +14,8 @@
|
||||
<p v-text="$locale.baseText('nodeCreator.noResults.wantUsToMakeItFaster')" />
|
||||
<div>
|
||||
<n8n-link :to="REQUEST_NODE_FORM_URL">
|
||||
<span>{{ $locale.baseText('nodeCreator.noResults.requestTheNode') }}</span>
|
||||
<span>{{ $locale.baseText('nodeCreator.noResults.requestTheNode') }}</span
|
||||
>
|
||||
<span>
|
||||
<font-awesome-icon
|
||||
:class="$style.external"
|
||||
@@ -28,7 +29,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import { REQUEST_NODE_FORM_URL } from '@/constants';
|
||||
import NoResultsIcon from './NoResultsIcon.vue';
|
||||
@@ -65,7 +65,8 @@ defineProps<Props>();
|
||||
}
|
||||
}
|
||||
|
||||
.action p, .request p {
|
||||
.action p,
|
||||
.request p {
|
||||
font-size: var(--font-size-s);
|
||||
line-height: var(--font-line-height-xloose);
|
||||
}
|
||||
@@ -83,11 +84,10 @@ defineProps<Props>();
|
||||
.icon {
|
||||
margin-top: var(--spacing-2xl);
|
||||
min-height: 67px;
|
||||
opacity: .6;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.external {
|
||||
font-size: var(--font-size-2xs);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,22 +1,47 @@
|
||||
<template>
|
||||
<svg width="75px" height="75px" viewBox="0 0 75 75" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>no-nodes-keyart</title>
|
||||
<g id="Nodes-panel-prototype-V2.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="nodes-panel-(component)" transform="translate(-2085.000000, -352.000000)">
|
||||
<g id="nodes_panel" transform="translate(1880.000000, 151.000000)">
|
||||
<g id="Panel" transform="translate(50.000000, 0.000000)">
|
||||
<g id="Group-3" transform="translate(105.000000, 171.000000)">
|
||||
<g id="no-nodes-keyart" transform="translate(50.000000, 30.000000)">
|
||||
<rect id="Rectangle" x="0" y="0" width="75" height="75"></rect>
|
||||
<g id="Group" transform="translate(6.562500, 8.164062)" fill="#C4C8D1" fill-rule="nonzero">
|
||||
<polygon id="Rectangle" transform="translate(49.192016, 45.302553) rotate(-45.000000) translate(-49.192016, -45.302553) " points="44.5045606 32.0526802 53.8794707 32.0526802 53.8794707 58.5524261 44.5045606 58.5524261"></polygon>
|
||||
<path d="M48.125,23.0859375 C54.15625,23.0859375 59.0625,18.1796875 59.0625,12.1484375 C59.0625,10.3359375 58.5625,8.6484375 57.78125,7.1484375 L49.34375,15.5859375 L44.6875,10.9296875 L53.125,2.4921875 C51.625,1.7109375 49.9375,1.2109375 48.125,1.2109375 C42.09375,1.2109375 37.1875,6.1171875 37.1875,12.1484375 C37.1875,13.4296875 37.4375,14.6484375 37.84375,15.7734375 L32.0625,21.5546875 L26.5,15.9921875 L28.71875,13.7734375 L24.3125,9.3671875 L30.9375,2.7421875 C27.28125,-0.9140625 21.34375,-0.9140625 17.6875,2.7421875 L6.625,13.8046875 L11.03125,18.2109375 L2.21875,18.2109375 L1.38777878e-15,20.4296875 L11.0625,31.4921875 L13.28125,29.2734375 L13.28125,20.4296875 L17.6875,24.8359375 L19.90625,22.6171875 L25.46875,28.1796875 L2.3125,51.3359375 L8.9375,57.9609375 L44.5,22.4296875 C45.625,22.8359375 46.84375,23.0859375 48.125,23.0859375 Z" id="Path"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<svg
|
||||
width="75px"
|
||||
height="75px"
|
||||
viewBox="0 0 75 75"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<title>no-nodes-keyart</title>
|
||||
<g
|
||||
id="Nodes-panel-prototype-V2.1"
|
||||
stroke="none"
|
||||
stroke-width="1"
|
||||
fill="none"
|
||||
fill-rule="evenodd"
|
||||
>
|
||||
<g id="nodes-panel-(component)" transform="translate(-2085.000000, -352.000000)">
|
||||
<g id="nodes_panel" transform="translate(1880.000000, 151.000000)">
|
||||
<g id="Panel" transform="translate(50.000000, 0.000000)">
|
||||
<g id="Group-3" transform="translate(105.000000, 171.000000)">
|
||||
<g id="no-nodes-keyart" transform="translate(50.000000, 30.000000)">
|
||||
<rect id="Rectangle" x="0" y="0" width="75" height="75"></rect>
|
||||
<g
|
||||
id="Group"
|
||||
transform="translate(6.562500, 8.164062)"
|
||||
fill="#C4C8D1"
|
||||
fill-rule="nonzero"
|
||||
>
|
||||
<polygon
|
||||
id="Rectangle"
|
||||
transform="translate(49.192016, 45.302553) rotate(-45.000000) translate(-49.192016, -45.302553) "
|
||||
points="44.5045606 32.0526802 53.8794707 32.0526802 53.8794707 58.5524261 44.5045606 58.5524261"
|
||||
></polygon>
|
||||
<path
|
||||
d="M48.125,23.0859375 C54.15625,23.0859375 59.0625,18.1796875 59.0625,12.1484375 C59.0625,10.3359375 58.5625,8.6484375 57.78125,7.1484375 L49.34375,15.5859375 L44.6875,10.9296875 L53.125,2.4921875 C51.625,1.7109375 49.9375,1.2109375 48.125,1.2109375 C42.09375,1.2109375 37.1875,6.1171875 37.1875,12.1484375 C37.1875,13.4296875 37.4375,14.6484375 37.84375,15.7734375 L32.0625,21.5546875 L26.5,15.9921875 L28.71875,13.7734375 L24.3125,9.3671875 L30.9375,2.7421875 C27.28125,-0.9140625 21.34375,-0.9140625 17.6875,2.7421875 L6.625,13.8046875 L11.03125,18.2109375 L2.21875,18.2109375 L1.38777878e-15,20.4296875 L11.0625,31.4921875 L13.28125,29.2734375 L13.28125,20.4296875 L17.6875,24.8359375 L19.90625,22.6171875 L25.46875,28.1796875 L2.3125,51.3359375 L8.9375,57.9609375 L44.5,22.4296875 C45.625,22.8359375 46.84375,23.0859375 48.125,23.0859375 Z"
|
||||
id="Path"
|
||||
></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
<template>
|
||||
<div>
|
||||
<aside :class="{'node-creator-scrim': true, active: nodeCreatorStore.showScrim}" />
|
||||
<aside :class="{ 'node-creator-scrim': true, active: nodeCreatorStore.showScrim }" />
|
||||
|
||||
<slide-transition>
|
||||
<div
|
||||
v-if="active"
|
||||
class="node-creator"
|
||||
ref="nodeCreator"
|
||||
v-click-outside="onClickOutside"
|
||||
@dragover="onDragOver"
|
||||
@drop="onDrop"
|
||||
v-click-outside="onClickOutside"
|
||||
@dragover="onDragOver"
|
||||
@drop="onDrop"
|
||||
@mousedown="onMouseDown"
|
||||
@mouseup="onMouseUp"
|
||||
data-test-id="node-creator"
|
||||
>
|
||||
<main-panel
|
||||
@nodeTypeSelected="$listeners.nodeTypeSelected"
|
||||
:searchItems="searchItems"
|
||||
/>
|
||||
<main-panel @nodeTypeSelected="$listeners.nodeTypeSelected" :searchItems="searchItems" />
|
||||
</div>
|
||||
</slide-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import { computed, watch, reactive, toRefs } from 'vue';
|
||||
|
||||
import { INodeCreateElement } from '@/Interface';
|
||||
@@ -41,7 +37,7 @@ export interface Props {
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'closeNodeCreator'): void,
|
||||
(event: 'closeNodeCreator'): void;
|
||||
}>();
|
||||
|
||||
const nodeCreatorStore = useNodeCreatorStore();
|
||||
@@ -72,13 +68,13 @@ const searchItems = computed<INodeCreateElement[]>(() => {
|
||||
}));
|
||||
});
|
||||
|
||||
function onClickOutside (event: Event) {
|
||||
function onClickOutside(event: Event) {
|
||||
// We need to prevent cases where user would click inside the node creator
|
||||
// and try to drag undraggable element. In that case the click event would
|
||||
// be fired and the node creator would be closed. So we stop that if we detect
|
||||
// that the click event originated from inside the node creator. And fire click even on the
|
||||
// original target.
|
||||
if(state.mousedownInsideEvent) {
|
||||
if (state.mousedownInsideEvent) {
|
||||
const clickEvent = new MouseEvent('click', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
@@ -86,7 +82,7 @@ function onClickOutside (event: Event) {
|
||||
state.mousedownInsideEvent.target?.dispatchEvent(clickEvent);
|
||||
state.mousedownInsideEvent = null;
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
if (event.type === 'click') {
|
||||
emit('closeNodeCreator');
|
||||
@@ -110,20 +106,29 @@ function onDrop(event: DragEvent) {
|
||||
const nodeCreatorBoundingRect = (state.nodeCreator as Element).getBoundingClientRect();
|
||||
|
||||
// Abort drag end event propagation if dropped inside nodes panel
|
||||
if (nodeTypeName && event.pageX >= nodeCreatorBoundingRect.x && event.pageY >= nodeCreatorBoundingRect.y) {
|
||||
if (
|
||||
nodeTypeName &&
|
||||
event.pageX >= nodeCreatorBoundingRect.x &&
|
||||
event.pageY >= nodeCreatorBoundingRect.y
|
||||
) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.active, (isActive) => {
|
||||
if(isActive === false) nodeCreatorStore.setShowScrim(false);
|
||||
});
|
||||
watch(
|
||||
() => props.active,
|
||||
(isActive) => {
|
||||
if (isActive === false) nodeCreatorStore.setShowScrim(false);
|
||||
},
|
||||
);
|
||||
|
||||
const { nodeCreator } = toRefs(state);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
::v-deep *, *:before, *:after {
|
||||
::v-deep *,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,17 +18,20 @@
|
||||
<template #tooltip v-if="isCommunityNode">
|
||||
<p
|
||||
:class="$style.communityNodeIcon"
|
||||
v-html="$locale.baseText('generic.communityNode.tooltip', { interpolate: { packageName: nodeType.name.split('.')[0], docURL: COMMUNITY_NODES_INSTALLATION_DOCS_URL } })"
|
||||
v-html="
|
||||
$locale.baseText('generic.communityNode.tooltip', {
|
||||
interpolate: {
|
||||
packageName: nodeType.name.split('.')[0],
|
||||
docURL: COMMUNITY_NODES_INSTALLATION_DOCS_URL,
|
||||
},
|
||||
})
|
||||
"
|
||||
@click="onCommunityNodeTooltipClick"
|
||||
/>
|
||||
</template>
|
||||
<template #dragContent>
|
||||
<div :class="$style.draggableDataTransfer" ref="draggableDataTransfer"/>
|
||||
<div
|
||||
:class="$style.draggable"
|
||||
:style="draggableStyle"
|
||||
v-show="dragging"
|
||||
>
|
||||
<div :class="$style.draggableDataTransfer" ref="draggableDataTransfer" />
|
||||
<div :class="$style.draggable" :style="draggableStyle" v-show="dragging">
|
||||
<node-icon :nodeType="nodeType" @click.capture.stop :size="40" :shrink="false" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -58,10 +61,10 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'dragstart', $e: DragEvent): void,
|
||||
(event: 'dragend', $e: DragEvent): void,
|
||||
(event: 'nodeTypeSelected', value: string[]): void,
|
||||
(event: 'actionsOpen', value: INodeTypeDescription): void,
|
||||
(event: 'dragstart', $e: DragEvent): void;
|
||||
(event: 'dragend', $e: DragEvent): void;
|
||||
(event: 'nodeTypeSelected', value: string[]): void;
|
||||
(event: 'actionsOpen', value: INodeTypeDescription): void;
|
||||
}>();
|
||||
|
||||
const instance = getCurrentInstance();
|
||||
@@ -83,9 +86,11 @@ const showActionArrow = computed(() => props.allowActions && hasActions.value);
|
||||
|
||||
const hasActions = computed<boolean>(() => (props.nodeType.actions?.length || 0) > 0);
|
||||
|
||||
const shortNodeType = computed<string>(() => instance?.proxy.$locale.shortNodeType(props.nodeType.name) || '');
|
||||
const shortNodeType = computed<string>(
|
||||
() => instance?.proxy.$locale.shortNodeType(props.nodeType.name) || '',
|
||||
);
|
||||
|
||||
const draggableStyle = computed<{ top: string; left: string; }>(() => ({
|
||||
const draggableStyle = computed<{ top: string; left: string }>(() => ({
|
||||
top: `${state.draggablePosition.y}px`,
|
||||
left: `${state.draggablePosition.x}px`,
|
||||
}));
|
||||
@@ -101,10 +106,12 @@ const displayName = computed<any>(() => {
|
||||
});
|
||||
});
|
||||
|
||||
const isTriggerNode = computed<boolean>(() => props.nodeType.displayName.toLowerCase().includes('trigger'));
|
||||
const isTriggerNode = computed<boolean>(() =>
|
||||
props.nodeType.displayName.toLowerCase().includes('trigger'),
|
||||
);
|
||||
|
||||
function onClick() {
|
||||
if(hasActions.value && props.allowActions) emit('actionsOpen', props.nodeType);
|
||||
if (hasActions.value && props.allowActions) emit('actionsOpen', props.nodeType);
|
||||
else emit('nodeTypeSelected', [props.nodeType.name]);
|
||||
}
|
||||
function onDragStart(event: DragEvent): void {
|
||||
@@ -113,13 +120,13 @@ function onDragStart(event: DragEvent): void {
|
||||
* All browsers attach the correct page coordinates to the "dragover" event.
|
||||
* @bug https://bugzilla.mozilla.org/show_bug.cgi?id=505521
|
||||
*/
|
||||
document.body.addEventListener("dragover", onDragOver);
|
||||
document.body.addEventListener('dragover', onDragOver);
|
||||
|
||||
const { pageX: x, pageY: y } = event;
|
||||
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.effectAllowed = "copy";
|
||||
event.dataTransfer.dropEffect = "copy";
|
||||
event.dataTransfer.effectAllowed = 'copy';
|
||||
event.dataTransfer.dropEffect = 'copy';
|
||||
event.dataTransfer.setDragImage(state.draggableDataTransfer as Element, 0, 0);
|
||||
event.dataTransfer.setData(
|
||||
'nodeTypeName',
|
||||
@@ -133,17 +140,17 @@ function onDragStart(event: DragEvent): void {
|
||||
}
|
||||
|
||||
function onDragOver(event: DragEvent): void {
|
||||
if (!state.dragging || event.pageX === 0 && event.pageY === 0) {
|
||||
if (!state.dragging || (event.pageX === 0 && event.pageY === 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [x,y] = getNewNodePosition([], [event.pageX - NODE_SIZE / 2, event.pageY - NODE_SIZE / 2]);
|
||||
const [x, y] = getNewNodePosition([], [event.pageX - NODE_SIZE / 2, event.pageY - NODE_SIZE / 2]);
|
||||
|
||||
state.draggablePosition = { x, y };
|
||||
}
|
||||
|
||||
function onDragEnd(event: DragEvent): void {
|
||||
document.body.removeEventListener("dragover", onDragOver);
|
||||
document.body.removeEventListener('dragover', onDragOver);
|
||||
|
||||
emit('dragend', event);
|
||||
|
||||
@@ -159,7 +166,6 @@ function onCommunityNodeTooltipClick(event: MouseEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
defineExpose({
|
||||
onClick,
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Vue, { onMounted, reactive, toRefs, onBeforeUnmount } from 'vue';
|
||||
import Vue, { onMounted, reactive, toRefs, onBeforeUnmount } from 'vue';
|
||||
import { externalHooks } from '@/mixins/externalHooks';
|
||||
|
||||
export interface Props {
|
||||
@@ -39,7 +39,7 @@ withDefaults(defineProps<Props>(), {
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'input', value: string): void,
|
||||
(event: 'input', value: string): void;
|
||||
}>();
|
||||
|
||||
const { $externalHooks } = new externalHooks();
|
||||
@@ -54,11 +54,11 @@ function focus() {
|
||||
|
||||
function onInput(event: Event) {
|
||||
const input = event.target as HTMLInputElement;
|
||||
emit("input", input.value);
|
||||
emit('input', input.value);
|
||||
}
|
||||
|
||||
function clear() {
|
||||
emit("input", "");
|
||||
emit('input', '');
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -91,7 +91,7 @@ defineExpose({
|
||||
border-radius: 4px;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--color-secondary)
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div :class="{[$style.subcategory]: true, [$style.subcategoryWithIcon]: hasIcon}">
|
||||
<div :class="{ [$style.subcategory]: true, [$style.subcategoryWithIcon]: hasIcon }">
|
||||
<node-icon v-if="hasIcon" :class="$style.subcategoryIcon" :nodeType="itemProperties" />
|
||||
<div :class="$style.details">
|
||||
<div :class="$style.title">
|
||||
@@ -32,7 +32,7 @@ export default Vue.extend({
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
itemProperties() : ISubcategoryItemProps {
|
||||
itemProperties(): ISubcategoryItemProps {
|
||||
return this.item.properties as ISubcategoryItemProps;
|
||||
},
|
||||
subcategoryName(): string {
|
||||
@@ -45,7 +45,6 @@ export default Vue.extend({
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" module>
|
||||
.subcategoryIcon {
|
||||
min-width: 26px;
|
||||
@@ -95,5 +94,4 @@ export default Vue.extend({
|
||||
width: 12px;
|
||||
color: $node-creator-arrow-color;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -23,13 +23,17 @@
|
||||
@actionsOpen="setActiveActionsNodeType"
|
||||
@actionSelected="onActionSelected"
|
||||
>
|
||||
|
||||
<template #noResultsTitle v-if="isActionsActive">
|
||||
<i />
|
||||
</template>
|
||||
<template #noResultsAction v-if="isActionsActive">
|
||||
<p v-if="containsAPIAction" v-html="getCustomAPICallHintLocale('apiCallNoResult')" class="clickable" @click.stop="addHttpNode" />
|
||||
<p v-else v-text="$locale.baseText('nodeCreator.noResults.noMatchingActions')"/>
|
||||
<template #noResultsAction v-if="isActionsActive">
|
||||
<p
|
||||
v-if="containsAPIAction"
|
||||
v-html="getCustomAPICallHintLocale('apiCallNoResult')"
|
||||
class="clickable"
|
||||
@click.stop="addHttpNode"
|
||||
/>
|
||||
<p v-else v-text="$locale.baseText('nodeCreator.noResults.noMatchingActions')" />
|
||||
</template>
|
||||
|
||||
<template #header>
|
||||
@@ -40,9 +44,13 @@
|
||||
:class="$style.title"
|
||||
/>
|
||||
</template>
|
||||
<template #footer v-if="(activeNodeActions && containsAPIAction)">
|
||||
<template #footer v-if="activeNodeActions && containsAPIAction">
|
||||
<slot name="footer" />
|
||||
<span v-html="getCustomAPICallHintLocale('apiCall')" class="clickable" @click.stop="addHttpNode" />
|
||||
<span
|
||||
v-html="getCustomAPICallHintLocale('apiCall')"
|
||||
class="clickable"
|
||||
@click.stop="addHttpNode"
|
||||
/>
|
||||
</template>
|
||||
</categorized-items>
|
||||
</div>
|
||||
@@ -50,116 +58,156 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, toRefs, getCurrentInstance, computed, onMounted, ref } from 'vue';
|
||||
import { INodeTypeDescription, INodeActionTypeDescription, INodeTypeNameVersion } from 'n8n-workflow';
|
||||
import { INodeCreateElement, IActionItemProps, SubcategoryCreateElement, IUpdateInformation } from '@/Interface';
|
||||
import { CORE_NODES_CATEGORY, WEBHOOK_NODE_TYPE, OTHER_TRIGGER_NODES_SUBCATEGORY, EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE, MANUAL_TRIGGER_NODE_TYPE, SCHEDULE_TRIGGER_NODE_TYPE, EMAIL_IMAP_NODE_TYPE, CUSTOM_API_CALL_NAME, HTTP_REQUEST_NODE_TYPE, STICKY_NODE_TYPE } from '@/constants';
|
||||
import {
|
||||
INodeTypeDescription,
|
||||
INodeActionTypeDescription,
|
||||
INodeTypeNameVersion,
|
||||
} from 'n8n-workflow';
|
||||
import {
|
||||
INodeCreateElement,
|
||||
IActionItemProps,
|
||||
SubcategoryCreateElement,
|
||||
IUpdateInformation,
|
||||
} from '@/Interface';
|
||||
import {
|
||||
CORE_NODES_CATEGORY,
|
||||
WEBHOOK_NODE_TYPE,
|
||||
OTHER_TRIGGER_NODES_SUBCATEGORY,
|
||||
EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE,
|
||||
MANUAL_TRIGGER_NODE_TYPE,
|
||||
SCHEDULE_TRIGGER_NODE_TYPE,
|
||||
EMAIL_IMAP_NODE_TYPE,
|
||||
CUSTOM_API_CALL_NAME,
|
||||
HTTP_REQUEST_NODE_TYPE,
|
||||
STICKY_NODE_TYPE,
|
||||
} from '@/constants';
|
||||
import CategorizedItems from './CategorizedItems.vue';
|
||||
import { useNodeCreatorStore } from '@/stores/nodeCreator';
|
||||
import { getCategoriesWithNodes, getCategorizedList } from "@/utils";
|
||||
import { getCategoriesWithNodes, getCategorizedList } from '@/utils';
|
||||
import { externalHooks } from '@/mixins/externalHooks';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes';
|
||||
import { BaseTextKey } from '@/plugins/i18n';
|
||||
|
||||
const instance = getCurrentInstance();
|
||||
const items: INodeCreateElement[] = [{
|
||||
key: "*",
|
||||
type: "subcategory",
|
||||
const items: INodeCreateElement[] = [
|
||||
{
|
||||
key: '*',
|
||||
type: 'subcategory',
|
||||
title: instance?.proxy.$locale.baseText('nodeCreator.subcategoryNames.appTriggerNodes'),
|
||||
properties: {
|
||||
subcategory: "App Trigger Nodes",
|
||||
description: instance?.proxy.$locale.baseText('nodeCreator.subcategoryDescriptions.appTriggerNodes'),
|
||||
icon: "fa:satellite-dish",
|
||||
subcategory: 'App Trigger Nodes',
|
||||
description: instance?.proxy.$locale.baseText(
|
||||
'nodeCreator.subcategoryDescriptions.appTriggerNodes',
|
||||
),
|
||||
icon: 'fa:satellite-dish',
|
||||
defaults: {
|
||||
color: "#7D838F",
|
||||
color: '#7D838F',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: SCHEDULE_TRIGGER_NODE_TYPE,
|
||||
type: "node",
|
||||
type: 'node',
|
||||
properties: {
|
||||
nodeType: {
|
||||
|
||||
group: [],
|
||||
name: SCHEDULE_TRIGGER_NODE_TYPE,
|
||||
displayName: instance?.proxy.$locale.baseText('nodeCreator.triggerHelperPanel.scheduleTriggerDisplayName'),
|
||||
description: instance?.proxy.$locale.baseText('nodeCreator.triggerHelperPanel.scheduleTriggerDescription'),
|
||||
icon: "fa:clock",
|
||||
displayName: instance?.proxy.$locale.baseText(
|
||||
'nodeCreator.triggerHelperPanel.scheduleTriggerDisplayName',
|
||||
),
|
||||
description: instance?.proxy.$locale.baseText(
|
||||
'nodeCreator.triggerHelperPanel.scheduleTriggerDescription',
|
||||
),
|
||||
icon: 'fa:clock',
|
||||
defaults: {
|
||||
color: "#7D838F",
|
||||
color: '#7D838F',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: WEBHOOK_NODE_TYPE,
|
||||
type: "node",
|
||||
type: 'node',
|
||||
properties: {
|
||||
nodeType: {
|
||||
group: [],
|
||||
name: WEBHOOK_NODE_TYPE,
|
||||
displayName: instance?.proxy.$locale.baseText('nodeCreator.triggerHelperPanel.webhookTriggerDisplayName'),
|
||||
description: instance?.proxy.$locale.baseText('nodeCreator.triggerHelperPanel.webhookTriggerDescription'),
|
||||
displayName: instance?.proxy.$locale.baseText(
|
||||
'nodeCreator.triggerHelperPanel.webhookTriggerDisplayName',
|
||||
),
|
||||
description: instance?.proxy.$locale.baseText(
|
||||
'nodeCreator.triggerHelperPanel.webhookTriggerDescription',
|
||||
),
|
||||
iconData: {
|
||||
type: "file",
|
||||
icon: "webhook",
|
||||
fileBuffer: "/static/webhook-icon.svg",
|
||||
type: 'file',
|
||||
icon: 'webhook',
|
||||
fileBuffer: '/static/webhook-icon.svg',
|
||||
},
|
||||
defaults: {
|
||||
color: "#7D838F",
|
||||
color: '#7D838F',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: MANUAL_TRIGGER_NODE_TYPE,
|
||||
type: "node",
|
||||
type: 'node',
|
||||
properties: {
|
||||
nodeType: {
|
||||
group: [],
|
||||
name: MANUAL_TRIGGER_NODE_TYPE,
|
||||
displayName: instance?.proxy.$locale.baseText('nodeCreator.triggerHelperPanel.manualTriggerDisplayName'),
|
||||
description: instance?.proxy.$locale.baseText('nodeCreator.triggerHelperPanel.manualTriggerDescription'),
|
||||
icon: "fa:mouse-pointer",
|
||||
displayName: instance?.proxy.$locale.baseText(
|
||||
'nodeCreator.triggerHelperPanel.manualTriggerDisplayName',
|
||||
),
|
||||
description: instance?.proxy.$locale.baseText(
|
||||
'nodeCreator.triggerHelperPanel.manualTriggerDescription',
|
||||
),
|
||||
icon: 'fa:mouse-pointer',
|
||||
defaults: {
|
||||
color: "#7D838F",
|
||||
color: '#7D838F',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE,
|
||||
type: "node",
|
||||
type: 'node',
|
||||
properties: {
|
||||
nodeType: {
|
||||
group: [],
|
||||
name: EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE,
|
||||
displayName: instance?.proxy.$locale.baseText('nodeCreator.triggerHelperPanel.workflowTriggerDisplayName'),
|
||||
description: instance?.proxy.$locale.baseText('nodeCreator.triggerHelperPanel.workflowTriggerDescription'),
|
||||
icon: "fa:sign-out-alt",
|
||||
displayName: instance?.proxy.$locale.baseText(
|
||||
'nodeCreator.triggerHelperPanel.workflowTriggerDisplayName',
|
||||
),
|
||||
description: instance?.proxy.$locale.baseText(
|
||||
'nodeCreator.triggerHelperPanel.workflowTriggerDescription',
|
||||
),
|
||||
icon: 'fa:sign-out-alt',
|
||||
defaults: {
|
||||
color: "#7D838F",
|
||||
color: '#7D838F',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "subcategory",
|
||||
type: 'subcategory',
|
||||
key: OTHER_TRIGGER_NODES_SUBCATEGORY,
|
||||
category: CORE_NODES_CATEGORY,
|
||||
properties: {
|
||||
subcategory: OTHER_TRIGGER_NODES_SUBCATEGORY,
|
||||
description: instance?.proxy.$locale.baseText('nodeCreator.subcategoryDescriptions.otherTriggerNodes'),
|
||||
icon: "fa:folder-open",
|
||||
description: instance?.proxy.$locale.baseText(
|
||||
'nodeCreator.subcategoryDescriptions.otherTriggerNodes',
|
||||
),
|
||||
icon: 'fa:folder-open',
|
||||
defaults: {
|
||||
color: "#7D838F",
|
||||
color: '#7D838F',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const emit = defineEmits({
|
||||
"nodeTypeSelected": (nodeTypes: string[]) => true,
|
||||
nodeTypeSelected: (nodeTypes: string[]) => true,
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
@@ -181,12 +229,19 @@ const {
|
||||
|
||||
const telemetry = instance?.proxy.$telemetry;
|
||||
const { categorizedItems: allNodes, isTriggerNode } = useNodeTypesStore();
|
||||
const containsAPIAction = computed(() => state.latestNodeData?.properties.some((p) => p.options?.find((o) => o.name === CUSTOM_API_CALL_NAME)) === true);
|
||||
const containsAPIAction = computed(
|
||||
() =>
|
||||
state.latestNodeData?.properties.some((p) =>
|
||||
p.options?.find((o) => o.name === CUSTOM_API_CALL_NAME),
|
||||
) === true,
|
||||
);
|
||||
|
||||
const computedCategorizedItems = computed(() => getCategorizedList(computedCategoriesWithNodes.value, true));
|
||||
const computedCategorizedItems = computed(() =>
|
||||
getCategorizedList(computedCategoriesWithNodes.value, true),
|
||||
);
|
||||
|
||||
const nodeAppSubcategory = computed<(SubcategoryCreateElement | undefined)>(() => {
|
||||
if(!state.activeNodeActions) return undefined;
|
||||
const nodeAppSubcategory = computed<SubcategoryCreateElement | undefined>(() => {
|
||||
if (!state.activeNodeActions) return undefined;
|
||||
|
||||
return {
|
||||
type: 'subcategory',
|
||||
@@ -205,45 +260,49 @@ const searchPlaceholder = computed(() => {
|
||||
const nodeNameTitle = state.activeNodeActions?.displayName?.trim() as string;
|
||||
const actionsSearchPlaceholder = instance?.proxy.$locale.baseText(
|
||||
'nodeCreator.actionsCategory.searchActions',
|
||||
{ interpolate: { nodeNameTitle }},
|
||||
{ interpolate: { nodeNameTitle } },
|
||||
);
|
||||
|
||||
return isActionsActive.value ? actionsSearchPlaceholder : undefined;
|
||||
});
|
||||
|
||||
const filteredMergedAppNodes = computed(() => {
|
||||
const WHITELISTED_APP_CORE_NODES = [
|
||||
EMAIL_IMAP_NODE_TYPE,
|
||||
WEBHOOK_NODE_TYPE,
|
||||
];
|
||||
const WHITELISTED_APP_CORE_NODES = [EMAIL_IMAP_NODE_TYPE, WEBHOOK_NODE_TYPE];
|
||||
|
||||
if(isAppEventSubcategory.value) return mergedAppNodes.filter(node => {
|
||||
const isRegularNode = !isTriggerNode(node.name);
|
||||
const isStickyNode = node.name === STICKY_NODE_TYPE;
|
||||
const isCoreNode = node.codex?.categories?.includes(CORE_NODES_CATEGORY) && !WHITELISTED_APP_CORE_NODES.includes(node.name);
|
||||
const hasActions = (node.actions || []).length > 0;
|
||||
if (isAppEventSubcategory.value)
|
||||
return mergedAppNodes.filter((node) => {
|
||||
const isRegularNode = !isTriggerNode(node.name);
|
||||
const isStickyNode = node.name === STICKY_NODE_TYPE;
|
||||
const isCoreNode =
|
||||
node.codex?.categories?.includes(CORE_NODES_CATEGORY) &&
|
||||
!WHITELISTED_APP_CORE_NODES.includes(node.name);
|
||||
const hasActions = (node.actions || []).length > 0;
|
||||
|
||||
if(isRegularNode && !hasActions) return false;
|
||||
return !isCoreNode && !isStickyNode;
|
||||
});
|
||||
if (isRegularNode && !hasActions) return false;
|
||||
return !isCoreNode && !isStickyNode;
|
||||
});
|
||||
|
||||
return mergedAppNodes;
|
||||
});
|
||||
|
||||
const computedCategoriesWithNodes = computed(() => {
|
||||
if(!state.activeNodeActions) return getCategoriesWithNodes(filteredMergedAppNodes.value, []);
|
||||
if (!state.activeNodeActions) return getCategoriesWithNodes(filteredMergedAppNodes.value, []);
|
||||
|
||||
return getCategoriesWithNodes(selectedNodeActions.value, [], state.activeNodeActions.displayName) ;
|
||||
return getCategoriesWithNodes(selectedNodeActions.value, [], state.activeNodeActions.displayName);
|
||||
});
|
||||
|
||||
const selectedNodeActions = computed<INodeActionTypeDescription[]>(() => state.activeNodeActions?.actions ?? []);
|
||||
const isAppEventSubcategory = computed(() => state.selectedSubcategory === "*");
|
||||
const selectedNodeActions = computed<INodeActionTypeDescription[]>(
|
||||
() => state.activeNodeActions?.actions ?? [],
|
||||
);
|
||||
const isAppEventSubcategory = computed(() => state.selectedSubcategory === '*');
|
||||
const isActionsActive = computed(() => state.activeNodeActions !== null);
|
||||
const firstLevelItems = computed(() => isRoot.value ? items : []);
|
||||
const firstLevelItems = computed(() => (isRoot.value ? items : []));
|
||||
|
||||
const isSearchActive = computed(() => useNodeCreatorStore().itemsFilter !== '');
|
||||
const searchItems = computed<INodeCreateElement[]>(() => {
|
||||
const sorted = state.activeNodeActions ? [...selectedNodeActions.value] : [...filteredMergedAppNodes.value];
|
||||
const sorted = state.activeNodeActions
|
||||
? [...selectedNodeActions.value]
|
||||
: [...filteredMergedAppNodes.value];
|
||||
sorted.sort((a, b) => {
|
||||
const textA = a.displayName.toLowerCase();
|
||||
const textB = b.displayName.toLowerCase();
|
||||
@@ -264,21 +323,23 @@ const searchItems = computed<INodeCreateElement[]>(() => {
|
||||
});
|
||||
|
||||
function onNodeTypeSelected(nodeTypes: string[]) {
|
||||
emit("nodeTypeSelected", nodeTypes.length === 1 ? getNodeTypesWithManualTrigger(nodeTypes[0]) : nodeTypes);
|
||||
emit(
|
||||
'nodeTypeSelected',
|
||||
nodeTypes.length === 1 ? getNodeTypesWithManualTrigger(nodeTypes[0]) : nodeTypes,
|
||||
);
|
||||
}
|
||||
function getCustomAPICallHintLocale(key: string) {
|
||||
if(!state.activeNodeActions) return '';
|
||||
if (!state.activeNodeActions) return '';
|
||||
|
||||
const nodeNameTitle = state.activeNodeActions.displayName;
|
||||
return instance?.proxy.$locale.baseText(
|
||||
`nodeCreator.actionsList.${key}` as BaseTextKey,
|
||||
{ interpolate: { nodeNameTitle }},
|
||||
);
|
||||
const nodeNameTitle = state.activeNodeActions.displayName;
|
||||
return instance?.proxy.$locale.baseText(`nodeCreator.actionsList.${key}` as BaseTextKey, {
|
||||
interpolate: { nodeNameTitle },
|
||||
});
|
||||
}
|
||||
// The nodes.json doesn't contain API CALL option so we need to fetch the node detail
|
||||
// to determine if need to render the API CALL hint
|
||||
async function fetchNodeDetails() {
|
||||
if(!state.activeNodeActions) return;
|
||||
if (!state.activeNodeActions) return;
|
||||
|
||||
const { getNodesInformation } = useNodeTypesStore();
|
||||
const { version, name } = state.activeNodeActions;
|
||||
@@ -297,7 +358,7 @@ function setActiveActionsNodeType(nodeType: INodeTypeDescription | null) {
|
||||
setShowTabs(false);
|
||||
fetchNodeDetails();
|
||||
|
||||
if(nodeType) trackActionsView();
|
||||
if (nodeType) trackActionsView();
|
||||
}
|
||||
|
||||
function onActionSelected(actionCreateElement: INodeCreateElement) {
|
||||
@@ -311,7 +372,7 @@ function addHttpNode() {
|
||||
name: '',
|
||||
key: HTTP_REQUEST_NODE_TYPE,
|
||||
value: {
|
||||
authentication: "predefinedCredentialType",
|
||||
authentication: 'predefinedCredentialType',
|
||||
},
|
||||
} as IUpdateInformation;
|
||||
|
||||
@@ -328,28 +389,30 @@ function onSubcategorySelected(subcategory: INodeCreateElement) {
|
||||
state.selectedSubcategory = subcategory.key;
|
||||
}
|
||||
function onSubcategoryClose(activeSubcategories: INodeCreateElement[]) {
|
||||
if(isActionsActive.value === true) setActiveActionsNodeType(null);
|
||||
if (isActionsActive.value === true) setActiveActionsNodeType(null);
|
||||
|
||||
state.isRoot = activeSubcategories.length === 0;
|
||||
state.selectedSubcategory = activeSubcategories[activeSubcategories.length - 1]?.key ?? '';
|
||||
}
|
||||
|
||||
function shouldShowNodeActions(node: INodeCreateElement) {
|
||||
if(isAppEventSubcategory.value) return true;
|
||||
if(state.isRoot && !isSearchActive.value) return false;
|
||||
if (isAppEventSubcategory.value) return true;
|
||||
if (state.isRoot && !isSearchActive.value) return false;
|
||||
// Do not show actions for core category when searching
|
||||
if(node.type === 'node') return !node.properties.nodeType.codex?.categories?.includes(CORE_NODES_CATEGORY);
|
||||
if (node.type === 'node')
|
||||
return !node.properties.nodeType.codex?.categories?.includes(CORE_NODES_CATEGORY);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function trackActionsView() {
|
||||
const trigger_action_count = selectedNodeActions.value
|
||||
.filter((action) => action.name.toLowerCase().includes('trigger')).length;
|
||||
const trigger_action_count = selectedNodeActions.value.filter((action) =>
|
||||
action.name.toLowerCase().includes('trigger'),
|
||||
).length;
|
||||
|
||||
const trackingPayload = {
|
||||
app_identifier: state.activeNodeActions?.name,
|
||||
actions: selectedNodeActions.value.map(action => action.displayName),
|
||||
actions: selectedNodeActions.value.map((action) => action.displayName),
|
||||
regular_action_count: selectedNodeActions.value.length - trigger_action_count,
|
||||
trigger_action_count,
|
||||
};
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
<template>
|
||||
<div class="type-selector" v-if="nodeCreatorStore.showTabs" data-test-id="node-creator-type-selector">
|
||||
<el-tabs stretch :value="nodeCreatorStore.selectedType" @input="nodeCreatorStore.setSelectedType">
|
||||
<el-tab-pane :label="$locale.baseText('nodeCreator.mainPanel.all')" :name="ALL_NODE_FILTER"></el-tab-pane>
|
||||
<el-tab-pane :label="$locale.baseText('nodeCreator.mainPanel.regular')" :name="REGULAR_NODE_FILTER"></el-tab-pane>
|
||||
<el-tab-pane :label="$locale.baseText('nodeCreator.mainPanel.trigger')" :name="TRIGGER_NODE_FILTER"></el-tab-pane>
|
||||
<div
|
||||
class="type-selector"
|
||||
v-if="nodeCreatorStore.showTabs"
|
||||
data-test-id="node-creator-type-selector"
|
||||
>
|
||||
<el-tabs
|
||||
stretch
|
||||
:value="nodeCreatorStore.selectedType"
|
||||
@input="nodeCreatorStore.setSelectedType"
|
||||
>
|
||||
<el-tab-pane
|
||||
:label="$locale.baseText('nodeCreator.mainPanel.all')"
|
||||
:name="ALL_NODE_FILTER"
|
||||
></el-tab-pane>
|
||||
<el-tab-pane
|
||||
:label="$locale.baseText('nodeCreator.mainPanel.regular')"
|
||||
:name="REGULAR_NODE_FILTER"
|
||||
></el-tab-pane>
|
||||
<el-tab-pane
|
||||
:label="$locale.baseText('nodeCreator.mainPanel.trigger')"
|
||||
:name="TRIGGER_NODE_FILTER"
|
||||
></el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user