mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
feat(editor): Action/credentials tab in node settings in zoomed view (no-changelog) (#17730)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
143
packages/frontend/editor-ui/src/components/NodeActionsList.vue
Normal file
143
packages/frontend/editor-ui/src/components/NodeActionsList.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<script setup lang="ts">
|
||||
import { useActions } from '@/components/Node/NodeCreator/composables/useActions';
|
||||
import { useActionsGenerator } from '@/components/Node/NodeCreator/composables/useActionsGeneration';
|
||||
import { CUSTOM_API_CALL_KEY } from '@/constants';
|
||||
import type { ActionCreateElement, INodeCreateElement, INodeUi } from '@/Interface';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { N8nIcon, N8nText } from '@n8n/design-system';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { type INodeParameters } from 'n8n-workflow';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
const { node } = defineProps<{
|
||||
node: INodeUi;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
actionSelected: [INodeParameters];
|
||||
}>();
|
||||
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
const { generateMergedNodesAndActions } = useActionsGenerator();
|
||||
const { parseCategoryActions, getActionData } = useActions();
|
||||
const i18n = useI18n();
|
||||
|
||||
const selectedActionRef = ref<HTMLElement>();
|
||||
|
||||
const nodeType = computed(() => nodeTypesStore.getNodeType(node.type, node.typeVersion));
|
||||
const options = computed(() => {
|
||||
const { actions } = generateMergedNodesAndActions(nodeType.value ? [nodeType.value] : [], []);
|
||||
|
||||
return parseCategoryActions(
|
||||
Object.values(actions).flatMap((typeDescriptions) =>
|
||||
typeDescriptions
|
||||
.filter(({ actionKey }) => actionKey !== CUSTOM_API_CALL_KEY)
|
||||
.map<ActionCreateElement>((typeDescription) => ({
|
||||
type: 'action',
|
||||
subcategory: typeDescription.actionKey,
|
||||
key: typeDescription.actionKey,
|
||||
properties: typeDescription,
|
||||
})),
|
||||
),
|
||||
i18n.baseText('nodeCreator.actionsCategory.actions'),
|
||||
true,
|
||||
).map((action) => {
|
||||
if (action.type !== 'action') {
|
||||
return { action, isSelected: false };
|
||||
}
|
||||
|
||||
const data = getActionData(action.properties).value;
|
||||
let isSelected = true;
|
||||
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
isSelected = isSelected && node.parameters[key] === value;
|
||||
}
|
||||
|
||||
return { action, isSelected };
|
||||
});
|
||||
});
|
||||
|
||||
function handleClickOption(option: INodeCreateElement) {
|
||||
if (option.type !== 'action') {
|
||||
return;
|
||||
}
|
||||
|
||||
emit('actionSelected', getActionData(option.properties).value);
|
||||
}
|
||||
|
||||
function handleSelectedItemRef(el: unknown) {
|
||||
if (el instanceof HTMLDivElement) {
|
||||
selectedActionRef.value = el;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
selectedActionRef,
|
||||
(selected) => {
|
||||
selected?.scrollIntoView();
|
||||
},
|
||||
{ flush: 'post' },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="$style.component">
|
||||
<template v-for="option in options" :key="option.action.key">
|
||||
<N8nText
|
||||
v-if="option.action.type === 'label'"
|
||||
tag="div"
|
||||
:class="$style.label"
|
||||
size="xsmall"
|
||||
color="text-base"
|
||||
bold
|
||||
>
|
||||
{{ option.action.key }}
|
||||
</N8nText>
|
||||
<div
|
||||
v-else-if="option.action.type === 'action'"
|
||||
:ref="option.isSelected ? handleSelectedItemRef : undefined"
|
||||
:class="{
|
||||
[$style.option]: true,
|
||||
[$style.selected]: option.isSelected,
|
||||
}"
|
||||
role="button"
|
||||
@click="handleClickOption(option.action)"
|
||||
>
|
||||
<NodeIcon :size="20" :node-type="nodeType" />
|
||||
<N8nText size="small" bold :class="$style.optionText">{{
|
||||
option.action.properties.displayName
|
||||
}}</N8nText>
|
||||
<N8nIcon v-if="option.isSelected" icon="check" color="primary" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.component {
|
||||
padding-block: var(--spacing-2xs);
|
||||
}
|
||||
|
||||
.label {
|
||||
padding: var(--spacing-3xs) var(--spacing-s);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--spacing-3xs) var(--spacing-s);
|
||||
gap: var(--spacing-2xs);
|
||||
cursor: pointer;
|
||||
|
||||
&.selected,
|
||||
&:hover {
|
||||
background-color: var(--color-background-base);
|
||||
}
|
||||
}
|
||||
|
||||
.optionText {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user