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