mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-10 12:35:46 +03:00
fix(editor): Fix i18n issues (#3072)
* 🐛 Fix `defaultLocale` watcher * ⚡ Improve error handling for headers * ✏️ Improve naming * 🐛 Fix hiring banner check * ⚡ Flatten base text keys * ⚡ Fix miscorrected key * ⚡ Implement pluralization * ✏️ Update docs * 🚚 Move headers fetching to `App.vue` * fix hiring banner * ⚡ Fix missing import * ✏️ Alphabetize translations * ⚡ Switch to async check * feat(editor): Refactor Output Panel + fix i18n issues (#3097) * update main panel * finish up tabs * fix docs link * add icon * update node settings * clean up settings * add rename modal * fix component styles * fix spacing * truncate name * remove mixin * fix spacing * fix spacing * hide docs url * fix bug * fix renaming * refactor tabs out * refactor execute button * refactor header * add more views * fix error view * fix workflow rename bug * rename component * fix small screen bug * move items, fix positions * add hover state * show selector on empty state * add empty run state * fix binary view * 1 item * add vjs styles * show empty row for every item * refactor tabs * add branch names * fix spacing * fix up spacing * add run selector * fix positioning * clean up * increase width of selector * fix up spacing * fix copy button * fix branch naming; type issues * fix docs in custom nodes * add type * hide items when run selector is shown * increase selector size * add select prepend * clean up a bit * Add pagination * add stale icon * enable stale data in execution run * Revert "enable stale data in execution run"8edb68dbff
* move metadata to its own state * fix smaller size * add scroll buttons * update tabs on resize * update stale data on rename * remove metadata on delete * hide x * change title colors * binary data classes * remove duplicate css * add colors * delete unused keys * use event bus * update styles of pagination * fix ts issues * fix ts issues * use chevron icons * fix design with download button * add back to canvas button * add trigger warning disabled * show trigger warning tooltip * update button labels for triggers * update node output message * fix add-option bug * add page selector * fix pagination selector bug * fix executions bug * remove hint * add json colors * add colors for json * add color json keys * fix select options bug * update keys * address comments * update name limit * align pencil * update icon size * update radio buttons height * address comments * fix pencil bug * change buttons alignment * fully center * change order of buttons * add no output message in branch * scroll to top * change active state * fix page size * all items * update expression background * update naming * align pencil * update modal background * add schedule group * update schedule nodes messages * use ellpises for last chars * fix spacing * fix tabs issue * fix too far data bug * fix executions bug * fix table wrapping * fix rename bug * add padding * handle unkown errors * add sticky header * ignore empty input, trim node name * nudge lightness of color * center buttons * update pagination * set colors of title * increase table font, fix alignment * fix pencil bug * fix spacing * use date now * address pagination issues * delete unused keys * update keys sort * fix prepend * fix radio button position * Revert "fix radio button position"ae42781786
Co-authored-by: Mutasem <mutdmour@gmail.com> Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com>
This commit is contained in:
parent
94a52b9358
commit
4ae0f5b6fb
@ -30,7 +30,7 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
|
||||
import express from 'express';
|
||||
import { readFileSync } from 'fs';
|
||||
import { readFileSync, promises } from 'fs';
|
||||
import { readFile } from 'fs/promises';
|
||||
import _, { cloneDeep } from 'lodash';
|
||||
import { dirname as pathDirname, join as pathJoin, resolve as pathResolve } from 'path';
|
||||
@ -1503,10 +1503,17 @@ class App {
|
||||
async (req: express.Request, res: express.Response): Promise<object | void> => {
|
||||
const packagesPath = pathJoin(__dirname, '..', '..', '..');
|
||||
const headersPath = pathJoin(packagesPath, 'nodes-base', 'dist', 'nodes', 'headers');
|
||||
|
||||
try {
|
||||
await promises.access(`${headersPath}.js`);
|
||||
} catch (_) {
|
||||
return; // no headers available
|
||||
}
|
||||
|
||||
try {
|
||||
return require(headersPath);
|
||||
} catch (error) {
|
||||
res.status(500).send('Failed to find headers file');
|
||||
res.status(500).send('Failed to load headers file');
|
||||
}
|
||||
},
|
||||
),
|
||||
|
@ -2,6 +2,7 @@
|
||||
<component
|
||||
:is="$options.components.N8nText"
|
||||
:size="props.size"
|
||||
:color="props.color"
|
||||
:compact="true"
|
||||
>
|
||||
<component
|
||||
@ -36,6 +37,8 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
color: {
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -11,7 +11,13 @@ const Template = (args, { argTypes }) => ({
|
||||
N8nInfoTip,
|
||||
},
|
||||
template:
|
||||
'<n8n-info-tip>Need help doing something? <a href="/docs" target="_blank">Open docs</a></n8n-info-tip>',
|
||||
'<n8n-info-tip v-bind="$props">Need help doing something? <a href="/docs" target="_blank">Open docs</a></n8n-info-tip>',
|
||||
});
|
||||
|
||||
export const InputLabel = Template.bind({});
|
||||
export const Note = Template.bind({});
|
||||
|
||||
export const Tooltip = Template.bind({});
|
||||
Tooltip.args = {
|
||||
type: 'tooltip',
|
||||
tooltipPlacement: 'right',
|
||||
};
|
||||
|
@ -1,23 +1,48 @@
|
||||
<template functional>
|
||||
<div :class="$style.infotip">
|
||||
<component :is="$options.components.N8nIcon" icon="info-circle" /> <span><slot></slot></span>
|
||||
<template>
|
||||
<div :class="[$style[theme], $style[type]]">
|
||||
<n8n-tooltip :placement="tooltipPlacement" :popper-class="$style.tooltipPopper" :disabled="type !== 'tooltip'">
|
||||
<span>
|
||||
<n8n-icon :icon="theme.startsWith('info') ? 'info-circle': 'exclamation-triangle'" />
|
||||
<span v-if="type === 'note'"><slot></slot></span>
|
||||
</span>
|
||||
<span v-if="type === 'tooltip'" slot="content"><slot></slot></span>
|
||||
</n8n-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import N8nIcon from '../N8nIcon';
|
||||
import N8nTooltip from '../N8nTooltip';
|
||||
|
||||
export default {
|
||||
name: 'n8n-info-tip',
|
||||
components: {
|
||||
N8nIcon,
|
||||
N8nTooltip,
|
||||
},
|
||||
props: {
|
||||
theme: {
|
||||
type: String,
|
||||
default: 'info',
|
||||
validator: (value: string): boolean =>
|
||||
['info', 'info-light', 'warning', 'danger'].includes(value),
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'note',
|
||||
validator: (value: string): boolean =>
|
||||
['note', 'tooltip'].includes(value),
|
||||
},
|
||||
tooltipPlacement: {
|
||||
type: String,
|
||||
default: 'top',
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.infotip {
|
||||
color: var(--color-text-light);
|
||||
.base {
|
||||
font-size: var(--font-size-2xs);
|
||||
font-weight: var(--font-weight-bold);
|
||||
line-height: var(--font-size-s);
|
||||
@ -27,7 +52,35 @@ export default {
|
||||
|
||||
svg {
|
||||
font-size: var(--font-size-s);
|
||||
}
|
||||
}
|
||||
|
||||
.note {
|
||||
composes: base;
|
||||
|
||||
svg {
|
||||
margin-right: var(--spacing-4xs);
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
composes: base;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.info-light {
|
||||
color: var(--color-foreground-dark);
|
||||
}
|
||||
|
||||
.info {
|
||||
color: var(--color-text-light);
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
.danger {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
</style>
|
||||
|
@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<label role="radio" tabindex="-1" :class="$style.container" aria-checked="true">
|
||||
<input type="radio" tabindex="-1" autocomplete="off" :class="$style.input" :value="value">
|
||||
<div :class="{[$style.button]: true, [$style.active]: active}" @click="$emit('click')">{{ label }}</div>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'n8n-radio-button',
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.container {
|
||||
display: inline-block;
|
||||
outline: 0;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
.button:not(.active) {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
opacity: 0;
|
||||
outline: 0;
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.button {
|
||||
border-radius: 0;
|
||||
padding: 0 var(--spacing-s);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 26px;
|
||||
font-size: var(--font-size-2xs);
|
||||
border-radius: var(--border-radius-base);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-base);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: var(--color-foreground-xlight);
|
||||
color: var(--color-text-dark);
|
||||
}
|
||||
</style>
|
@ -0,0 +1,51 @@
|
||||
import N8nRadioButtons from './RadioButtons.vue';
|
||||
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
export default {
|
||||
title: 'Atoms/RadioButtons',
|
||||
component: N8nRadioButtons,
|
||||
argTypes: {
|
||||
},
|
||||
parameters: {
|
||||
backgrounds: { default: '--color-background-xlight' },
|
||||
},
|
||||
};
|
||||
|
||||
const methods = {
|
||||
onInput: action('input'),
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nRadioButtons,
|
||||
},
|
||||
template:
|
||||
`<n8n-radio-buttons v-model="val" v-bind="$props" @input="onInput">
|
||||
</n8n-radio-buttons>`,
|
||||
methods,
|
||||
data() {
|
||||
return {
|
||||
val: '',
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const Example = Template.bind({});
|
||||
Example.args = {
|
||||
options: [
|
||||
{
|
||||
label: 'Test',
|
||||
value: 'test',
|
||||
},
|
||||
{
|
||||
label: 'World',
|
||||
value: 'world',
|
||||
},
|
||||
{
|
||||
label: 'Hello',
|
||||
value: 'hello',
|
||||
},
|
||||
],
|
||||
};
|
@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div role="radiogroup" :class="$style.radioGroup">
|
||||
<RadioButton
|
||||
v-for="option in options"
|
||||
:key="option.value"
|
||||
v-bind="option"
|
||||
:active="value === option.value"
|
||||
@click="(e) => onClick(option.value, e)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import RadioButton from './RadioButton';
|
||||
|
||||
export default {
|
||||
name: 'n8n-radio-buttons',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
},
|
||||
options: {
|
||||
},
|
||||
},
|
||||
components: {
|
||||
RadioButton,
|
||||
},
|
||||
methods: {
|
||||
onClick(value) {
|
||||
this.$emit('input', value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
||||
.radioGroup {
|
||||
display: inline-flex;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
font-size: 0;
|
||||
background-color: var(--color-foreground-base);
|
||||
padding: var(--spacing-5xs);
|
||||
border-radius: var(--border-radius-base);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@ -0,0 +1,3 @@
|
||||
import N8nRadioButtons from './RadioButtons.vue';
|
||||
|
||||
export default N8nRadioButtons;
|
@ -1,24 +1,29 @@
|
||||
<template functional>
|
||||
<component
|
||||
:is="$options.components.ElSelect"
|
||||
v-bind="props"
|
||||
:value="props.value"
|
||||
:size="$options.methods.getSize(props.size)"
|
||||
:class="$style[$options.methods.getClass(props)]"
|
||||
:popper-class="$options.methods.getPopperClass(props, $style)"
|
||||
v-on="listeners"
|
||||
:ref="data.ref"
|
||||
>
|
||||
<template v-slot:prefix>
|
||||
<slot name="prefix" />
|
||||
</template>
|
||||
<template v-slot:suffix>
|
||||
<slot name="suffix" />
|
||||
</template>
|
||||
<template v-slot:default>
|
||||
<slot></slot>
|
||||
</template>
|
||||
</component>
|
||||
<div :class="{[$style.container]: true, [$style.withPrepend]: !!$slots.prepend}">
|
||||
<div v-if="$slots.prepend" :class="$style.prepend">
|
||||
<slot name="prepend" />
|
||||
</div>
|
||||
<component
|
||||
:is="$options.components.ElSelect"
|
||||
v-bind="props"
|
||||
:value="props.value"
|
||||
:size="$options.methods.getSize(props.size)"
|
||||
:class="$style[$options.methods.getClass(props)]"
|
||||
:popper-class="$options.methods.getPopperClass(props, $style)"
|
||||
v-on="listeners"
|
||||
:ref="data.ref"
|
||||
>
|
||||
<template v-slot:prefix>
|
||||
<slot name="prefix" />
|
||||
</template>
|
||||
<template v-slot:suffix>
|
||||
<slot name="suffix" />
|
||||
</template>
|
||||
<template v-slot:default>
|
||||
<slot></slot>
|
||||
</template>
|
||||
</component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@ -121,4 +126,30 @@ export default {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.withPrepend {
|
||||
input {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.prepend {
|
||||
font-size: var(--font-size-2xs);
|
||||
border: var(--border-base);
|
||||
border-right: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 var(--spacing-3xs);
|
||||
background-color: var(--color-background-light);
|
||||
border-bottom-left-radius: var(--input-border-radius, var(--border-radius-base));
|
||||
border-top-left-radius: var(--input-border-radius, var(--border-radius-base));
|
||||
color: var(--color-text-base);
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
@ -0,0 +1,54 @@
|
||||
import N8nTabs from './Tabs.vue';
|
||||
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
export default {
|
||||
title: 'Atoms/Tabs',
|
||||
component: N8nTabs,
|
||||
argTypes: {
|
||||
},
|
||||
parameters: {
|
||||
backgrounds: { default: '--color-background-xlight' },
|
||||
},
|
||||
};
|
||||
|
||||
const methods = {
|
||||
onInput: action('input'),
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nTabs,
|
||||
},
|
||||
template:
|
||||
`<n8n-tabs v-model="val" v-bind="$props" @input="onInput">
|
||||
</n8n-tabs>`,
|
||||
methods,
|
||||
data() {
|
||||
return {
|
||||
val: '',
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const Example = Template.bind({});
|
||||
Example.args = {
|
||||
options: [
|
||||
{
|
||||
label: 'Test',
|
||||
value: 'test',
|
||||
},
|
||||
{
|
||||
label: 'Github',
|
||||
value: 'github',
|
||||
href: 'https://github.com/',
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
value: 'settings',
|
||||
icon: 'cog',
|
||||
align: 'right',
|
||||
},
|
||||
],
|
||||
};
|
192
packages/design-system/src/components/N8nTabs/Tabs.vue
Normal file
192
packages/design-system/src/components/N8nTabs/Tabs.vue
Normal file
@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<div :class="$style.container">
|
||||
<div :class="$style.back" v-if="scrollPosition > 0" @click="scrollLeft">
|
||||
<n8n-icon icon="chevron-left" size="small" />
|
||||
</div>
|
||||
<div :class="$style.next" v-if="canScrollRight" @click="scrollRight">
|
||||
<n8n-icon icon="chevron-right" size="small" />
|
||||
</div>
|
||||
<div ref="tabs" :class="$style.tabs">
|
||||
<div v-for="option in options" :key="option.value" :class="{ [$style.alignRight]: option.align === 'right' }">
|
||||
<a
|
||||
v-if="option.href"
|
||||
target="_blank"
|
||||
:href="option.href"
|
||||
:class="[$style.link, $style.tab]"
|
||||
@click="handleTabClick"
|
||||
>
|
||||
<div>
|
||||
{{ option.label }}
|
||||
<span :class="$style.external"><n8n-icon icon="external-link-alt" size="small" /></span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div
|
||||
v-else
|
||||
:class="{ [$style.tab]: true, [$style.activeTab]: value === option.value }"
|
||||
@click="() => handleTabClick(option.value)"
|
||||
>
|
||||
<n8n-icon v-if="option.icon" :icon="option.icon" size="medium" />
|
||||
<span v-if="option.label">{{ option.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import N8nIcon from '../N8nIcon';
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'N8nTabs',
|
||||
components: {
|
||||
N8nIcon,
|
||||
},
|
||||
mounted() {
|
||||
const container = this.$refs.tabs;
|
||||
if (container) {
|
||||
container.addEventListener('scroll', (e) => {
|
||||
const width = container.clientWidth;
|
||||
const scrollWidth = container.scrollWidth;
|
||||
this.scrollPosition = e.srcElement.scrollLeft;
|
||||
this.canScrollRight = scrollWidth - width > this.scrollPosition;
|
||||
});
|
||||
|
||||
this.resizeObserver = new ResizeObserver(() => {
|
||||
const width = container.clientWidth;
|
||||
const scrollWidth = container.scrollWidth;
|
||||
this.canScrollRight = scrollWidth - width > this.scrollPosition;
|
||||
});
|
||||
this.resizeObserver.observe(container);
|
||||
|
||||
const width = container.clientWidth;
|
||||
const scrollWidth = container.scrollWidth;
|
||||
this.canScrollRight = scrollWidth - width > this.scrollPosition;
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
this.resizeObserver.disconnect();
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
scrollPosition: 0,
|
||||
canScrollRight: false,
|
||||
resizeObserver: null,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
},
|
||||
options: {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleTabClick(tab: string) {
|
||||
this.$emit('input', tab);
|
||||
},
|
||||
scrollLeft() {
|
||||
this.scroll(-50);
|
||||
},
|
||||
scrollRight() {
|
||||
this.scroll(50);
|
||||
},
|
||||
scroll(left: number) {
|
||||
const container = this.$refs.tabs;
|
||||
if (container) {
|
||||
container.scrollBy({ left, top: 0, behavior: 'smooth' });
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" module>
|
||||
.container {
|
||||
position: relative;
|
||||
height: 24px;
|
||||
min-height: 24px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
color: var(--color-text-base);
|
||||
font-weight: var(--font-weight-bold);
|
||||
display: flex;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
overflow-x: scroll;
|
||||
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.tab {
|
||||
display: block;
|
||||
padding: 0 var(--spacing-s) var(--spacing-2xs) var(--spacing-s);
|
||||
padding-bottom: var(--spacing-2xs);
|
||||
font-size: var(--font-size-s);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.activeTab {
|
||||
color: var(--color-primary);
|
||||
border-bottom: var(--color-primary) 2px solid;
|
||||
}
|
||||
|
||||
.alignRight {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.link {
|
||||
cursor: pointer;
|
||||
color: var(--color-text-base);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
|
||||
.external {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.external {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.button {
|
||||
position: absolute;
|
||||
background-color: var(--color-background-light);
|
||||
z-index: 1;
|
||||
height: 24px;
|
||||
width: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.back {
|
||||
composes: tab;
|
||||
composes: button;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.next {
|
||||
composes: tab;
|
||||
composes: button;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
</style>
|
3
packages/design-system/src/components/N8nTabs/index.js
Normal file
3
packages/design-system/src/components/N8nTabs/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import N8nTabs from './Tabs.vue';
|
||||
|
||||
export default N8nTabs;
|
@ -12,6 +12,7 @@ import Switch from 'element-ui/lib/switch';
|
||||
import Select from 'element-ui/lib/select';
|
||||
import Option from 'element-ui/lib/option';
|
||||
import OptionGroup from 'element-ui/lib/option-group';
|
||||
import Pagination from 'element-ui/lib/pagination';
|
||||
import ButtonGroup from 'element-ui/lib/button-group';
|
||||
import Table from 'element-ui/lib/table';
|
||||
import TableColumn from 'element-ui/lib/table-column';
|
||||
@ -29,6 +30,7 @@ import Loading from 'element-ui/lib/loading';
|
||||
import MessageBox from 'element-ui/lib/message-box';
|
||||
import Message from 'element-ui/lib/message';
|
||||
import Notification from 'element-ui/lib/notification';
|
||||
import Popover from 'element-ui/lib/popover';
|
||||
import CollapseTransition from 'element-ui/lib/transitions/collapse-transition';
|
||||
|
||||
import N8nActionBox from './N8nActionBox';
|
||||
@ -52,10 +54,12 @@ import N8nMenu from './N8nMenu';
|
||||
import N8nMenuItem from './N8nMenuItem';
|
||||
import N8nLink from './N8nLink';
|
||||
import N8nOption from './N8nOption';
|
||||
import N8nRadioButtons from './N8nRadioButtons';
|
||||
import N8nSelect from './N8nSelect';
|
||||
import N8nSpinner from './N8nSpinner';
|
||||
import N8nSquareButton from './N8nSquareButton';
|
||||
import N8nTags from './N8nTags';
|
||||
import N8nTabs from './N8nTabs';
|
||||
import N8nTag from './N8nTag';
|
||||
import N8nText from './N8nText';
|
||||
import N8nTooltip from './N8nTooltip';
|
||||
@ -86,9 +90,11 @@ export {
|
||||
N8nMenu,
|
||||
N8nMenuItem,
|
||||
N8nOption,
|
||||
N8nRadioButtons,
|
||||
N8nSelect,
|
||||
N8nSpinner,
|
||||
N8nSquareButton,
|
||||
N8nTabs,
|
||||
N8nTags,
|
||||
N8nTag,
|
||||
N8nText,
|
||||
@ -110,6 +116,7 @@ export {
|
||||
Select,
|
||||
Option,
|
||||
OptionGroup,
|
||||
Pagination,
|
||||
ButtonGroup,
|
||||
Table,
|
||||
TableColumn,
|
||||
@ -128,6 +135,7 @@ export {
|
||||
Message,
|
||||
Notification,
|
||||
CollapseTransition,
|
||||
Popover,
|
||||
|
||||
locale,
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
export function addTargetBlank(html: string) {
|
||||
return html.includes('href=')
|
||||
return html && html.includes('href=')
|
||||
? html.replace(/href=/g, 'target="_blank" href=')
|
||||
: html;
|
||||
}
|
||||
|
@ -154,3 +154,15 @@ import ColorCircles from './ColorCircles.vue';
|
||||
}}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
|
||||
<Canvas>
|
||||
<Story name="json">
|
||||
{{
|
||||
template: `<color-circles :colors="['--color-json-default', '--color-json-null', '--color-json-boolean', '--color-json-number', '--color-json-string', '--color-json-key', '--color-json-brackets', '--color-json-brackets-hover', '--color-json-line', '--color-json-highlight']" />`,
|
||||
components: {
|
||||
ColorCircles,
|
||||
},
|
||||
}}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
@ -285,8 +285,8 @@
|
||||
);
|
||||
|
||||
--color-background-light-h: 220;
|
||||
--color-background-light-s: 27.3%;
|
||||
--color-background-light-l: 97.8%;
|
||||
--color-background-light-s: 60%;
|
||||
--color-background-light-l: 99%;
|
||||
--color-background-light: hsl(
|
||||
var(--color-background-light-h),
|
||||
var(--color-background-light-s),
|
||||
@ -329,6 +329,17 @@
|
||||
var(--color-canvas-background-l)
|
||||
);
|
||||
|
||||
--color-json-default: #5045A1;
|
||||
--color-json-null: var(--color-danger);
|
||||
--color-json-boolean: #1d8ce0;
|
||||
--color-json-number: #1d8ce0;
|
||||
--color-json-string: #5045A1;
|
||||
--color-json-key: var(--color-text-dark);
|
||||
--color-json-brackets: var(--color-text-dark);
|
||||
--color-json-brackets-hover: #1890ff;
|
||||
--color-json-line: #bfcbd9;
|
||||
--color-json-highlight: #E2E5EE;
|
||||
|
||||
--border-radius-xlarge: 12px;
|
||||
--border-radius-large: 8px;
|
||||
--border-radius-base: 4px;
|
||||
|
@ -777,7 +777,7 @@ $table-fixed-box-shadow: 0 0 10px rgba(0, 0, 0, 0.12);
|
||||
/* Pagination
|
||||
-------------------------- */
|
||||
/// fontSize||Font|1
|
||||
$pagination-font-size: 13px;
|
||||
$pagination-font-size: var(--font-size-2xs);
|
||||
/// color||Color|0
|
||||
$pagination-background-color: $color-white;
|
||||
/// color||Color|0
|
||||
@ -799,7 +799,7 @@ $pagination-hover-color: var(--color-primary);
|
||||
/* Popup
|
||||
-------------------------- */
|
||||
/// color||Color|0
|
||||
$popup-modal-background-color: hsla(247,14%, 70%, 0.75);
|
||||
$popup-modal-background-color: hsla(247,14%, 30%, 0.75);
|
||||
/// opacity||Other|1
|
||||
$popup-modal-opacity: 0.65;
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
@use "./tokens.dark.scss" as dark;
|
||||
@use "./reset.scss";
|
||||
@use "./base.scss";
|
||||
// @use "./pagination.scss";
|
||||
@use "./pagination.scss";
|
||||
@use "./dialog.scss";
|
||||
// @use "./autocomplete.scss";
|
||||
@use "./dropdown.scss";
|
||||
|
@ -193,11 +193,11 @@
|
||||
.btn-prev,
|
||||
.btn-next,
|
||||
.el-pager li {
|
||||
margin: 0 5px;
|
||||
background-color: var.$color-info-lighter;
|
||||
color: var(--color-text-dark);
|
||||
margin: 0 1px;
|
||||
color: var(--color-text-base);
|
||||
min-width: 30px;
|
||||
border-radius: 2px;
|
||||
border-radius: var(--border-radius-base);
|
||||
border: 1px solid transparent;
|
||||
|
||||
&.disabled {
|
||||
color: var(--color-text-lighter);
|
||||
@ -215,12 +215,14 @@
|
||||
|
||||
.el-pager li:not(.disabled) {
|
||||
&:hover {
|
||||
color: var.$pagination-hover-color;
|
||||
color: var(--color-primary);
|
||||
background-color: var(--color-background-xlight);
|
||||
border: 1px solid var(--color-foreground-base);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--color-primary);
|
||||
color: var.$color-white;
|
||||
border: 1px solid var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,7 +254,6 @@
|
||||
padding: 0 4px;
|
||||
background: var.$pagination-background-color;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
font-size: var.$pagination-font-size;
|
||||
min-width: var.$pagination-button-width;
|
||||
height: var.$pagination-button-height;
|
||||
@ -261,6 +262,9 @@
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.btn-quicknext,
|
||||
&.btn-quickprev {
|
||||
|
@ -28,10 +28,13 @@ import { showMessage } from './components/mixins/showMessage';
|
||||
import { IUser } from './Interface';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { userHelpers } from './components/mixins/userHelpers';
|
||||
import { addHeaders, loadLanguage } from './plugins/i18n';
|
||||
import { restApi } from '@/components/mixins/restApi';
|
||||
|
||||
export default mixins(
|
||||
showMessage,
|
||||
userHelpers,
|
||||
restApi,
|
||||
).extend({
|
||||
name: 'App',
|
||||
components: {
|
||||
@ -42,6 +45,9 @@ export default mixins(
|
||||
computed: {
|
||||
...mapGetters('settings', ['isHiringBannerEnabled', 'isTemplatesEnabled', 'isTemplatesEndpointReachable', 'isUserManagementEnabled', 'showSetupPage']),
|
||||
...mapGetters('users', ['currentUser']),
|
||||
defaultLocale (): string {
|
||||
return this.$store.getters.defaultLocale;
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -79,7 +85,7 @@ export default mixins(
|
||||
}
|
||||
},
|
||||
logHiringBanner() {
|
||||
if (!this.isHiringBannerEnabled && this.$route.name !== VIEWS.DEMO) {
|
||||
if (this.isHiringBannerEnabled && this.$route.name !== VIEWS.DEMO) {
|
||||
console.log(HIRING_BANNER); // eslint-disable-line no-console
|
||||
}
|
||||
},
|
||||
@ -143,8 +149,8 @@ export default mixins(
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
this.logHiringBanner();
|
||||
await this.initialize();
|
||||
this.logHiringBanner();
|
||||
this.authenticate();
|
||||
this.redirectIfNecessary();
|
||||
|
||||
@ -152,6 +158,11 @@ export default mixins(
|
||||
|
||||
this.trackPage();
|
||||
this.$externalHooks().run('app.mount');
|
||||
|
||||
if (this.defaultLocale !== 'en') {
|
||||
const headers = await this.restApi().getNodeTranslationHeaders();
|
||||
if (headers) addHeaders(headers, this.defaultLocale);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route(route) {
|
||||
@ -160,6 +171,9 @@ export default mixins(
|
||||
|
||||
this.trackPage();
|
||||
},
|
||||
defaultLocale(newLocale) {
|
||||
loadLanguage(newLocale);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -746,6 +746,10 @@ export interface ITemplatesNode extends IVersionNode {
|
||||
categories?: ITemplatesCategory[];
|
||||
}
|
||||
|
||||
export interface INodeMetadata {
|
||||
parametersLastUpdatedAt?: number;
|
||||
}
|
||||
|
||||
export interface IRootState {
|
||||
activeExecutions: IExecutionsCurrentSummaryExtended[];
|
||||
activeWorkflows: string[];
|
||||
@ -783,6 +787,7 @@ export interface IRootState {
|
||||
workflow: IWorkflowDb;
|
||||
sidebarMenuItems: IMenuItem[];
|
||||
instanceId: string;
|
||||
nodeMetadata: {[nodeName: string]: INodeMetadata};
|
||||
}
|
||||
|
||||
export interface ICredentialTypeMap {
|
||||
@ -958,3 +963,11 @@ export type IFormBoxConfig = {
|
||||
redirectLink?: string;
|
||||
redirectText?: string;
|
||||
};
|
||||
|
||||
export interface ITab {
|
||||
value: string | number;
|
||||
label?: string;
|
||||
href?: string;
|
||||
icon?: string;
|
||||
align?: 'right';
|
||||
}
|
||||
|
@ -1,40 +1,24 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:visible="!!node"
|
||||
:visible="!!node || renaming"
|
||||
:before-close="close"
|
||||
:custom-class="`classic data-display-wrapper`"
|
||||
:show-close="false"
|
||||
custom-class="data-display-wrapper"
|
||||
width="85%"
|
||||
append-to-body
|
||||
@opened="showDocumentHelp = true"
|
||||
>
|
||||
<div class="data-display" >
|
||||
<NodeSettings @valueChanged="valueChanged" />
|
||||
<RunData />
|
||||
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<div v-if="nodeType && showDocumentHelp" class="doc-help-wrapper">
|
||||
<svg id="help-logo" :href="documentationUrl" target="_blank" width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>{{ $locale.baseText('dataDisplay.nodeDocumentation') }}</title>
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(-1127.000000, -836.000000)" fill-rule="nonzero">
|
||||
<g transform="translate(1117.000000, 825.000000)">
|
||||
<g transform="translate(10.000000, 11.000000)">
|
||||
<g transform="translate(2.250000, 2.250000)" fill="#FF6150">
|
||||
<path d="M6,11.25 L7.5,11.25 L7.5,9.75 L6,9.75 L6,11.25 M6.75,2.25 C5.09314575,2.25 3.75,3.59314575 3.75,5.25 L5.25,5.25 C5.25,4.42157288 5.92157288,3.75 6.75,3.75 C7.57842712,3.75 8.25,4.42157288 8.25,5.25 C8.25,6.75 6,6.5625 6,9 L7.5,9 C7.5,7.3125 9.75,7.125 9.75,5.25 C9.75,3.59314575 8.40685425,2.25 6.75,2.25 M1.5,0 L12,0 C12.8284271,0 13.5,0.671572875 13.5,1.5 L13.5,12 C13.5,12.8284271 12.8284271,13.5 12,13.5 L1.5,13.5 C0.671572875,13.5 0,12.8284271 0,12 L0,1.5 C0,0.671572875 0.671572875,0 1.5,0 Z"></path>
|
||||
</g>
|
||||
<rect x="0" y="0" width="18" height="18"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<div class="text">
|
||||
{{ $locale.baseText('dataDisplay.needHelp') }} <n8n-link size="small" :to="documentationUrl" :bold="true" @click="onDocumentationUrlClick">{{ $locale.baseText('dataDisplay.openDocumentationFor', { interpolate: { nodeTypeDisplayName: nodeType.displayName } }) }}</n8n-link>
|
||||
</div>
|
||||
<n8n-tooltip placement="bottom-start" :value="showTriggerWaitingWarning" :disabled="!showTriggerWaitingWarning" :manual="true">
|
||||
<div slot="content" :class="$style.triggerWarning">{{ $locale.baseText('ndv.backToCanvas.waitingForTriggerWarning') }}</div>
|
||||
<div :class="$style.backToCanvas" @click="close">
|
||||
<n8n-icon icon="arrow-left" color="text-xlight" size="medium" />
|
||||
<n8n-text color="text-xlight" size="medium" :bold="true">{{ $locale.baseText('ndv.backToCanvas') }}</n8n-text>
|
||||
</div>
|
||||
</transition>
|
||||
</n8n-tooltip>
|
||||
|
||||
<div class="data-display" v-if="node" >
|
||||
<NodeSettings :eventBus="settingsEventBus" @valueChanged="valueChanged" @execute="onNodeExecute" />
|
||||
<RunData @openSettings="openSettings" />
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
@ -55,6 +39,8 @@ import NodeSettings from '@/components/NodeSettings.vue';
|
||||
import RunData from '@/components/RunData.vue';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import Vue from 'vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default mixins(externalHooks, nodeHelpers, workflowHelpers).extend({
|
||||
name: 'DataDisplay',
|
||||
@ -62,23 +48,24 @@ export default mixins(externalHooks, nodeHelpers, workflowHelpers).extend({
|
||||
NodeSettings,
|
||||
RunData,
|
||||
},
|
||||
props: {
|
||||
renaming: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
basePath: this.$store.getters.getBaseUrl,
|
||||
showDocumentHelp: false,
|
||||
settingsEventBus: new Vue(),
|
||||
triggerWaitingWarningEnabled: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
documentationUrl (): string {
|
||||
if (!this.nodeType) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (this.nodeType.documentationUrl && this.nodeType.documentationUrl.startsWith('http')) {
|
||||
return this.nodeType.documentationUrl;
|
||||
}
|
||||
|
||||
return 'https://docs.n8n.io/nodes/' + (this.nodeType.documentationUrl || this.nodeType.name) + '?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=' + this.nodeType.name;
|
||||
...mapGetters(['executionWaitingForWebhook']),
|
||||
workflowRunning (): boolean {
|
||||
return this.$store.getters.isActionActive('workflowRunning');
|
||||
},
|
||||
showTriggerWaitingWarning(): boolean {
|
||||
return this.triggerWaitingWarningEnabled && !!this.nodeType && !this.nodeType.group.includes('trigger') && this.workflowRunning && this.executionWaitingForWebhook;
|
||||
},
|
||||
node (): INodeUi {
|
||||
return this.$store.getters.activeNode;
|
||||
@ -93,6 +80,7 @@ export default mixins(externalHooks, nodeHelpers, workflowHelpers).extend({
|
||||
watch: {
|
||||
node (node, oldNode) {
|
||||
if(node && !oldNode) {
|
||||
this.triggerWaitingWarningEnabled = false;
|
||||
this.$externalHooks().run('dataDisplay.nodeTypeChanged', { nodeSubtitle: this.getNodeSubtitle(node, this.nodeType, this.getWorkflow()) });
|
||||
this.$telemetry.track('User opened node modal', { node_type: this.nodeType ? this.nodeType.name : '', workflow_id: this.$store.getters.workflowId });
|
||||
}
|
||||
@ -102,6 +90,17 @@ export default mixins(externalHooks, nodeHelpers, workflowHelpers).extend({
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onNodeExecute() {
|
||||
setTimeout(() => {
|
||||
if (!this.node || !this.workflowRunning) {
|
||||
return;
|
||||
}
|
||||
this.triggerWaitingWarningEnabled = true;
|
||||
}, 1000);
|
||||
},
|
||||
openSettings() {
|
||||
this.settingsEventBus.$emit('openSettings');
|
||||
},
|
||||
valueChanged (parameterData: IUpdateInformation) {
|
||||
this.$emit('valueChanged', parameterData);
|
||||
},
|
||||
@ -110,12 +109,9 @@ export default mixins(externalHooks, nodeHelpers, workflowHelpers).extend({
|
||||
},
|
||||
close () {
|
||||
this.$externalHooks().run('dataDisplay.nodeEditingFinished');
|
||||
this.showDocumentHelp = false;
|
||||
this.triggerWaitingWarningEnabled = false;
|
||||
this.$store.commit('setActiveNode', null);
|
||||
},
|
||||
onDocumentationUrlClick () {
|
||||
this.$externalHooks().run('dataDisplay.onDocumentationUrlClick', { nodeType: this.nodeType, documentationUrl: this.documentationUrl });
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -124,6 +120,7 @@ export default mixins(externalHooks, nodeHelpers, workflowHelpers).extend({
|
||||
<style lang="scss">
|
||||
.data-display-wrapper {
|
||||
height: 85%;
|
||||
margin-top: 48px !important;
|
||||
|
||||
.el-dialog__header {
|
||||
padding: 0 !important;
|
||||
@ -145,41 +142,6 @@ export default mixins(externalHooks, nodeHelpers, workflowHelpers).extend({
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.doc-help-wrapper {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
transition-delay: 2s;
|
||||
background-color: #fff;
|
||||
margin-top: 1%;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #DCDFE6;
|
||||
border-radius: 4px;
|
||||
background-color: #FFFFFF;
|
||||
box-shadow: 0 2px 7px 0 rgba(0,0,0,0.15);
|
||||
min-width: 319px;
|
||||
height: 40px;
|
||||
float: right;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-top: 10px;
|
||||
padding-right: 12px;
|
||||
|
||||
#help-logo {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-left: 5px;
|
||||
flex: 9;
|
||||
font-family: "Open Sans";
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 17px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-enter-active, .fade-enter-to, .fade-leave-active {
|
||||
transition: all .75s ease;
|
||||
opacity: 1;
|
||||
@ -189,3 +151,30 @@ export default mixins(externalHooks, nodeHelpers, workflowHelpers).extend({
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" module>
|
||||
.triggerWarning {
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
.backToCanvas {
|
||||
position: absolute;
|
||||
top: -40px;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
> * {
|
||||
margin-right: var(--spacing-3xs);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: $--breakpoint-lg) {
|
||||
.backToCanvas {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
left: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,128 +0,0 @@
|
||||
<template>
|
||||
<span class="static-text-wrapper">
|
||||
<span v-show="!editActive" :title="$locale.baseText('displayWithChange.clickToChange')">
|
||||
<span class="static-text" @mousedown="startEdit">{{currentValue}}</span>
|
||||
</span>
|
||||
<span v-show="editActive">
|
||||
<input class="edit-field" ref="inputField" type="text" v-model="newValue" @keydown.enter.stop.prevent="setValue" @keydown.escape.stop.prevent="cancelEdit" @keydown.stop="noOp" @blur="cancelEdit" />
|
||||
<font-awesome-icon icon="times" @mousedown="cancelEdit" class="icons clickable" :title="$locale.baseText('displayWithChange.cancelEdit')" />
|
||||
<font-awesome-icon icon="check" @mousedown="setValue" class="icons clickable" :title="$locale.baseText('displayWithChange.setValue')" />
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||
import { INodeUi } from '@/Interface';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
export default mixins(genericHelpers).extend({
|
||||
name: 'DisplayWithChange',
|
||||
props: {
|
||||
keyName: String,
|
||||
},
|
||||
computed: {
|
||||
node (): INodeUi {
|
||||
return this.$store.getters.activeNode;
|
||||
},
|
||||
currentValue (): string {
|
||||
const getDescendantProp = (obj: object, path: string): string => {
|
||||
// @ts-ignore
|
||||
return path.split('.').reduce((acc, part) => acc && acc[part], obj);
|
||||
};
|
||||
|
||||
if (this.keyName === 'name' && this.node.type.startsWith('n8n-nodes-base.')) {
|
||||
const shortNodeType = this.$locale.shortNodeType(this.node.type);
|
||||
|
||||
return this.$locale.headerText({
|
||||
key: `headers.${shortNodeType}.displayName`,
|
||||
fallback: getDescendantProp(this.node, this.keyName),
|
||||
});
|
||||
}
|
||||
|
||||
return getDescendantProp(this.node, this.keyName);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
currentValue (val) {
|
||||
// Deactivate when the data to edit changes
|
||||
// (like when a different node gets selected)
|
||||
this.editActive = false;
|
||||
},
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
editActive: false,
|
||||
newValue: '',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
noOp () {},
|
||||
startEdit () {
|
||||
if (this.isReadOnly === true) {
|
||||
return;
|
||||
}
|
||||
this.editActive = true;
|
||||
this.newValue = this.currentValue;
|
||||
|
||||
setTimeout(() => {
|
||||
(this.$refs.inputField as HTMLInputElement).focus();
|
||||
});
|
||||
},
|
||||
cancelEdit () {
|
||||
this.editActive = false;
|
||||
},
|
||||
setValue () {
|
||||
const sendData = {
|
||||
value: this.newValue,
|
||||
name: this.keyName,
|
||||
};
|
||||
|
||||
this.$emit('valueChanged', sendData);
|
||||
this.editActive = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.static-text-wrapper {
|
||||
line-height: 1.4em;
|
||||
font-weight: 600;
|
||||
|
||||
.static-text {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
|
||||
&:hover {
|
||||
border-bottom: 1px dashed #555;
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
font-weight: 600;
|
||||
|
||||
&.edit-field {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1em;
|
||||
color: #555;
|
||||
border-bottom: 1px dashed #555;
|
||||
width: calc(100% - 130px);
|
||||
}
|
||||
&.edit-field:focus {
|
||||
outline-offset: unset;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.icons {
|
||||
margin-left: 0.6em;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
@ -157,7 +157,7 @@ export default mixins(
|
||||
padding: 1em 0 0.5em 1.8em;
|
||||
border-top-left-radius: 8px;
|
||||
|
||||
background-color: $--custom-window-sidebar-top;
|
||||
background-color: var(--color-background-base);
|
||||
color: #555;
|
||||
border-bottom: 1px solid $--color-primary;
|
||||
margin-bottom: 1em;
|
||||
|
@ -26,13 +26,13 @@
|
||||
</span>
|
||||
{{ $locale.baseText('executionDetails.of') }}
|
||||
<span class="primary-color clickable" :title="$locale.baseText('executionDetails.openWorkflow')">
|
||||
<WorkflowNameShort :name="workflowName">
|
||||
<ShortenName :name="workflowName">
|
||||
<template v-slot="{ shortenedName }">
|
||||
<span @click="openWorkflow(workflowExecution.workflowId)">
|
||||
"{{ shortenedName }}"
|
||||
</span>
|
||||
</template>
|
||||
</WorkflowNameShort>
|
||||
</ShortenName>
|
||||
</span>
|
||||
{{ $locale.baseText('executionDetails.workflow') }}
|
||||
</span>
|
||||
@ -47,13 +47,13 @@ import { IExecutionResponse } from "../../../Interface";
|
||||
|
||||
import { titleChange } from "@/components/mixins/titleChange";
|
||||
|
||||
import WorkflowNameShort from "@/components/WorkflowNameShort.vue";
|
||||
import ShortenName from "@/components/ShortenName.vue";
|
||||
import ReadOnly from "@/components/MainHeader/ExecutionDetails/ReadOnly.vue";
|
||||
|
||||
export default mixins(titleChange).extend({
|
||||
name: "ExecutionDetails",
|
||||
components: {
|
||||
WorkflowNameShort,
|
||||
ShortenName,
|
||||
ReadOnly,
|
||||
},
|
||||
computed: {
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="container" v-if="workflowName">
|
||||
<BreakpointsObserver :valueXS="15" :valueSM="25" :valueMD="50" class="name-container">
|
||||
<template v-slot="{ value }">
|
||||
<WorkflowNameShort
|
||||
<ShortenName
|
||||
:name="workflowName"
|
||||
:limit="value"
|
||||
:custom="true"
|
||||
@ -19,7 +19,7 @@
|
||||
class="name"
|
||||
/>
|
||||
</template>
|
||||
</WorkflowNameShort>
|
||||
</ShortenName>
|
||||
</template>
|
||||
</BreakpointsObserver>
|
||||
|
||||
@ -81,7 +81,7 @@ import mixins from "vue-typed-mixins";
|
||||
import { mapGetters } from "vuex";
|
||||
import { MAX_WORKFLOW_NAME_LENGTH } from "@/constants";
|
||||
|
||||
import WorkflowNameShort from "@/components/WorkflowNameShort.vue";
|
||||
import ShortenName from "@/components/ShortenName.vue";
|
||||
import TagsContainer from "@/components/TagsContainer.vue";
|
||||
import PushConnectionTracker from "@/components/PushConnectionTracker.vue";
|
||||
import WorkflowActivator from "@/components/WorkflowActivator.vue";
|
||||
@ -105,7 +105,7 @@ export default mixins(workflowHelpers).extend({
|
||||
components: {
|
||||
TagsContainer,
|
||||
PushConnectionTracker,
|
||||
WorkflowNameShort,
|
||||
ShortenName,
|
||||
WorkflowActivator,
|
||||
SaveButton,
|
||||
TagsDropdown,
|
||||
|
65
packages/editor-ui/src/components/NodeExecuteButton.vue
Normal file
65
packages/editor-ui/src/components/NodeExecuteButton.vue
Normal file
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<n8n-button
|
||||
:loading="workflowRunning"
|
||||
:label="label"
|
||||
size="small"
|
||||
@click="onClick"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { INodeUi } from '@/Interface';
|
||||
import { INodeTypeDescription } from 'n8n-workflow';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { workflowRun } from './mixins/workflowRun';
|
||||
|
||||
export default mixins(
|
||||
workflowRun,
|
||||
).extend({
|
||||
props: {
|
||||
nodeName: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
node (): INodeUi {
|
||||
return this.$store.getters.getNodeByName(this.nodeName);
|
||||
},
|
||||
nodeType (): INodeTypeDescription | null {
|
||||
if (this.node) {
|
||||
return this.$store.getters.nodeType(this.node.type, this.node.typeVersion);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
workflowRunning (): boolean {
|
||||
return this.$store.getters.isActionActive('workflowRunning');
|
||||
},
|
||||
isTriggerNode (): boolean {
|
||||
return !!(this.nodeType && this.nodeType.group.includes('trigger'));
|
||||
},
|
||||
isPollingTypeNode (): boolean {
|
||||
return !!(this.nodeType && this.nodeType.polling);
|
||||
},
|
||||
isScheduleTrigger (): boolean {
|
||||
return !!(this.nodeType && this.nodeType.group.includes('schedule'));
|
||||
},
|
||||
label(): string {
|
||||
if (this.isPollingTypeNode) {
|
||||
return this.$locale.baseText('ndv.execute.fetchEvent');
|
||||
}
|
||||
|
||||
if (this.isTriggerNode && !this.isScheduleTrigger) {
|
||||
return this.$locale.baseText('ndv.execute.listenForEvent');
|
||||
}
|
||||
|
||||
return this.$locale.baseText('ndv.execute.executeNode');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.runWorkflow(this.nodeName, 'RunData.ExecuteNodeButton');
|
||||
this.$emit('execute');
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
@ -1,17 +1,15 @@
|
||||
<template>
|
||||
<div class="node-settings" @keydown.stop>
|
||||
<div class="header-side-menu">
|
||||
<span v-if="node">
|
||||
<display-with-change :key-name="'name'" @valueChanged="valueChanged"></display-with-change>
|
||||
<span class="node-info">
|
||||
<n8n-link v-if="nodeType" :to="'http://n8n.io/nodes/' + nodeType.name">
|
||||
<n8n-tooltip class="clickable" placement="top" >
|
||||
<div slot="content" v-html="`<strong>${$locale.baseText('nodeSettings.nodeDescription')}:</strong><br />` + nodeTypeDescription + `<br /><br /><strong>${$locale.baseText('nodeSettings.clickOnTheQuestionMarkIcon')}</strong>`"></div>
|
||||
<font-awesome-icon icon="question-circle" />
|
||||
</n8n-tooltip>
|
||||
</n8n-link>
|
||||
</span>
|
||||
</span>
|
||||
<div :class="$style.header">
|
||||
<div class="header-side-menu">
|
||||
<NodeTitle class="node-name" :value="node.name" :nodeType="nodeType" @input="nameChanged" :readOnly="isReadOnly"></NodeTitle>
|
||||
<div
|
||||
v-if="!isReadOnly"
|
||||
>
|
||||
<NodeExecuteButton :nodeName="node.name" @execute="onNodeExecute" />
|
||||
</div>
|
||||
</div>
|
||||
<NodeTabs v-model="openPanel" :nodeType="nodeType" />
|
||||
</div>
|
||||
<div class="node-is-not-valid" v-if="node && !nodeValid">
|
||||
<n8n-text>
|
||||
@ -24,22 +22,20 @@
|
||||
</n8n-text>
|
||||
</div>
|
||||
<div class="node-parameters-wrapper" v-if="node && nodeValid">
|
||||
<el-tabs stretch @tab-click="handleTabClick">
|
||||
<el-tab-pane :label="$locale.baseText('nodeSettings.parameters')">
|
||||
<node-credentials :node="node" @credentialSelected="credentialSelected"></node-credentials>
|
||||
<node-webhooks :node="node" :nodeType="nodeType" />
|
||||
<parameter-input-list :parameters="parametersNoneSetting" :hideDelete="true" :nodeValues="nodeValues" path="parameters" @valueChanged="valueChanged" />
|
||||
<div v-if="parametersNoneSetting.length === 0" class="no-parameters">
|
||||
<n8n-text>
|
||||
{{ $locale.baseText('nodeSettings.thisNodeDoesNotHaveAnyParameters') }}
|
||||
</n8n-text>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$locale.baseText('nodeSettings.settings')">
|
||||
<parameter-input-list :parameters="nodeSettings" :hideDelete="true" :nodeValues="nodeValues" path="" @valueChanged="valueChanged" />
|
||||
<parameter-input-list :parameters="parametersSetting" :nodeValues="nodeValues" path="parameters" @valueChanged="valueChanged" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div v-show="openPanel === 'params'">
|
||||
<node-credentials :node="node" @credentialSelected="credentialSelected"></node-credentials>
|
||||
<node-webhooks :node="node" :nodeType="nodeType" />
|
||||
<parameter-input-list :parameters="parametersNoneSetting" :hideDelete="true" :nodeValues="nodeValues" path="parameters" @valueChanged="valueChanged" />
|
||||
<div v-if="parametersNoneSetting.length === 0" class="no-parameters">
|
||||
<n8n-text>
|
||||
{{ $locale.baseText('nodeSettings.thisNodeDoesNotHaveAnyParameters') }}
|
||||
</n8n-text>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="openPanel === 'settings'">
|
||||
<parameter-input-list :parameters="nodeSettings" :hideDelete="true" :nodeValues="nodeValues" path="" @valueChanged="valueChanged" />
|
||||
<parameter-input-list :parameters="parametersSetting" :nodeValues="nodeValues" path="parameters" @valueChanged="valueChanged" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -59,12 +55,11 @@ import {
|
||||
IUpdateInformation,
|
||||
} from '@/Interface';
|
||||
|
||||
import { ElTabPane } from "element-ui/types/tab-pane";
|
||||
|
||||
import DisplayWithChange from '@/components/DisplayWithChange.vue';
|
||||
import NodeTitle from '@/components/NodeTitle.vue';
|
||||
import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
||||
import ParameterInputList from '@/components/ParameterInputList.vue';
|
||||
import NodeCredentials from '@/components/NodeCredentials.vue';
|
||||
import NodeTabs from '@/components/NodeTabs.vue';
|
||||
import NodeWebhooks from '@/components/NodeWebhooks.vue';
|
||||
import { get, set, unset } from 'lodash';
|
||||
|
||||
@ -73,21 +68,23 @@ import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import NodeExecuteButton from './NodeExecuteButton.vue';
|
||||
|
||||
export default mixins(
|
||||
externalHooks,
|
||||
genericHelpers,
|
||||
nodeHelpers,
|
||||
)
|
||||
|
||||
.extend({
|
||||
name: 'NodeSettings',
|
||||
components: {
|
||||
DisplayWithChange,
|
||||
NodeTitle,
|
||||
NodeCredentials,
|
||||
ParameterInputFull,
|
||||
ParameterInputList,
|
||||
NodeTabs,
|
||||
NodeWebhooks,
|
||||
NodeExecuteButton,
|
||||
},
|
||||
computed: {
|
||||
nodeType (): INodeTypeDescription | null {
|
||||
@ -150,14 +147,16 @@ export default mixins(
|
||||
|
||||
return this.nodeType.properties;
|
||||
},
|
||||
workflowRunning (): boolean {
|
||||
return this.$store.getters.isActionActive('workflowRunning');
|
||||
},
|
||||
props: {
|
||||
eventBus: {
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
nodeValid: true,
|
||||
nodeColor: null,
|
||||
openPanel: 'params',
|
||||
nodeValues: {
|
||||
color: '#ff0000',
|
||||
alwaysOutputData: false,
|
||||
@ -271,7 +270,9 @@ export default mixins(
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
noOp () {},
|
||||
onNodeExecute () {
|
||||
this.$emit('execute');
|
||||
},
|
||||
setValue (name: string, value: NodeParameterValue) {
|
||||
const nameParts = name.split('.');
|
||||
let lastNamePart: string | undefined = nameParts.pop();
|
||||
@ -337,6 +338,13 @@ export default mixins(
|
||||
|
||||
this.$externalHooks().run('nodeSettings.credentialSelected', { updateInformation });
|
||||
},
|
||||
nameChanged(name: string) {
|
||||
// @ts-ignore
|
||||
this.valueChanged({
|
||||
value: name,
|
||||
name: 'name',
|
||||
});
|
||||
},
|
||||
valueChanged (parameterData: IUpdateInformation) {
|
||||
let newValue: NodeParameterValue;
|
||||
if (parameterData.hasOwnProperty('value')) {
|
||||
@ -362,7 +370,6 @@ export default mixins(
|
||||
};
|
||||
this.$emit('valueChanged', sendData);
|
||||
|
||||
this.$store.commit('setActiveNode', newValue);
|
||||
} else if (parameterData.name.startsWith('parameters.')) {
|
||||
// A node parameter changed
|
||||
|
||||
@ -514,20 +521,25 @@ export default mixins(
|
||||
this.nodeValid = false;
|
||||
}
|
||||
},
|
||||
handleTabClick(tab: ElTabPane) {
|
||||
if(tab.label === 'Settings') {
|
||||
this.$telemetry.track('User viewed node settings', { node_type: this.node ? this.node.type : '', workflow_id: this.$store.getters.workflowId });
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.setNodeValues();
|
||||
if (this.eventBus) {
|
||||
(this.eventBus as Vue).$on('openSettings', () => {
|
||||
this.openPanel = 'settings';
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" module>
|
||||
.header {
|
||||
background-color: var(--color-background-base);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.node-settings {
|
||||
overflow: hidden;
|
||||
min-width: 350px;
|
||||
@ -538,20 +550,13 @@ export default mixins(
|
||||
}
|
||||
|
||||
.header-side-menu {
|
||||
padding: 1em 0 1em 1.8em;
|
||||
padding: var(--spacing-s) var(--spacing-s) var(--spacing-s) var(--spacing-s);
|
||||
font-size: var(--font-size-l);
|
||||
background-color: $--custom-window-sidebar-top;
|
||||
display: flex;
|
||||
|
||||
.node-info {
|
||||
display: none;
|
||||
padding-left: 0.5em;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.node-info {
|
||||
display: inline;
|
||||
}
|
||||
.node-name {
|
||||
padding-top: var(--spacing-5xs);
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -561,51 +566,8 @@ export default mixins(
|
||||
|
||||
.node-parameters-wrapper {
|
||||
height: 100%;
|
||||
|
||||
.el-tabs__header {
|
||||
background-color: #fff5f2;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.el-tabs {
|
||||
height: 100%;
|
||||
.el-tabs__content {
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
padding-bottom: 180px;
|
||||
|
||||
.el-tab-pane {
|
||||
margin: 0 var(--spacing-s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs__nav {
|
||||
padding-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.add-option {
|
||||
i.el-select__caret {
|
||||
color: var(--color-foreground-xlight);
|
||||
}
|
||||
.el-input .el-input__inner {
|
||||
&,
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-radius: 20px;
|
||||
color: var(--color-foreground-xlight);
|
||||
font-weight: 600;
|
||||
background-color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-foreground-xlight);
|
||||
opacity: 1; /** Firefox */
|
||||
}
|
||||
}
|
||||
}
|
||||
overflow-y: auto;
|
||||
padding: 0 20px 200px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
85
packages/editor-ui/src/components/NodeTabs.vue
Normal file
85
packages/editor-ui/src/components/NodeTabs.vue
Normal file
@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<n8n-tabs
|
||||
:options="options"
|
||||
:value="value"
|
||||
@input="onTabSelect"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { externalHooks } from '@/components/mixins/externalHooks';
|
||||
import { ITab } from '@/Interface';
|
||||
import { INodeTypeDescription } from 'n8n-workflow';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
export default mixins(
|
||||
externalHooks,
|
||||
).extend({
|
||||
name: 'NodeTabs',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
},
|
||||
nodeType: {
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
documentationUrl (): string {
|
||||
const nodeType = this.nodeType as INodeTypeDescription | null;
|
||||
if (!nodeType) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (nodeType.documentationUrl && nodeType.documentationUrl.startsWith('http')) {
|
||||
return nodeType.documentationUrl;
|
||||
}
|
||||
|
||||
if (nodeType.documentationUrl || (nodeType.name && nodeType.name.startsWith('n8n-nodes-base'))) {
|
||||
return 'https://docs.n8n.io/nodes/' + (nodeType.documentationUrl || nodeType.name) + '?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=' + nodeType.name;
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
options (): ITab[] {
|
||||
const options: ITab[] = [
|
||||
{
|
||||
label: this.$locale.baseText('nodeSettings.parameters'),
|
||||
value: 'params',
|
||||
},
|
||||
];
|
||||
if (this.documentationUrl) {
|
||||
options.push({
|
||||
label: this.$locale.baseText('nodeSettings.docs'),
|
||||
value: 'docs',
|
||||
href: this.documentationUrl,
|
||||
});
|
||||
}
|
||||
options.push(
|
||||
{
|
||||
icon: 'cog',
|
||||
value: 'settings',
|
||||
align: 'right',
|
||||
},
|
||||
);
|
||||
|
||||
return options;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onTabSelect(tab: string) {
|
||||
if (tab === 'docs' && this.nodeType) {
|
||||
this.$externalHooks().run('dataDisplay.onDocumentationUrlClick', { nodeType: this.nodeType as INodeTypeDescription, documentationUrl: this.documentationUrl });
|
||||
}
|
||||
|
||||
if(tab === 'settings' && this.nodeType) {
|
||||
this.$telemetry.track('User viewed node settings', { node_type: (this.nodeType as INodeTypeDescription).name, workflow_id: this.$store.getters.workflowId });
|
||||
}
|
||||
|
||||
if (tab === 'settings' || tab === 'params') {
|
||||
this.$emit('input', tab);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
135
packages/editor-ui/src/components/NodeTitle.vue
Normal file
135
packages/editor-ui/src/components/NodeTitle.vue
Normal file
@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<span :class="$style.container" @click="onEdit">
|
||||
<span :class="$style.iconWrapper"><NodeIcon :nodeType="nodeType" :size="18" /></span>
|
||||
<el-popover placement="right" width="200" :value="editName" :disabled="readOnly">
|
||||
<div
|
||||
:class="$style.editContainer"
|
||||
@keydown.enter="onRename"
|
||||
@keydown.stop
|
||||
@keydown.esc="editName = false"
|
||||
>
|
||||
<n8n-text :class="$style.renameText" :bold="true" color="text-base" tag="div"
|
||||
>{{ $locale.baseText('ndv.title.renameNode') }}</n8n-text>
|
||||
<n8n-input ref="input" size="small" v-model="newName" />
|
||||
<div :class="$style.editButtons">
|
||||
<n8n-button type="outline" size="small" @click="editName = false" :label="$locale.baseText('ndv.title.cancel')" />
|
||||
<n8n-button type="primary" size="small" @click="onRename" :label="$locale.baseText('ndv.title.rename')" />
|
||||
</div>
|
||||
</div>
|
||||
<div slot="reference" :class="{[$style.title]: true, [$style.hoverable]: !readOnly}">
|
||||
{{ value }}
|
||||
<div :class="$style.editIconContainer">
|
||||
<font-awesome-icon :class="$style.editIcon" icon="pencil-alt" v-if="!readOnly" />
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'NodeTitle',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
},
|
||||
nodeType: {},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editName: false,
|
||||
newName: '',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onEdit() {
|
||||
this.newName = this.value;
|
||||
this.editName = true;
|
||||
this.$nextTick(() => {
|
||||
const input = this.$refs.input;
|
||||
if (input) {
|
||||
(input as HTMLInputElement).focus();
|
||||
}
|
||||
});
|
||||
},
|
||||
onRename() {
|
||||
if (this.newName.trim() !== '') {
|
||||
this.$emit('input', this.newName.trim());
|
||||
}
|
||||
|
||||
this.editName = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.container {
|
||||
font-weight: var(--font-weight-bold);
|
||||
display: flex;
|
||||
font-size: var(--font-size-m);
|
||||
line-height: var(--font-line-height-compact);
|
||||
overflow-wrap: anywhere;
|
||||
padding-right: var(--spacing-s);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.title {
|
||||
max-height: 100px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 5;
|
||||
-webkit-box-orient: vertical;
|
||||
color: var(--color-text-dark);
|
||||
}
|
||||
|
||||
.hoverable {
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
.editIcon {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.iconWrapper {
|
||||
display: inline-flex;
|
||||
margin-right: var(--spacing-2xs);
|
||||
}
|
||||
|
||||
.editIcon {
|
||||
display: none;
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-base);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.editIconContainer {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.editButtons {
|
||||
text-align: right;
|
||||
margin-top: var(--spacing-s);
|
||||
|
||||
> * {
|
||||
margin-left: var(--spacing-4xs);
|
||||
}
|
||||
}
|
||||
|
||||
.editContainer {
|
||||
text-align: left;
|
||||
|
||||
> *:first-child {
|
||||
margin-bottom: var(--spacing-4xs);
|
||||
}
|
||||
}
|
||||
</style>
|
File diff suppressed because it is too large
Load Diff
@ -11,7 +11,7 @@ const DEFAULT_WORKFLOW_NAME_LIMIT = 25;
|
||||
const WORKFLOW_NAME_END_COUNT_TO_KEEP = 4;
|
||||
|
||||
export default Vue.extend({
|
||||
name: "WorkflowNameShort",
|
||||
name: "ShortenName",
|
||||
props: ["name", "limit"],
|
||||
computed: {
|
||||
shortenedName(): string {
|
@ -57,15 +57,7 @@ export default Vue.extend({
|
||||
},
|
||||
rows(): ITagRow[] {
|
||||
const getUsage = (count: number | undefined) => count && count > 0
|
||||
? this.$locale.baseText(
|
||||
count > 1 ?
|
||||
'tagsView.inUse.plural' : 'tagsView.inUse.singular',
|
||||
{
|
||||
interpolate: {
|
||||
count: count.toString(),
|
||||
},
|
||||
},
|
||||
)
|
||||
? this.$locale.baseText('tagsView.inUse', { adjustToNumber: count })
|
||||
: this.$locale.baseText('tagsView.notBeingUsed');
|
||||
|
||||
const disabled = this.isCreateEnabled || this.$data.updateId || this.$data.deleteId;
|
||||
|
@ -287,6 +287,9 @@ export const nodeHelpers = mixins(
|
||||
return [];
|
||||
}
|
||||
const executionData: IRunExecutionData = this.$store.getters.getWorkflowExecution.data;
|
||||
if (!executionData || !executionData.resultData) { // unknown status
|
||||
return [];
|
||||
}
|
||||
const runData = executionData.resultData.runData;
|
||||
|
||||
if (runData === null || runData[node.name] === undefined ||
|
||||
|
@ -120,9 +120,6 @@
|
||||
.el-tabs__nav:focus {
|
||||
outline: none;
|
||||
}
|
||||
.el-tabs__item {
|
||||
color: #555;
|
||||
}
|
||||
.el-tabs__item.is-active {
|
||||
font-weight: bold;
|
||||
}
|
||||
@ -185,3 +182,30 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-option {
|
||||
> * {
|
||||
border: none;
|
||||
}
|
||||
|
||||
i.el-select__caret {
|
||||
color: var(--color-foreground-xlight);
|
||||
}
|
||||
.el-input .el-input__inner {
|
||||
&,
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-radius: 20px;
|
||||
color: var(--color-foreground-xlight);
|
||||
font-weight: 600;
|
||||
background-color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-foreground-xlight);
|
||||
opacity: 1; /** Firefox */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,8 @@ import {
|
||||
Message,
|
||||
Notification,
|
||||
CollapseTransition,
|
||||
Pagination,
|
||||
Popover,
|
||||
|
||||
N8nActionBox,
|
||||
N8nAvatar,
|
||||
@ -60,8 +62,10 @@ import {
|
||||
N8nMenu,
|
||||
N8nMenuItem,
|
||||
N8nOption,
|
||||
N8nRadioButtons,
|
||||
N8nSelect,
|
||||
N8nSpinner,
|
||||
N8nTabs,
|
||||
N8nFormInputs,
|
||||
N8nFormBox,
|
||||
N8nSquareButton,
|
||||
@ -81,7 +85,7 @@ Vue.use(N8nAvatar);
|
||||
Vue.use(N8nButton);
|
||||
Vue.component('n8n-form-box', N8nFormBox);
|
||||
Vue.component('n8n-form-inputs', N8nFormInputs);
|
||||
Vue.use('n8n-icon', N8nIcon);
|
||||
Vue.component('n8n-icon', N8nIcon);
|
||||
Vue.use(N8nIconButton);
|
||||
Vue.use(N8nInfoTip);
|
||||
Vue.use(N8nInput);
|
||||
@ -96,8 +100,10 @@ Vue.use(N8nMenuItem);
|
||||
Vue.use(N8nOption);
|
||||
Vue.use(N8nSelect);
|
||||
Vue.use(N8nSpinner);
|
||||
Vue.use(N8nRadioButtons);
|
||||
Vue.component('n8n-square-button', N8nSquareButton);
|
||||
Vue.use(N8nTags);
|
||||
Vue.component('n8n-tabs', N8nTabs);
|
||||
Vue.use(N8nTag);
|
||||
Vue.component('n8n-text', N8nText);
|
||||
Vue.use(N8nTooltip);
|
||||
@ -130,6 +136,9 @@ Vue.use(Badge);
|
||||
Vue.use(Card);
|
||||
Vue.use(ColorPicker);
|
||||
Vue.use(Container);
|
||||
Vue.use(Pagination);
|
||||
Vue.use(Popover);
|
||||
|
||||
Vue.use(VueAgile);
|
||||
|
||||
Vue.component(CollapseTransition.name, CollapseTransition);
|
||||
|
@ -2,16 +2,24 @@
|
||||
|
||||
## Base text
|
||||
|
||||
### Interpolation
|
||||
### Pluralization
|
||||
|
||||
Certain base text strings use [interpolation](https://kazupon.github.io/vue-i18n/guide/formatting.html#named-formatting) to allow for a variable to be passed in, signalled by curly braces:
|
||||
Certain base text strings accept [singular and plural versions](https://kazupon.github.io/vue-i18n/guide/pluralization.html) separated by a `|` character:
|
||||
|
||||
```json
|
||||
{
|
||||
"stopExecution": {
|
||||
"message": "The execution with the ID {activeExecutionId} got stopped!",
|
||||
"title": "Execution stopped"
|
||||
}
|
||||
"tagsView.inUse": "{count} workflow | {count} workflows",
|
||||
}
|
||||
```
|
||||
|
||||
### Interpolation
|
||||
|
||||
Certain base text strings use [interpolation](https://kazupon.github.io/vue-i18n/guide/formatting.html#named-formatting) to allow for a variable between curly braces:
|
||||
|
||||
```json
|
||||
{
|
||||
"stopExecution.message": "The execution with the ID {activeExecutionId} got stopped!",
|
||||
"stopExecution.title": "Execution stopped"
|
||||
}
|
||||
```
|
||||
|
||||
@ -19,10 +27,8 @@ When translating a string containing an interpolated variable, leave the variabl
|
||||
|
||||
```json
|
||||
{
|
||||
"stopExecution": {
|
||||
"message": "Die Ausführung mit der ID {activeExecutionId} wurde gestoppt",
|
||||
"title": "Execution stopped"
|
||||
}
|
||||
"stopExecution.message": "Die Ausführung mit der ID {activeExecutionId} wurde gestoppt",
|
||||
"stopExecution.title": "Execution stopped"
|
||||
}
|
||||
```
|
||||
|
||||
@ -32,18 +38,12 @@ As a convenience, the base text file may contain the special key `reusableBaseTe
|
||||
|
||||
```json
|
||||
{
|
||||
"reusableBaseText": {
|
||||
"save": "🇩🇪 Save",
|
||||
},
|
||||
"duplicateWorkflowDialog": {
|
||||
"enterWorkflowName": "🇩🇪 Enter workflow name",
|
||||
"save": "@:reusableBaseText.save",
|
||||
},
|
||||
"saveButton": {
|
||||
"save": "@:reusableBaseText.save",
|
||||
"saving": "🇩🇪 Saving",
|
||||
"saved": "🇩🇪 Saved",
|
||||
},
|
||||
"reusableBaseText.save": "🇩🇪 Save",
|
||||
"duplicateWorkflowDialog.enterWorkflowName": "🇩🇪 Enter workflow name",
|
||||
"duplicateWorkflowDialog.save": "@:reusableBaseText.save",
|
||||
"saveButton.save": "@:reusableBaseText.save",
|
||||
"saveButton.saving": "🇩🇪 Saving",
|
||||
"saveButton.saved": "🇩🇪 Saved",
|
||||
}
|
||||
```
|
||||
|
||||
@ -92,23 +92,27 @@ Currently only the keys `oauth.clientId` and `oauth.clientSecret` are supported
|
||||
|
||||
```json
|
||||
{
|
||||
"reusableDynamicText": {
|
||||
"oauth2": {
|
||||
"clientId": "🇩🇪 Client ID",
|
||||
"clientSecret": "🇩🇪 Client Secret",
|
||||
}
|
||||
}
|
||||
"reusableDynamicText.oauth2.clientId": "🇩🇪 Client ID",
|
||||
"reusableDynamicText.oauth2.clientSecret": "🇩🇪 Client Secret",
|
||||
}
|
||||
```
|
||||
|
||||
### Special cases
|
||||
|
||||
`eventTriggerDescription` is a dynamic node property that is not part of node parameters. To translate it, set the `eventTriggerDescription` key at the root level of the `nodeView` property in the node translation file.
|
||||
`eventTriggerDescription` and `activationMessage` are dynamic node properties that are not part of node parameters. To translate them, set the key at the root level of the `nodeView` property in the node translation file.
|
||||
|
||||
Webhook node:
|
||||
|
||||
```json
|
||||
{
|
||||
"nodeView": {
|
||||
"eventTriggerDescription": "🇩🇪 Waiting for you to call the Test URL"
|
||||
}
|
||||
"nodeView.eventTriggerDescription": "🇩🇪 Waiting for you to call the Test URL",
|
||||
}
|
||||
```
|
||||
|
||||
Cron node:
|
||||
|
||||
```json
|
||||
{
|
||||
"nodeView.activationMessage": "🇩🇪 'Your cron trigger will now trigger executions on the schedule you have defined."
|
||||
}
|
||||
```
|
||||
|
@ -6,6 +6,7 @@ n8n allows for internalization of the majority of UI text:
|
||||
|
||||
- base text, e.g. menu display items in the left-hand sidebar menu,
|
||||
- node text, e.g. parameter display names and placeholders in the node view,
|
||||
- credential text, e.g. parameter display names and placeholders in the credential modal,
|
||||
- header text, e.g. node display names and descriptions at various spots.
|
||||
|
||||
Currently, n8n does _not_ allow for internalization of:
|
||||
@ -55,12 +56,10 @@ Base text is rendered with no dependencies, i.e. base text is fixed and does not
|
||||
The base text file for each locale is located at `/packages/editor-ui/src/plugins/i18n/locales/` and is named `{localeIdentifier}.json`. Keys in the base text file can be Vue component dirs, Vue component names, and references to symbols in those Vue components. These keys are added by the team as the UI is modified or expanded.
|
||||
|
||||
```json
|
||||
"nodeCreator": {
|
||||
"categoryNames": {
|
||||
"analytics": "🇩🇪 Analytics",
|
||||
"communication": "🇩🇪 Communication",
|
||||
"coreNodes": "🇩🇪 Core Nodes"
|
||||
}
|
||||
{
|
||||
"nodeCreator.categoryNames.analytics": "🇩🇪 Analytics",
|
||||
"nodeCreator.categoryNames.communication": "🇩🇪 Communication",
|
||||
"nodeCreator.categoryNames.coreNodes": "🇩🇪 Core Nodes"
|
||||
}
|
||||
```
|
||||
|
||||
@ -98,9 +97,9 @@ A credential translation file is placed at `/nodes-base/credentials/translations
|
||||
```
|
||||
credentials
|
||||
└── translations
|
||||
└── de
|
||||
├── githubApi.json
|
||||
└── githubOAuth2Api.json
|
||||
└── de
|
||||
├── githubApi.json
|
||||
└── githubOAuth2Api.json
|
||||
```
|
||||
Every credential must have its own credential translation file.
|
||||
|
||||
@ -123,9 +122,9 @@ GitHub
|
||||
├── GitHub.node.ts
|
||||
├── GitHubTrigger.node.ts
|
||||
└── translations
|
||||
└── de
|
||||
├── github.json
|
||||
└── githubTrigger.json
|
||||
└── de
|
||||
├── github.json
|
||||
└── githubTrigger.json
|
||||
```
|
||||
|
||||
Every node must have its own node translation file.
|
||||
@ -184,16 +183,10 @@ The object for each node credential parameter allows for the keys `displayName`,
|
||||
|
||||
```json
|
||||
{
|
||||
"server": {
|
||||
"displayName": "🇩🇪 Github Server",
|
||||
"description": "🇩🇪 The server to connect to. Only has to be set if Github Enterprise is used.",
|
||||
},
|
||||
"user": {
|
||||
"placeholder": "🇩🇪 Hans",
|
||||
},
|
||||
"accessToken": {
|
||||
"placeholder": "🇩🇪 123",
|
||||
},
|
||||
"server.displayName": "🇩🇪 Github Server",
|
||||
"server.description": "🇩🇪 The server to connect to. Only has to be set if Github Enterprise is used.",
|
||||
"user.placeholder": "🇩🇪 Hans",
|
||||
"accessToken.placeholder": "🇩🇪 123",
|
||||
}
|
||||
```
|
||||
|
||||
@ -224,10 +217,8 @@ export class Github implements INodeType {
|
||||
|
||||
```json
|
||||
{
|
||||
"header": {
|
||||
"displayName": "🇩🇪 GitHub",
|
||||
"description": "🇩🇪 Consume GitHub API",
|
||||
},
|
||||
"header.displayName": "🇩🇪 GitHub",
|
||||
"header.description": "🇩🇪 Consume GitHub API",
|
||||
}
|
||||
```
|
||||
|
||||
@ -264,11 +255,7 @@ export class Github implements INodeType {
|
||||
|
||||
```json
|
||||
{
|
||||
"nodeView": {
|
||||
"resource": {
|
||||
"displayName": "🇩🇪 Resource",
|
||||
},
|
||||
},
|
||||
"nodeView.resource.displayName": "🇩🇪 Resource",
|
||||
}
|
||||
```
|
||||
|
||||
@ -291,13 +278,9 @@ Allowed keys: `displayName`, `description`, `placeholder`
|
||||
|
||||
```json
|
||||
{
|
||||
"nodeView": {
|
||||
"owner": {
|
||||
"displayName": "🇩🇪 Repository Owner",
|
||||
"placeholder": "🇩🇪 n8n-io",
|
||||
"description": "🇩🇪 Owner of the repository",
|
||||
},
|
||||
},
|
||||
"nodeView.owner.displayName": "🇩🇪 Repository Owner",
|
||||
"nodeView.owner.placeholder": "🇩🇪 n8n-io",
|
||||
"nodeView.owner.description": "🇩🇪 Owner of the repository",
|
||||
}
|
||||
```
|
||||
|
||||
@ -333,20 +316,10 @@ Allowed subkeys: `options.{optionName}.displayName` and `options.{optionName}.de
|
||||
|
||||
```json
|
||||
{
|
||||
"nodeView": {
|
||||
"resource": {
|
||||
"displayName": "🇩🇪 Resource",
|
||||
"description": "🇩🇪 Resource to operate on",
|
||||
"options": {
|
||||
"file": {
|
||||
"displayName": "🇩🇪 File",
|
||||
},
|
||||
"issue": {
|
||||
"displayName": "🇩🇪 Issue",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"nodeView.resource.displayName": "🇩🇪 Resource",
|
||||
"nodeView.resource.description": "🇩🇪 Resource to operate on",
|
||||
"nodeView.resource.options.file.displayName": "🇩🇪 File",
|
||||
"nodeView.resource.options.issue.displayName": "🇩🇪 Issue",
|
||||
}
|
||||
```
|
||||
|
||||
@ -394,19 +367,11 @@ Example of `collection` parameter:
|
||||
|
||||
```json
|
||||
{
|
||||
"nodeView": {
|
||||
"labels": {
|
||||
"displayName": "🇩🇪 Labels",
|
||||
"multipleValueButtonText": "🇩🇪 Add Label",
|
||||
"options": {
|
||||
"label": {
|
||||
"displayName": "🇩🇪 Label",
|
||||
"description": "🇩🇪 Label to add to issue",
|
||||
"placeholder": "🇩🇪 Some placeholder"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"nodeView.labels.displayName": "🇩🇪 Labels",
|
||||
"nodeView.labels.multipleValueButtonText": "🇩🇪 Add Label",
|
||||
"nodeView.labels.options.label.displayName": "🇩🇪 Label",
|
||||
"nodeView.labels.options.label.description": "🇩🇪 Label to add to issue",
|
||||
"nodeView.labels.options.label.placeholder": "🇩🇪 Some placeholder"
|
||||
}
|
||||
```
|
||||
|
||||
@ -461,29 +426,15 @@ Example of `fixedCollection` parameter:
|
||||
|
||||
```json
|
||||
{
|
||||
"nodeView": {
|
||||
"additionalParameters": {
|
||||
"displayName": "🇩🇪 Additional Parameters",
|
||||
"placeholder": "🇩🇪 Add Field",
|
||||
"options": {
|
||||
"author": {
|
||||
"displayName": "🇩🇪 Author",
|
||||
"values": {
|
||||
"name": {
|
||||
"displayName": "🇩🇪 Name",
|
||||
"description": "🇩🇪 Name of the author of the commit",
|
||||
"placeholder": "🇩🇪 Jan"
|
||||
},
|
||||
"email": {
|
||||
"displayName": "🇩🇪 Email",
|
||||
"description": "🇩🇪 Email of the author of the commit",
|
||||
"placeholder": "🇩🇪 jan@n8n.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
"nodeView.additionalParameters.displayName": "🇩🇪 Additional Parameters",
|
||||
"nodeView.additionalParameters.placeholder": "🇩🇪 Add Field",
|
||||
"nodeView.additionalParameters.options.author.displayName": "🇩🇪 Author",
|
||||
"nodeView.additionalParameters.options.author.values.name.displayName": "🇩🇪 Name",
|
||||
"nodeView.additionalParameters.options.author.values.name.description": "🇩🇪 Name of the author of the commit",
|
||||
"nodeView.additionalParameters.options.author.values.name.placeholder": "🇩🇪 Jan",
|
||||
"nodeView.additionalParameters.options.author.values.email.displayName": "🇩🇪 Email",
|
||||
"nodeView.additionalParameters.options.author.values.email.description": "🇩🇪 Email of the author of the commit",
|
||||
"nodeView.additionalParameters.options.author.values.email.placeholder": "🇩🇪 jan@n8n.io",
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -63,8 +63,12 @@ export class I18nClass {
|
||||
*/
|
||||
baseText(
|
||||
key: string,
|
||||
options?: { interpolate: { [key: string]: string } },
|
||||
options?: { adjustToNumber: number; interpolate: { [key: string]: string } },
|
||||
): string {
|
||||
if (options && options.adjustToNumber) {
|
||||
return this.i18n.tc(key, options.adjustToNumber, options && options.interpolate).toString();
|
||||
}
|
||||
|
||||
return this.i18n.t(key, options && options.interpolate).toString();
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -61,6 +61,7 @@ import {
|
||||
faPause,
|
||||
faPauseCircle,
|
||||
faPen,
|
||||
faPencilAlt,
|
||||
faPlay,
|
||||
faPlayCircle,
|
||||
faPlus,
|
||||
@ -158,6 +159,7 @@ addIcon(faNetworkWired);
|
||||
addIcon(faPause);
|
||||
addIcon(faPauseCircle);
|
||||
addIcon(faPen);
|
||||
addIcon(faPencilAlt);
|
||||
addIcon(faPlay);
|
||||
addIcon(faPlayCircle);
|
||||
addIcon(faPlus);
|
||||
|
@ -90,6 +90,7 @@ const state: IRootState = {
|
||||
},
|
||||
sidebarMenuItems: [],
|
||||
instanceId: '',
|
||||
nodeMetadata: {},
|
||||
};
|
||||
|
||||
const modules = {
|
||||
@ -328,6 +329,9 @@ export const store = new Vuex.Store({
|
||||
if (state.lastSelectedNode === nameData.old) {
|
||||
state.lastSelectedNode = nameData.new;
|
||||
}
|
||||
|
||||
Vue.set(state.nodeMetadata, nameData.new, state.nodeMetadata[nameData.old]);
|
||||
Vue.delete(state.nodeMetadata, nameData.old);
|
||||
},
|
||||
|
||||
resetAllNodesIssues (state) {
|
||||
@ -418,6 +422,8 @@ export const store = new Vuex.Store({
|
||||
state.workflow.nodes.push(nodeData);
|
||||
},
|
||||
removeNode (state, node: INodeUi) {
|
||||
Vue.delete(state.nodeMetadata, node.name);
|
||||
|
||||
for (let i = 0; i < state.workflow.nodes.length; i++) {
|
||||
if (state.workflow.nodes[i].name === node.name) {
|
||||
state.workflow.nodes.splice(i, 1);
|
||||
@ -470,6 +476,11 @@ export const store = new Vuex.Store({
|
||||
|
||||
state.stateIsDirty = true;
|
||||
Vue.set(node, 'parameters', updateInformation.value);
|
||||
|
||||
if (!state.nodeMetadata[node.name]) {
|
||||
Vue.set(state.nodeMetadata, node.name, {});
|
||||
}
|
||||
Vue.set(state.nodeMetadata[node.name], 'parametersLastUpdatedAt', Date.now());
|
||||
},
|
||||
|
||||
// Node-Index
|
||||
@ -666,6 +677,10 @@ export const store = new Vuex.Store({
|
||||
return state.activeExecutions;
|
||||
},
|
||||
|
||||
getParametersLastUpdated: (state): ((name: string) => number | undefined) => {
|
||||
return (nodeName: string) => state.nodeMetadata[nodeName] && state.nodeMetadata[nodeName].parametersLastUpdatedAt;
|
||||
},
|
||||
|
||||
getBaseUrl: (state): string => {
|
||||
return state.baseUrl;
|
||||
},
|
||||
|
@ -33,7 +33,7 @@
|
||||
></node>
|
||||
</div>
|
||||
</div>
|
||||
<DataDisplay @valueChanged="valueChanged"/>
|
||||
<DataDisplay :renaming="renamingActive" @valueChanged="valueChanged"/>
|
||||
<div v-if="!createNodeActive && !isReadOnly" class="node-creator-button" :title="$locale.baseText('nodeView.addNode')" @click="() => openNodeCreator('add_node_button')">
|
||||
<n8n-icon-button size="xlarge" icon="plus" />
|
||||
</div>
|
||||
@ -170,7 +170,6 @@ import {
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
import {
|
||||
loadLanguage,
|
||||
addNodeTranslation,
|
||||
addHeaders,
|
||||
} from '@/plugins/i18n';
|
||||
@ -232,9 +231,6 @@ export default mixins(
|
||||
deep: true,
|
||||
},
|
||||
|
||||
async defaultLocale (newLocale, oldLocale) {
|
||||
loadLanguage(newLocale);
|
||||
},
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
const result = this.$store.getters.getStateIsDirty;
|
||||
@ -271,7 +267,7 @@ export default mixins(
|
||||
defaultLocale (): string {
|
||||
return this.$store.getters.defaultLocale;
|
||||
},
|
||||
englishLocale(): boolean {
|
||||
isEnglishLocale(): boolean {
|
||||
return this.defaultLocale === 'en';
|
||||
},
|
||||
...mapGetters(['nativelyNumberSuffixedDefaults']),
|
||||
@ -348,6 +344,7 @@ export default mixins(
|
||||
pullConnActiveNodeName: null as string | null,
|
||||
pullConnActive: false,
|
||||
dropPrevented: false,
|
||||
renamingActive: false,
|
||||
};
|
||||
},
|
||||
beforeDestroy () {
|
||||
@ -378,7 +375,7 @@ export default mixins(
|
||||
type?: string,
|
||||
}) {
|
||||
const allNodeNamesOnCanvas = this.$store.getters.allNodes.map((n: INodeUi) => n.name);
|
||||
originalName = this.englishLocale ? originalName : this.translateName(type, originalName);
|
||||
originalName = this.isEnglishLocale ? originalName : this.translateName(type, originalName);
|
||||
|
||||
if (
|
||||
!allNodeNamesOnCanvas.includes(originalName) &&
|
||||
@ -388,7 +385,7 @@ export default mixins(
|
||||
}
|
||||
|
||||
let natives: string[] = this.nativelyNumberSuffixedDefaults;
|
||||
natives = this.englishLocale ? natives : natives.map(name => {
|
||||
natives = this.isEnglishLocale ? natives : natives.map(name => {
|
||||
const type = name.toLowerCase().replace('_', '');
|
||||
return this.translateName(type, name);
|
||||
});
|
||||
@ -1260,15 +1257,10 @@ export default mixins(
|
||||
const maxNodes = nodeTypeData.maxNodes;
|
||||
this.$showMessage({
|
||||
title: this.$locale.baseText('nodeView.showMessage.showMaxNodeTypeError.title'),
|
||||
message: this.$locale.baseText(
|
||||
maxNodes === 1
|
||||
? 'nodeView.showMessage.showMaxNodeTypeError.message.singular'
|
||||
: 'nodeView.showMessage.showMaxNodeTypeError.message.plural',
|
||||
message: this.$locale.baseText('nodeView.showMessage.showMaxNodeTypeError.message',
|
||||
{
|
||||
interpolate: {
|
||||
maxNodes: maxNodes!.toString(),
|
||||
nodeTypeDataDisplayName: nodeTypeData.displayName,
|
||||
},
|
||||
adjustToNumber: maxNodes,
|
||||
interpolate: { nodeTypeDataDisplayName: nodeTypeData.displayName },
|
||||
},
|
||||
),
|
||||
type: 'error',
|
||||
@ -2244,6 +2236,13 @@ export default mixins(
|
||||
if (currentName === newName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeNodeName = this.activeNode && this.activeNode.name;
|
||||
const isActive = activeNodeName === currentName;
|
||||
if (isActive) {
|
||||
this.renamingActive = true;
|
||||
}
|
||||
|
||||
// Check if node-name is unique else find one that is
|
||||
newName = this.getUniqueNodeName({
|
||||
originalName: newName,
|
||||
@ -2271,6 +2270,11 @@ export default mixins(
|
||||
// Make sure that the node is selected again
|
||||
this.deselectAllNodes();
|
||||
this.nodeSelectedByName(newName);
|
||||
|
||||
if (isActive) {
|
||||
this.$store.commit('setActiveNode', newName);
|
||||
this.renamingActive = false;
|
||||
}
|
||||
},
|
||||
deleteEveryEndpoint () {
|
||||
// Check as it does not exist on first load
|
||||
@ -2744,15 +2748,6 @@ export default mixins(
|
||||
|
||||
try {
|
||||
await Promise.all(loadPromises);
|
||||
|
||||
if (this.defaultLocale !== 'en') {
|
||||
try {
|
||||
const headers = await this.restApi().getNodeTranslationHeaders();
|
||||
addHeaders(headers, this.defaultLocale);
|
||||
} catch (_) {
|
||||
// no headers available
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.$showError(
|
||||
error,
|
||||
|
@ -22,7 +22,7 @@ export class Cron implements INodeType {
|
||||
displayName: 'Cron',
|
||||
name: 'cron',
|
||||
icon: 'fa:calendar',
|
||||
group: ['trigger'],
|
||||
group: ['trigger', 'schedule'],
|
||||
version: 1,
|
||||
description: 'Triggers the workflow at a specific time',
|
||||
eventTriggerDescription: '',
|
||||
|
@ -12,7 +12,7 @@ export class Interval implements INodeType {
|
||||
displayName: 'Interval',
|
||||
name: 'interval',
|
||||
icon: 'fa:hourglass',
|
||||
group: ['trigger'],
|
||||
group: ['trigger', 'schedule'],
|
||||
version: 1,
|
||||
description: 'Triggers the workflow in a given interval',
|
||||
eventTriggerDescription: '',
|
||||
|
Loading…
Reference in New Issue
Block a user