mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
fix(editor): NDV in focus panel review batch 2 (no-changelog) (#19463)
This commit is contained in:
@@ -104,8 +104,9 @@ defineExpose({
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
|
||||
/* Override break-all set for el-popper */
|
||||
/* Override styles set for el-popper */
|
||||
word-break: normal;
|
||||
text-align: unset;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
|
||||
@@ -477,21 +477,23 @@ function handleChangeCollapsingColumn(columnName: string | null) {
|
||||
v-if="(isActiveNodeConfig && rootNode) || parentNodes.length"
|
||||
:class="$style.noOutputData"
|
||||
>
|
||||
<N8nText v-if="nodeNotRunMessageVariant === 'simple'" color="text-base" size="small">
|
||||
<I18nT scope="global" keypath="ndv.input.noOutputData.embeddedNdv.description">
|
||||
<template #link>
|
||||
<NodeExecuteButton
|
||||
:class="$style.executeButton"
|
||||
size="medium"
|
||||
:node-name="nodeNameToExecute"
|
||||
:label="i18n.baseText('ndv.input.noOutputData.embeddedNdv.link')"
|
||||
text
|
||||
telemetry-source="inputs"
|
||||
hide-icon
|
||||
/>
|
||||
</template>
|
||||
</I18nT>
|
||||
</N8nText>
|
||||
<NDVEmptyState v-if="nodeNotRunMessageVariant === 'simple'">
|
||||
<template #description>
|
||||
<I18nT scope="global" keypath="ndv.input.noOutputData.embeddedNdv.description">
|
||||
<template #link>
|
||||
<NodeExecuteButton
|
||||
:class="$style.executeButton"
|
||||
size="large"
|
||||
:node-name="nodeNameToExecute"
|
||||
:label="i18n.baseText('ndv.input.noOutputData.embeddedNdv.link')"
|
||||
text
|
||||
telemetry-source="inputs"
|
||||
hide-icon
|
||||
/>
|
||||
</template>
|
||||
</I18nT>
|
||||
</template>
|
||||
</NDVEmptyState>
|
||||
|
||||
<template v-else-if="isNDVV2">
|
||||
<NDVEmptyState
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{ title: string }>();
|
||||
defineProps<{ title?: string }>();
|
||||
|
||||
defineSlots<{
|
||||
icon(): unknown;
|
||||
@@ -10,7 +10,7 @@ defineSlots<{
|
||||
<template>
|
||||
<article :class="$style.empty">
|
||||
<slot name="icon" />
|
||||
<h1 :class="$style.title">{{ title }}</h1>
|
||||
<h1 v-if="title" :class="$style.title">{{ title }}</h1>
|
||||
<p :class="$style.description"><slot name="description" /></p>
|
||||
</article>
|
||||
</template>
|
||||
@@ -36,7 +36,8 @@ defineSlots<{
|
||||
|
||||
.description {
|
||||
font-size: var(--font-size-s);
|
||||
max-width: 180px;
|
||||
max-width: 240px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -675,7 +675,7 @@ describe('ParameterInput.vue', () => {
|
||||
inputNode: { name: 'n1', runIndex: 0, branchIndex: 0 },
|
||||
});
|
||||
|
||||
it('should render mapper', async () => {
|
||||
it('should render mapper when the current value is empty', async () => {
|
||||
const rendered = renderComponent({
|
||||
global: { provide: { [ExpressionLocalResolveContextSymbol]: ctx } },
|
||||
props: {
|
||||
@@ -688,6 +688,37 @@ describe('ParameterInput.vue', () => {
|
||||
expect(rendered.queryByTestId('ndv-input-panel')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render mapper when editor type is specified in the parameter', async () => {
|
||||
const rendered = renderComponent({
|
||||
global: { provide: { [ExpressionLocalResolveContextSymbol]: ctx } },
|
||||
props: {
|
||||
path: 'name',
|
||||
parameter: {
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
typeOptions: { editor: 'sqlEditor' },
|
||||
},
|
||||
modelValue: 'SELECT 1;',
|
||||
},
|
||||
});
|
||||
|
||||
expect(rendered.queryByTestId('ndv-input-panel')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render mapper when the current value is an expression', async () => {
|
||||
const rendered = renderComponent({
|
||||
global: { provide: { [ExpressionLocalResolveContextSymbol]: ctx } },
|
||||
props: {
|
||||
path: 'name',
|
||||
parameter: { displayName: 'Name', name: 'name', type: 'string' },
|
||||
modelValue: '={{$today}}',
|
||||
},
|
||||
});
|
||||
|
||||
expect(rendered.queryByTestId('ndv-input-panel')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render mapper if given node property is a node setting', async () => {
|
||||
const rendered = renderComponent({
|
||||
global: { provide: { [ExpressionLocalResolveContextSymbol]: ctx } },
|
||||
|
||||
@@ -277,7 +277,7 @@ const modelValueExpressionEdit = computed<NodeParameterValueType>(() => {
|
||||
|
||||
const editorRows = computed(() => getTypeOption<number>('rows'));
|
||||
|
||||
const editorType = computed<EditorType | 'json' | 'code' | 'cssEditor'>(() => {
|
||||
const editorType = computed<EditorType | 'json' | 'code' | 'cssEditor' | undefined>(() => {
|
||||
return getTypeOption<EditorType>('editor');
|
||||
});
|
||||
const editorIsReadOnly = computed<boolean>(() => {
|
||||
@@ -636,7 +636,8 @@ const isMapperAvailable = computed(
|
||||
!props.parameter.isNodeSetting &&
|
||||
(isModelValueExpression.value ||
|
||||
props.forceShowExpression ||
|
||||
(isEmpty(props.modelValue) && props.parameter.type !== 'dateTime')),
|
||||
(isEmpty(props.modelValue) && props.parameter.type !== 'dateTime') ||
|
||||
editorType.value !== undefined),
|
||||
);
|
||||
|
||||
function isRemoteParameterOption(option: INodePropertyOptions) {
|
||||
@@ -825,7 +826,6 @@ async function setFocus() {
|
||||
}
|
||||
|
||||
isFocused.value = true;
|
||||
isMapperShown.value = isMapperAvailable.value;
|
||||
}
|
||||
|
||||
emit('focus');
|
||||
@@ -966,14 +966,23 @@ function expressionUpdated(value: string) {
|
||||
valueChanged(val);
|
||||
}
|
||||
|
||||
function onBlur(event?: FocusEvent | KeyboardEvent) {
|
||||
function onBlur() {
|
||||
emit('blur');
|
||||
isFocused.value = false;
|
||||
}
|
||||
|
||||
function onFocusIn() {
|
||||
if (isMapperAvailable.value) {
|
||||
isMapperShown.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
function onFocusOutOrOutsideClickMapper(event: FocusEvent | MouseEvent) {
|
||||
if (
|
||||
!(event?.target instanceof Node && wrapper.value?.contains(event.target)) &&
|
||||
!(event?.target instanceof Node && mapperElRef.value?.contains(event.target)) &&
|
||||
!(
|
||||
event instanceof FocusEvent &&
|
||||
'relatedTarget' in event &&
|
||||
event.relatedTarget instanceof Node &&
|
||||
mapperElRef.value?.contains(event.relatedTarget)
|
||||
)
|
||||
@@ -1027,12 +1036,6 @@ function onUpdateTextInput(value: string) {
|
||||
|
||||
const onUpdateTextInputDebounced = debounce(onUpdateTextInput, { debounceTime: 200 });
|
||||
|
||||
function onClickOutsideMapper() {
|
||||
if (!isFocused.value) {
|
||||
isMapperShown.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function optionSelected(command: string) {
|
||||
const prevValue = props.modelValue;
|
||||
|
||||
@@ -1247,7 +1250,7 @@ onUpdated(async () => {
|
||||
}
|
||||
});
|
||||
|
||||
onClickOutside(mapperElRef, onClickOutsideMapper);
|
||||
onClickOutside(mapperElRef, onFocusOutOrOutsideClickMapper);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -1290,6 +1293,8 @@ onClickOutside(mapperElRef, onClickOutsideMapper);
|
||||
]"
|
||||
:style="parameterInputWrapperStyle"
|
||||
:data-parameter-path="path"
|
||||
@focusin="onFocusIn"
|
||||
@focusout="onFocusOutOrOutsideClickMapper"
|
||||
>
|
||||
<ResourceLocator
|
||||
v-if="parameter.type === 'resourceLocator'"
|
||||
|
||||
@@ -89,6 +89,7 @@ import { usePostHog } from '@/stores/posthog.store';
|
||||
import { I18nT } from 'vue-i18n';
|
||||
import RunDataBinary from '@/components/RunDataBinary.vue';
|
||||
import { hasTrimmedRunData } from '@/utils/executionUtils';
|
||||
import NDVEmptyState from '@/components/NDVEmptyState.vue';
|
||||
|
||||
const LazyRunDataTable = defineAsyncComponent(
|
||||
async () => await import('@/components/RunDataTable.vue'),
|
||||
@@ -1774,9 +1775,8 @@ defineExpose({ enterEditMode });
|
||||
"
|
||||
:class="$style.center"
|
||||
>
|
||||
<div v-if="search">
|
||||
<N8nText tag="h3" size="large">{{ i18n.baseText('ndv.search.noMatch.title') }}</N8nText>
|
||||
<N8nText>
|
||||
<NDVEmptyState v-if="search" :title="i18n.baseText('ndv.search.noMatch.title')">
|
||||
<template #description>
|
||||
<I18nT keypath="ndv.search.noMatch.description" tag="span" scope="global">
|
||||
<template #link>
|
||||
<a href="#" @click="onSearchClear">
|
||||
@@ -1784,8 +1784,8 @@ defineExpose({ enterEditMode });
|
||||
</a>
|
||||
</template>
|
||||
</I18nT>
|
||||
</N8nText>
|
||||
</div>
|
||||
</template>
|
||||
</NDVEmptyState>
|
||||
<N8nText v-else>
|
||||
{{ noDataInBranchMessage }}
|
||||
</N8nText>
|
||||
@@ -1848,9 +1848,12 @@ defineExpose({ enterEditMode });
|
||||
</N8nText>
|
||||
</div>
|
||||
|
||||
<div v-else-if="showIoSearchNoMatchContent" :class="$style.center">
|
||||
<N8nText tag="h3" size="large">{{ i18n.baseText('ndv.search.noMatch.title') }}</N8nText>
|
||||
<N8nText>
|
||||
<NDVEmptyState
|
||||
v-else-if="showIoSearchNoMatchContent"
|
||||
:class="$style.center"
|
||||
:title="i18n.baseText('ndv.search.noMatch.title')"
|
||||
>
|
||||
<template #description>
|
||||
<I18nT keypath="ndv.search.noMatch.description" tag="span" scope="global">
|
||||
<template #link>
|
||||
<a href="#" @click="onSearchClear">
|
||||
@@ -1858,8 +1861,8 @@ defineExpose({ enterEditMode });
|
||||
</a>
|
||||
</template>
|
||||
</I18nT>
|
||||
</N8nText>
|
||||
</div>
|
||||
</template>
|
||||
</NDVEmptyState>
|
||||
|
||||
<Suspense v-else-if="hasNodeRun && displayMode === 'table' && node">
|
||||
<LazyRunDataTable
|
||||
|
||||
@@ -47,6 +47,7 @@ import { DateTime } from 'luxon';
|
||||
import NodeExecuteButton from './NodeExecuteButton.vue';
|
||||
import { I18nT } from 'vue-i18n';
|
||||
import { useTelemetryContext } from '@/composables/useTelemetryContext';
|
||||
import NDVEmptyState from '@/components/NDVEmptyState.vue';
|
||||
|
||||
type Props = {
|
||||
nodes?: IConnectedNode[];
|
||||
@@ -430,10 +431,15 @@ const onDragEnd = (el: HTMLElement) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="['run-data-schema', 'full-height', props.compact ? 'compact' : '']">
|
||||
<div v-if="noSearchResults" class="no-results">
|
||||
<N8nText tag="h3" size="large">{{ i18n.baseText('ndv.search.noNodeMatch.title') }}</N8nText>
|
||||
<N8nText>
|
||||
<div
|
||||
:class="[
|
||||
'run-data-schema',
|
||||
'full-height',
|
||||
{ compact: props.compact, 'no-search-results': noSearchResults },
|
||||
]"
|
||||
>
|
||||
<NDVEmptyState v-if="noSearchResults" :title="i18n.baseText('ndv.search.noNodeMatch.title')">
|
||||
<template #description>
|
||||
<I18nT keypath="ndv.search.noMatchSchema.description" tag="span" scope="global">
|
||||
<template #link>
|
||||
<a href="#" @click="emit('clear:search')">
|
||||
@@ -441,8 +447,8 @@ const onDragEnd = (el: HTMLElement) => {
|
||||
</a>
|
||||
</template>
|
||||
</I18nT>
|
||||
</N8nText>
|
||||
</div>
|
||||
</template>
|
||||
</NDVEmptyState>
|
||||
|
||||
<Draggable
|
||||
v-if="items.length"
|
||||
@@ -552,6 +558,12 @@ const onDragEnd = (el: HTMLElement) => {
|
||||
|
||||
.run-data-schema {
|
||||
padding: 0;
|
||||
|
||||
&.no-search-results {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-l) 0;
|
||||
}
|
||||
}
|
||||
|
||||
.scroller {
|
||||
@@ -563,17 +575,6 @@ const onDragEnd = (el: HTMLElement) => {
|
||||
}
|
||||
}
|
||||
|
||||
.no-results {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
gap: var(--spacing-2xs);
|
||||
padding: var(--ndv-spacing) var(--ndv-spacing) var(--spacing-xl) var(--ndv-spacing);
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-flex;
|
||||
margin-left: var(--spacing-xl);
|
||||
|
||||
@@ -106,8 +106,9 @@ defineExpose({
|
||||
box-shadow: none !important;
|
||||
margin-top: -2px;
|
||||
|
||||
/* Override break-all set for el-popper */
|
||||
/* Override styles set for el-popper */
|
||||
word-break: normal;
|
||||
text-align: unset;
|
||||
}
|
||||
|
||||
.inputPanel {
|
||||
|
||||
@@ -146,6 +146,7 @@ import { useAITemplatesStarterCollectionStore } from '@/experiments/aiTemplatesS
|
||||
import { useReadyToRunWorkflowsStore } from '@/experiments/readyToRunWorkflows/stores/readyToRunWorkflows.store';
|
||||
import { useKeybindings } from '@/composables/useKeybindings';
|
||||
import { type ContextMenuAction } from '@/composables/useContextMenuItems';
|
||||
import { useExperimentalNdvStore } from '@/components/canvas/experimental/experimentalNdv.store';
|
||||
|
||||
defineOptions({
|
||||
name: 'NodeView',
|
||||
@@ -208,6 +209,7 @@ const agentRequestStore = useAgentRequestStore();
|
||||
const logsStore = useLogsStore();
|
||||
const aiTemplatesStarterCollectionStore = useAITemplatesStarterCollectionStore();
|
||||
const readyToRunWorkflowsStore = useReadyToRunWorkflowsStore();
|
||||
const experimentalNdvStore = useExperimentalNdvStore();
|
||||
|
||||
const { addBeforeUnloadEventBindings, removeBeforeUnloadEventBindings } = useBeforeUnload({
|
||||
route,
|
||||
@@ -2217,7 +2219,9 @@ onBeforeUnmount(() => {
|
||||
</Suspense>
|
||||
</WorkflowCanvas>
|
||||
<FocusPanel
|
||||
v-if="!isLoading"
|
||||
v-if="
|
||||
!isLoading && (experimentalNdvStore.isNdvInFocusPanelEnabled ? !isCanvasReadOnly : true)
|
||||
"
|
||||
:is-canvas-read-only="isCanvasReadOnly"
|
||||
@save-keyboard-shortcut="onSaveWorkflow"
|
||||
@context-menu-action="onContextMenuAction"
|
||||
|
||||
Reference in New Issue
Block a user