Update parameter inputs to be multi-line (#2299)

* introduce analytics

* add user survey backend

* add user survey backend

* set answers on survey submit

Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com>

* change name to personalization

* lint

Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com>

* N8n 2495 add personalization modal (#2280)

* update modals

* add onboarding modal

* implement questions

* introduce analytics

* simplify impl

* implement survey handling

* add personalized cateogry

* update modal behavior

* add thank you view

* handle empty cases

* rename modal

* standarize modal names

* update image, add tags to headings

* remove unused file

* remove unused interfaces

* clean up footer spacing

* introduce analytics

* refactor to fix bug

* update endpoint

* set min height

* update stories

* update naming from questions to survey

* remove spacing after core categories

* fix bug in logic

* sort nodes

* rename types

* merge with be

* rename userSurvey

* clean up rest api

* use constants for keys

* use survey keys

* clean up types

* move personalization to its own file

Co-authored-by: ahsan-virani <ahsan.virani@gmail.com>

* update parameter inputs to be multiline

* update spacing

* Survey new options (#2300)

* split up options

* fix quotes

* remove unused import

* refactor node credentials

* add user created workflow event (#2301)

* update multi params

* simplify env vars

* fix versionCli on FE

* update personalization env

* clean up node detail settings

* fix event User opened Credentials panel

* fix font sizes across modals

* clean up input spacing

* fix select modal spacing

* increase spacing

* fix input copy

* fix webhook, tab spacing, retry button

* fix button sizes

* fix button size

* add mini xlarge sizes

* fix webhook spacing

* fix nodes panel event

* fix workflow id in workflow execute event

* improve telemetry error logging

* fix config and stop process events

* add flush call on n8n stop

* ready for release

* fix input error highlighting

* revert change

* update toggle spacing

* fix delete positioning

* keep tooltip while focused

* set strict size

* increase left spacing

* fix sort icons

* remove unnessary margin

* clean unused functionality

* remove unnessary css

* remove duplicate tracking

* only show tooltip when hovering over label

* update credentials section

* use includes

Co-authored-by: ahsan-virani <ahsan.virani@gmail.com>
Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
Mutasem Aldmour 2021-10-27 21:55:37 +02:00 committed by GitHub
parent 3e1fb3e0c9
commit 171f5a458c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 443 additions and 459 deletions

View file

@ -18,7 +18,7 @@ export default {
size: {
control: {
type: 'select',
options: ['small', 'medium', 'large'],
options: ['mini', 'small', 'medium', 'large', 'xlarge'],
},
},
loading: {
@ -31,12 +31,6 @@ export default {
type: 'text',
},
},
iconSize: {
control: {
type: 'select',
options: ['small', 'medium', 'large'],
},
},
circle: {
control: {
type: 'boolean',

View file

@ -16,13 +16,13 @@
<component
:is="$options.components.N8nSpinner"
v-if="props.loading"
:size="props.iconSize"
:size="props.size"
/>
<component
:is="$options.components.N8nIcon"
v-else-if="props.icon"
:icon="props.icon"
:size="props.iconSize"
:size="props.size"
/>
</span>
<span v-if="props.label">{{ props.label }}</span>
@ -58,7 +58,7 @@ export default {
type: String,
default: 'medium',
validator: (value: string): boolean =>
['small', 'medium', 'large'].indexOf(value) !== -1,
['mini', 'small', 'medium', 'large', 'xlarge'].indexOf(value) !== -1,
},
loading: {
type: Boolean,
@ -71,9 +71,6 @@ export default {
icon: {
type: String,
},
iconSize: {
type: String,
},
round: {
type: Boolean,
default: true,

View file

@ -35,9 +35,6 @@ export declare class N8nButton extends N8nComponent {
/** Button icon, accepts an icon name of font awesome icon component */
icon: string;
/** Size of icon */
iconSize: N8nComponentSize;
/** Full width */
fullWidth: boolean;

View file

@ -1,19 +1,27 @@
<template functional>
<component
:is="$options.components.N8nText"
:size="props.size"
:compact="true"
>
<component
:is="$options.components.FontAwesomeIcon"
:class="$style[`_${props.size}`]"
:icon="props.icon"
:spin="props.spin"
:class="$style[props.size]"
/>
</component>
</template>
<script lang="ts">
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import N8nText from '../N8nText';
export default {
name: 'n8n-icon',
components: {
FontAwesomeIcon,
N8nText,
},
props: {
icon: {
@ -23,9 +31,6 @@ export default {
size: {
type: String,
default: 'medium',
validator: function (value: string): boolean {
return ['small', 'medium', 'large'].indexOf(value) !== -1;
},
},
spin: {
type: Boolean,
@ -35,22 +40,21 @@ export default {
};
</script>
<style lang="scss" module>
._small {
font-size: 0.85em;
height: 0.85em;
width: 0.85em !important;
.xlarge {
width: var(--font-size-xl) !important;
}
._medium {
font-size: 0.95em;
height: 0.95em;
width: 0.95em !important;
.large {
width: var(--font-size-m) !important;
}
._large {
font-size: 1.22em;
height: 1.22em;
width: 1.22em !important;
.medium {
width: var(--font-size-s) !important;
}
.small {
width: var(--font-size-2xs) !important;
}
</style>

View file

@ -2,11 +2,10 @@
<component :is="$options.components.N8nButton"
:type="props.type"
:disabled="props.disabled"
:size="props.size === 'xlarge' ? 'large' : props.size"
:size="props.size"
:loading="props.loading"
:title="props.title"
:icon="props.icon"
:iconSize="$options.iconSizeMap[props.size] || props.size"
:theme="props.theme"
@click="(e) => listeners.click && listeners.click(e)"
circle
@ -16,11 +15,6 @@
<script lang="ts">
import N8nButton from '../N8nButton';
const iconSizeMap = {
large: 'medium',
xlarge: 'large',
};
export default {
name: 'n8n-icon-button',
components: {
@ -36,8 +30,6 @@ export default {
size: {
type: String,
default: 'medium',
validator: (value: string): boolean =>
['small', 'medium', 'large', 'xlarge'].indexOf(value) !== -1,
},
loading: {
type: Boolean,
@ -55,6 +47,5 @@ export default {
type: String,
},
},
iconSizeMap,
};
</script>

View file

@ -12,9 +12,6 @@ export declare class N8nIconButton extends N8nComponent {
/** Button size */
size: N8nComponentSize | 'xlarge';
/** icon size */
iconSize: N8nComponentSize;
/** Determine whether it's loading */
loading: boolean;

View file

@ -1,13 +1,13 @@
<template functional>
<div :class="$style.inputLabel">
<div :class="props.label ? $style.label: ''">
<component v-if="props.label" :is="$options.components.N8nText" :bold="true">
<div :class="{[$style.inputLabelContainer]: !props.labelHoverableOnly}">
<div :class="{[$style.inputLabel]: props.labelHoverableOnly, [$options.methods.getLabelClass(props, $style)]: true}">
<component v-if="props.label" :is="$options.components.N8nText" :bold="props.bold" :size="props.size" :compact="!props.underline">
{{ props.label }}
<component :is="$options.components.N8nText" color="primary" :bold="true" v-if="props.required">*</component>
<component :is="$options.components.N8nText" color="primary" :bold="props.bold" :size="props.size" v-if="props.required">*</component>
</component>
<span :class="$style.infoIcon" v-if="props.tooltipText">
<span :class="[$style.infoIcon, props.showTooltip ? $style.showIcon: $style.hiddenIcon]" v-if="props.tooltipText">
<component :is="$options.components.N8nTooltip" placement="top" :popper-class="$style.tooltipPopper">
<component :is="$options.components.N8nIcon" icon="question-circle" />
<component :is="$options.components.N8nIcon" icon="question-circle" size="small" />
<div slot="content" v-html="$options.methods.addTargetBlank(props.tooltipText)"></div>
</component>
</span>
@ -40,31 +40,97 @@ export default {
required: {
type: Boolean,
},
bold: {
type: Boolean,
default: true,
},
size: {
type: String,
default: 'medium',
validator: (value: string): boolean =>
['small', 'medium'].includes(value),
},
underline: {
type: Boolean,
},
showTooltip: {
type: Boolean,
},
labelHoverableOnly: {
type: Boolean,
default: false,
},
},
methods: {
addTargetBlank,
getLabelClass(props: {label: string, size: string, underline: boolean}, $style: any) {
if (!props.label) {
return '';
}
if (props.underline) {
return $style[`label-${props.size}-underline`];
}
return $style[`label-${props.size}`];
},
},
};
</script>
<style lang="scss" module>
.inputLabel {
&:hover {
--info-icon-display: inline-block;
.inputLabelContainer:hover {
> div > .infoIcon {
display: inline-block;
}
}
.label {
margin-bottom: var(--spacing-2xs);
* {
margin-right: var(--spacing-4xs);
.inputLabel:hover {
> .infoIcon {
display: inline-block;
}
}
.infoIcon {
color: var(--color-text-light);
display: var(--info-icon-display, none);
}
.showIcon {
display: inline-block;
}
.hiddenIcon {
display: none;
}
.label {
* {
margin-right: var(--spacing-5xs);
}
}
.label-small {
composes: label;
margin-bottom: var(--spacing-4xs);
}
.label-medium {
composes: label;
margin-bottom: var(--spacing-2xs);
}
.underline {
border-bottom: var(--border-base);
}
.label-small-underline {
composes: label-small;
composes: underline;
}
.label-medium-underline {
composes: label-medium;
composes: underline;
}
.tooltipPopper {

View file

@ -16,7 +16,7 @@ export default Vue.extend({
size: {
type: String,
default: 'medium',
validator: (value: string): boolean => ['large', 'medium', 'small'].includes(value),
validator: (value: string): boolean => ['mini', 'small', 'medium', 'large', 'xlarge'].includes(value),
},
color: {
type: String,
@ -26,16 +26,23 @@ export default Vue.extend({
type: String,
validator: (value: string): boolean => ['right', 'left', 'center'].includes(value),
},
compact: {
type: Boolean,
default: false,
},
},
methods: {
getClass(props: {size: string, bold: boolean}) {
return `body-${props.size}${props.bold ? '-bold' : '-regular'}`;
},
getStyles(props: {color: string, align: string}) {
getStyles(props: {color: string, align: string, compact: false}) {
const styles = {} as any;
if (props.color) {
styles.color = `var(--color-${props.color})`;
}
if (props.compact) {
styles['line-height'] = 1;
}
if (props.align) {
styles['text-align'] = props.align;
}
@ -54,6 +61,22 @@ export default Vue.extend({
font-weight: var(--font-weight-regular);
}
.body-xlarge {
font-size: var(--font-size-xl);
line-height: var(--font-line-height-xloose);
}
.body-xlarge-regular {
composes: regular;
composes: body-xlarge;
}
.body-xlarge-bold {
composes: bold;
composes: body-xlarge;
}
.body-large {
font-size: var(--font-size-m);
line-height: var(--font-line-height-xloose);

View file

@ -7,4 +7,4 @@ export declare class N8nComponent extends Vue {
}
/** Component size definition for button, input, etc */
export type N8nComponentSize = 'large' | 'medium' | 'small';
export type N8nComponentSize = 'xlarge' | 'large' | 'medium' | 'small' | 'mini';

View file

@ -83,6 +83,17 @@ $loading-overlay-background-color: rgba(255, 255, 255, 0.35);
--button-border-radius: 50%;
}
@include mixins.m(mini) {
--button-padding-vertical: var(--spacing-4xs);
--button-padding-horizontal: var(--spacing-2xs);
--button-font-size: var(--font-size-2xs);
@include mixins.when(circle) {
--button-padding-vertical: var(--spacing-4xs);
--button-padding-horizontal: var(--spacing-4xs);
}
}
@include mixins.m(small) {
--button-padding-vertical: var(--spacing-3xs);
--button-padding-horizontal: var(--spacing-xs);
@ -104,4 +115,15 @@ $loading-overlay-background-color: rgba(255, 255, 255, 0.35);
--button-padding-horizontal: var(--spacing-2xs);
}
}
@include mixins.m(xlarge) {
--button-padding-vertical: var(--spacing-xs);
--button-padding-horizontal: var(--spacing-s);
--button-font-size: var(--font-size-m);
@include mixins.when(circle) {
--button-padding-vertical: var(--spacing-xs);
--button-padding-horizontal: var(--spacing-xs);
}
}
}

View file

@ -68,8 +68,6 @@
@include mixins.e(body) {
padding: var.$dialog-padding-primary;
color: var(--color-text-base);
font-size: var.$dialog-content-font-size;
word-break: break-all;
}
@include mixins.e(footer) {

View file

@ -5,7 +5,7 @@
@include mixins.e(header) {
padding: 0;
position: relative;
margin: 0 0 15px;
margin: 0;
}
@include mixins.e(active-bar) {
position: absolute;

View file

@ -67,6 +67,7 @@ export default mixins(
<style scoped lang="scss">
.n8n-about {
font-size: var(--font-size-s);
.el-row {
padding: 0.25em 0;
}

View file

@ -1,13 +1,12 @@
<template>
<div v-if="dialogVisible">
<el-dialog :visible="dialogVisible" append-to-body :close-on-click-modal="false" width="80%" :title="`Edit ${parameter.displayName}`" :before-close="closeDialog">
<div class="text-editor-wrapper ignore-key-press">
<div class="editor-description">
{{parameter.displayName}}:
</div>
<div class="text-editor" @keydown.stop>
<div class="ignore-key-press">
<n8n-input-label :label="parameter.displayName">
<div :class="$style.editor" @keydown.stop>
<prism-editor :lineNumbers="true" :code="value" :readonly="isReadOnly" @change="valueChanged" language="js"></prism-editor>
</div>
</n8n-input-label>
</div>
</el-dialog>
</div>
@ -53,9 +52,8 @@ export default mixins(
});
</script>
<style scoped>
.editor-description {
font-weight: bold;
padding: 0 0 0.5em 0.2em;;
<style lang="scss" module>
.editor {
font-size: var(--font-size-s);
}
</style>

View file

@ -2,10 +2,10 @@
<div @keydown.stop class="collection-parameter">
<div class="collection-parameter-wrapper">
<div v-if="getProperties.length === 0" class="no-items-exist">
Currently no properties exist
<n8n-text size="small">Currently no properties exist</n8n-text>
</div>
<parameter-input-list :parameters="getProperties" :nodeValues="nodeValues" :path="path" :hideDelete="hideDelete" @valueChanged="valueChanged" />
<parameter-input-list :parameters="getProperties" :nodeValues="nodeValues" :path="path" :hideDelete="hideDelete" :indent="true" @valueChanged="valueChanged" />
<div v-if="parameterOptions.length > 0 && !isReadOnly" class="param-options">
<n8n-button
@ -184,14 +184,14 @@ export default mixins(
<style lang="scss">
.collection-parameter {
padding-left: 2em;
padding-left: var(--spacing-s);
.param-options {
padding-top: 0.5em;
margin-top: var(--spacing-xs);
}
.no-items-exist {
margin: 0.8em 0 0.4em 0;
margin: var(--spacing-xs) 0;
}
.option {
position: relative;

View file

@ -53,6 +53,7 @@ export default mixins(copyPaste, showMessage).extend({
span {
font-family: Monaco, Consolas;
line-height: 1.5;
font-size: var(--font-size-s);
}
padding: var(--spacing-xs);

View file

@ -24,7 +24,7 @@
<div :class="$style.credActions">
<n8n-icon-button
v-if="currentCredential"
size="medium"
size="small"
title="Delete"
icon="trash"
type="text"

View file

@ -2,7 +2,7 @@
<div :class="$style.container">
<el-row>
<el-col :span="8" :class="$style.accessLabel">
<span>Allow use by</span>
<n8n-text :compact="true" :bold="true">Allow use by</n8n-text>
</el-col>
<el-col :span="16">
<div
@ -20,26 +20,26 @@
</el-row>
<el-row v-if="currentCredential">
<el-col :span="8" :class="$style.label">
<span>Created</span>
<n8n-text :compact="true" :bold="true">Created</n8n-text>
</el-col>
<el-col :span="16" :class="$style.valueLabel">
<TimeAgo :date="currentCredential.createdAt" :capitalize="true" />
<n8n-text :compact="true"><TimeAgo :date="currentCredential.createdAt" :capitalize="true" /></n8n-text>
</el-col>
</el-row>
<el-row v-if="currentCredential">
<el-col :span="8" :class="$style.label">
<span>Last modified</span>
<n8n-text :compact="true" :bold="true">Last modified</n8n-text>
</el-col>
<el-col :span="16" :class="$style.valueLabel">
<TimeAgo :date="currentCredential.updatedAt" :capitalize="true" />
<n8n-text :compact="true"><TimeAgo :date="currentCredential.updatedAt" :capitalize="true" /></n8n-text>
</el-col>
</el-row>
<el-row v-if="currentCredential">
<el-col :span="8" :class="$style.label">
<span>ID</span>
<n8n-text :compact="true" :bold="true">ID</n8n-text>
</el-col>
<el-col :span="16" :class="$style.valueLabel">
<span>{{currentCredential.id}}</span>
<n8n-text :compact="true">{{currentCredential.id}}</n8n-text>
</el-col>
</el-row>
</div>

View file

@ -26,8 +26,8 @@
width="120">
<template slot-scope="scope">
<div class="cred-operations">
<n8n-icon-button title="Edit Credentials" @click.stop="editCredential(scope.row)" icon="pen" />
<n8n-icon-button title="Delete Credentials" @click.stop="deleteCredential(scope.row)" icon="trash" />
<n8n-icon-button title="Edit Credentials" @click.stop="editCredential(scope.row)" size="small" icon="pen" />
<n8n-icon-button title="Delete Credentials" @click.stop="deleteCredential(scope.row)" size="small" icon="trash" />
</div>
</template>
</el-table-column>

View file

@ -3,7 +3,7 @@
:visible="!!node"
:before-close="close"
:custom-class="`classic data-display-wrapper`"
width="80%"
width="85%"
append-to-body
@opened="showDocumentHelp = true"
>

View file

@ -35,7 +35,7 @@
<div class="selection-options">
<span v-if="checkAll === true || isIndeterminate === true">
Selected: {{numSelected}} / <span v-if="finishedExecutionsCountEstimated === true">~</span>{{finishedExecutionsCount}}
<n8n-icon-button title="Delete Selected" icon="trash" size="small" @click="handleDeleteSelected" />
<n8n-icon-button title="Delete Selected" icon="trash" size="mini" @click="handleDeleteSelected" />
</span>
</div>
@ -101,7 +101,7 @@
v-if="scope.row.stoppedAt !== undefined && !scope.row.finished && scope.row.retryOf === undefined && scope.row.retrySuccessId === undefined && !scope.row.waitTill"
type="light"
:theme="scope.row.stoppedAt === null ? 'warning': 'danger'"
size="small"
size="mini"
title="Retry execution"
icon="redo"
/>
@ -134,10 +134,10 @@
<template slot-scope="scope">
<div class="actions-container">
<span v-if="scope.row.stoppedAt === undefined || scope.row.waitTill">
<n8n-icon-button icon="stop" title="Stop Execution" @click.stop="stopExecution(scope.row.id)" :loading="stoppingExecutions.includes(scope.row.id)" />
<n8n-icon-button icon="stop" size="small" title="Stop Execution" @click.stop="stopExecution(scope.row.id)" :loading="stoppingExecutions.includes(scope.row.id)" />
</span>
<span v-if="scope.row.stoppedAt !== undefined && scope.row.id" >
<n8n-icon-button icon="folder-open" title="Open Past Execution" @click.stop="displayExecution(scope.row)" />
<n8n-icon-button icon="folder-open" size="small" title="Open Past Execution" @click.stop="displayExecution(scope.row)" />
</span>
</div>
</template>
@ -711,12 +711,10 @@ export default mixins(
position: relative;
display: inline-block;
padding: 0 10px;
height: 22.6px;
line-height: 22.6px;
border-radius: 15px;
text-align: center;
font-weight: 400;
font-size: 12px;
font-size: var(--font-size-s);
&.error {
background-color: var(--color-danger-tint-1);

View file

@ -143,6 +143,7 @@ export default mixins(
.el-dialog__body {
padding: 0;
font-size: var(--font-size-s);
}
.right-side {

View file

@ -1,13 +1,16 @@
<template>
<div @keydown.stop class="fixed-collection-parameter">
<div v-if="getProperties.length === 0" class="no-items-exist">
Currently no items exist
<n8n-text size="small">Currently no items exist</n8n-text>
</div>
<div v-for="property in getProperties" :key="property.name" class="fixed-collection-parameter-property">
<div v-if="property.displayName === '' || parameter.options.length === 1"></div>
<div v-else class="parameter-name" :title="property.displayName">{{property.displayName}}:</div>
<n8n-input-label
:label="property.displayName === '' || parameter.options.length === 1 ? '' : property.displayName"
:underline="true"
:labelHoverableOnly="true"
size="small"
>
<div v-if="multipleValues === true">
<div v-for="(value, index) in values[property.name]" :key="property.name + index" class="parameter-item">
<div class="parameter-item-wrapper">
@ -30,6 +33,7 @@
<parameter-input-list :parameters="property.values" :nodeValues="nodeValues" :path="getPropertyPath(property.name)" class="parameter-item" @valueChanged="valueChanged" :hideDelete="true" />
</div>
</div>
</n8n-input-label>
</div>
<div v-if="parameterOptions.length > 0 && !isReadOnly">
@ -221,16 +225,11 @@ export default mixins(genericHelpers)
<style scoped lang="scss">
.fixed-collection-parameter {
padding: 0 0 0 1em;
padding-left: var(--spacing-s);
}
.fixed-collection-parameter-property {
margin: 0.5em 0;
padding: 0.5em 0;
.parameter-name {
border-bottom: 1px solid #999;
}
margin: var(--spacing-xs) 0;
}
.delete-option {
@ -244,28 +243,33 @@ export default mixins(genericHelpers)
height: 100%;
}
.parameter-item-wrapper:hover > .delete-option {
.parameter-item:hover > .parameter-item-wrapper > .delete-option {
display: block;
}
.parameter-item {
position: relative;
padding: 0 0 0 1em;
margin: 0.6em 0 0.5em 0.1em;
+ .parameter-item {
.parameter-item-wrapper {
padding-top: 0.5em;
border-top: 1px dashed #999;
.delete-option {
top: 14px;
}
}
}
}
.no-items-exist {
margin: 0.8em 0;
margin: var(--spacing-xs) 0;
}
.sort-icon {
display: flex;
flex-direction: column;
margin-left: 1px;
margin-top: .5em;
}
</style>

View file

@ -1,13 +1,12 @@
<template>
<div @keydown.stop class="duplicate-parameter">
<div class="parameter-name">
{{parameter.displayName}}:
<n8n-tooltip v-if="parameter.description" class="parameter-info" placement="top" >
<div slot="content" v-html="addTargetBlank(parameter.description)"></div>
<font-awesome-icon icon="question-circle" />
</n8n-tooltip>
</div>
<n8n-input-label
:label="parameter.displayName"
:tooltipText="parameter.description"
:underline="true"
:labelHoverableOnly="true"
size="small"
>
<div v-for="(value, index) in values" :key="index" class="duplicate-parameter-item" :class="parameter.type">
<div class="delete-item clickable" v-if="!isReadOnly">
@ -27,11 +26,12 @@
<div class="add-item-wrapper">
<div v-if="values && Object.keys(values).length === 0 || isReadOnly" class="no-items-exist">
Currently no items exist
<n8n-text size="small">Currently no items exist</n8n-text>
</div>
<n8n-button v-if="!isReadOnly" fullWidth @click="addItem()" :label="addButtonText" />
</div>
</n8n-input-label>
</div>
</template>
@ -48,7 +48,6 @@ import { get } from 'lodash';
import { genericHelpers } from '@/components/mixins/genericHelpers';
import mixins from 'vue-typed-mixins';
import { addTargetBlank } from './helpers';
export default mixins(genericHelpers)
.extend({
@ -75,7 +74,6 @@ export default mixins(genericHelpers)
},
},
methods: {
addTargetBlank,
addItem () {
const name = this.getPath();
let currentValue = get(this.nodeValues, name);
@ -134,11 +132,7 @@ export default mixins(genericHelpers)
<style scoped lang="scss">
.duplicate-parameter-item ~.add-item-wrapper {
margin: 1.5em 0 0em 0em;
}
.add-item-wrapper {
margin: 0.5em 0 0em 2em;
margin-top: var(--spacing-xs);
}
.delete-item {
@ -149,23 +143,15 @@ export default mixins(genericHelpers)
z-index: 999;
color: #f56c6c;
width: 15px;
font-size: var(--font-size-2xs);
:hover {
color: #ff0000;
}
}
.duplicate-parameter {
margin-top: 0.5em;
.parameter-name {
border-bottom: 1px solid #999;
}
}
::v-deep .duplicate-parameter-item {
position: relative;
margin-top: 0.5em;
padding-top: 0.5em;
.multi > .delete-item{
top: 0.1em;
@ -179,12 +165,12 @@ export default mixins(genericHelpers)
::v-deep .duplicate-parameter-item + .duplicate-parameter-item {
.collection-parameter-wrapper {
border-top: 1px dashed #999;
padding-top: 0.5em;
margin-top: var(--spacing-xs);
}
}
.no-items-exist {
margin: 0 0 1em 0;
margin: var(--spacing-xs) 0;
}
</style>

View file

@ -1,17 +1,18 @@
<template>
<div v-if="credentialTypesNodeDescriptionDisplayed.length" class="node-credentials">
<div class="headline">
Credentials
<div v-if="credentialTypesNodeDescriptionDisplayed.length" :class="$style.container">
<div v-for="credentialTypeDescription in credentialTypesNodeDescriptionDisplayed" :key="credentialTypeDescription.name">
<n8n-input-label
:label="`Credential for ${credentialTypeNames[credentialTypeDescription.name]}`"
:bold="false"
size="small"
:set="issues = getIssues(credentialTypeDescription.name)"
>
<div v-if="isReadOnly">
<n8n-input disabled :value="selected && selected[credentialTypeDescription.name] && selected[credentialTypeDescription.name].name" size="small" />
</div>
<div v-for="credentialTypeDescription in credentialTypesNodeDescriptionDisplayed" :key="credentialTypeDescription.name" class="credential-data">
<el-row class="credential-parameter-wrapper">
<el-col :span="10" class="parameter-name">
{{credentialTypeNames[credentialTypeDescription.name]}}:
</el-col>
<el-col v-if="!isReadOnly" :span="12" class="parameter-value" :class="getIssues(credentialTypeDescription.name).length?'has-issues':''">
<div :style="credentialInputWrapperStyle(credentialTypeDescription.name)">
<div :class="issues.length ? $style.hasIssues : $style.input" v-else >
<n8n-select :value="getSelectedId(credentialTypeDescription.name)" @change="(value) => onCredentialSelected(credentialTypeDescription.name, value)" placeholder="Select Credential" size="small">
<n8n-option
v-for="(item) in credentialOptions[credentialTypeDescription.name]"
@ -26,26 +27,20 @@
>
</n8n-option>
</n8n-select>
</div>
<div class="credential-issues">
<div :class="$style.warning" v-if="issues.length">
<n8n-tooltip placement="top" >
<div slot="content" v-html="'Issues:<br />&nbsp;&nbsp;- ' + getIssues(credentialTypeDescription.name).join('<br />&nbsp;&nbsp;- ')"></div>
<div slot="content" v-html="'Issues:<br />&nbsp;&nbsp;- ' + issues.join('<br />&nbsp;&nbsp;- ')"></div>
<font-awesome-icon icon="exclamation-triangle" />
</n8n-tooltip>
</div>
</el-col>
<el-col v-if="!isReadOnly" :span="2" class="parameter-value credential-action">
<font-awesome-icon v-if="isCredentialExisting(credentialTypeDescription.name)" icon="pen" @click="editCredential(credentialTypeDescription.name)" class="update-credentials clickable" title="Update Credentials" />
</el-col>
<el-col v-if="isReadOnly" :span="14" class="readonly-container" >
<n8n-input disabled :value="selected && selected[credentialTypeDescription.name] && selected[credentialTypeDescription.name].name" size="small" />
</el-col>
</el-row>
<div :class="$style.edit" v-if="selected[credentialTypeDescription.name] && isCredentialExisting(credentialTypeDescription.name)">
<font-awesome-icon icon="pen" @click="editCredential(credentialTypeDescription.name)" class="clickable" title="Update Credentials" />
</div>
</div>
</n8n-input-label>
</div>
</div>
</template>
@ -285,62 +280,39 @@ export default mixins(
});
</script>
<style lang="scss">
<style lang="scss" module>
.container {
margin: var(--spacing-xs) 0;
.node-credentials {
padding-bottom: 1em;
margin: 0.5em;
border-bottom: 1px solid #ccc;
> * {
margin-bottom: var(--spacing-xs);
}
}
.credential-issues {
display: none;
width: 20px;
text-align: right;
float: right;
.warning {
min-width: 20px;
margin-left: 5px;
color: #ff8080;
font-size: 1.2em;
margin-top: 3px;
font-size: var(--font-size-s);
}
.credential-data + .credential-data {
margin-top: 1em;
}
.has-issues {
.credential-issues {
display: inline-block;
}
}
.headline {
font-weight: bold;
margin-bottom: 0.7em;
}
.credential-parameter-wrapper {
display: flex;
align-items: center;
}
.parameter-name {
font-weight: 400;
}
.parameter-value {
display: flex;
align-items: center;
}
.credential-action {
.edit {
display: flex;
justify-content: center;
align-items: center;
color: var(--color-text-base);
min-width: 20px;
margin-left: 5px;
font-size: var(--font-size-s);
}
.readonly-container {
padding-right: 0.5em;
}
.input {
display: flex;
align-items: center;
}
.hasIssues {
composes: input;
--input-border-color: var(--color-danger);
}
</style>

View file

@ -10,10 +10,9 @@
</n8n-tooltip>
</a>
</span>
<span v-else>No node active</span>
</div>
<div class="node-is-not-valid" v-if="node && !nodeValid">
The node is not valid as its type "{{node.type}}" is unknown.
<n8n-text>The node is not valid as its type "{{node.type}}" is unknown.</n8n-text>
</div>
<div class="node-parameters-wrapper" v-if="node && nodeValid">
<el-tabs stretch @tab-click="handleTabClick">
@ -21,8 +20,8 @@
<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">
This node does not have any parameters.
<div v-if="parametersNoneSetting.length === 0" class="no-parameters">
<n8n-text>This node does not have any parameters.</n8n-text>
</div>
</el-tab-pane>
<el-tab-pane label="Settings">
@ -521,7 +520,10 @@ export default mixins(
overflow: hidden;
min-width: 350px;
max-width: 350px;
font-size: var(--font-size-s);
.no-parameters {
margin-top: var(--spacing-xs);
}
.header-side-menu {
padding: 1em 0 1em 1.8em;
@ -547,10 +549,10 @@ export default mixins(
.node-parameters-wrapper {
height: 100%;
font-size: .9em;
.el-tabs__header {
background-color: #fff5f2;
margin-bottom: 0;
}
.el-tabs {
@ -561,13 +563,13 @@ export default mixins(
padding-bottom: 180px;
.el-tab-pane {
margin: 0 1em;
margin: 0 var(--spacing-s);
}
}
}
.el-tabs__nav {
padding-bottom: 1em;
padding-bottom: var(--spacing-xs);
}
.add-option {
@ -621,14 +623,6 @@ export default mixins(
padding: 0 1em;
}
.parameter-name {
line-height: 1.5;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
align-self: center;
}
.color-reset-button-wrapper {
position: relative;

View file

@ -128,14 +128,14 @@ export default mixins(
<style scoped lang="scss">
.webhoooks {
padding: 0.7em;
font-size: 0.9em;
margin: 0.5em 0;
padding-bottom: var(--spacing-xs);
margin: var(--spacing-xs) 0;
border-bottom: 1px solid #ccc;
.headline {
color: $--color-primary;
font-weight: 600;
font-size: var(--font-size-2xs);
}
}
@ -154,8 +154,8 @@ export default mixins(
margin-left: 5px;
text-align: center;
border-radius: 2px;
font-size: 0.8em;
font-weight: 600;
font-size: var(--font-size-2xs);
font-weight: var(--font-weight-bold);
color: #fff;
}
@ -175,11 +175,11 @@ export default mixins(
.url-field {
display: inline-block;
width: calc(100% - 60px);
margin-left: 50px;
margin-left: 55px;
}
.url-selection {
margin-top: 1em;
margin-top: var(--spacing-xs);
}
.minimize-button {
@ -205,12 +205,11 @@ export default mixins(
position: relative;
top: 0;
width: 100%;
font-size: 0.9em;
font-size: var(--font-size-2xs);
white-space: normal;
overflow: visible;
text-overflow: initial;
color: #404040;
padding: 0.5em;
text-align: left;
direction: ltr;
word-break: break-all;
@ -219,9 +218,8 @@ export default mixins(
.webhook-wrapper {
line-height: 1.5;
position: relative;
margin: 1em 0 0.5em 0;
margin-top: var(--spacing-xs);
background-color: #fff;
padding: 2px 0;
border-radius: 3px;
}
</style>

View file

@ -16,7 +16,7 @@
<code-edit :dialogVisible="codeEditDialogVisible" :value="value" :parameter="parameter" @closeDialog="closeCodeEditDialog" @valueChanged="expressionUpdated"></code-edit>
<text-edit :dialogVisible="textEditDialogVisible" :value="value" :parameter="parameter" @closeDialog="closeTextEditDialog" @valueChanged="expressionUpdated"></text-edit>
<div v-if="isEditor === true" class="clickable" @click="displayEditDialog()">
<div v-if="isEditor === true" class="code-edit clickable" @click="displayEditDialog()">
<prism-editor v-if="!codeEditDialogVisible" :lineNumbers="true" :readonly="true" :code="displayValue" language="js"></prism-editor>
</div>
@ -515,8 +515,9 @@ export default mixins(
const classes = [];
const rows = this.getArgument('rows');
const isTextarea = this.parameter.type === 'string' && rows !== undefined;
const isSwitch = this.parameter.type === 'boolean' && !this.isValueExpression;
if (!isTextarea) {
if (!isTextarea && !isSwitch) {
classes.push('parameter-value-container');
}
if (this.isValueExpression) {
@ -664,6 +665,8 @@ export default mixins(
(this.$refs.inputField.$el.querySelector(this.getStringInputType === 'textarea' ? 'textarea' : 'input') as HTMLInputElement).focus();
}
});
this.$emit('focus');
},
rgbaToHex (value: string): string | null {
// Convert rgba to hex from: https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
@ -775,8 +778,12 @@ export default mixins(
<style scoped lang="scss">
.code-edit {
font-size: var(--font-size-xs);
}
.switch-input {
margin: 5px 0;
margin: 2px 0;
}
.parameter-value-container {
@ -804,7 +811,7 @@ export default mixins(
text-align: right;
float: right;
color: #ff8080;
font-size: 1.2em;
font-size: var(--font-size-s);
}
::v-deep .color-input {

View file

@ -3,6 +3,7 @@
:label="parameter.displayName"
:tooltipText="parameter.description"
:required="parameter.required"
:showTooltip="focused"
>
<parameter-input
:parameter="parameter"
@ -12,7 +13,7 @@
:displayOptions="true"
:documentationUrl="documentationUrl"
:errorHighlight="showRequiredErrors"
@focus="onFocus"
@blur="onBlur"
@textInput="valueChanged"
@valueChanged="valueChanged"
@ -48,7 +49,8 @@ export default Vue.extend({
},
data() {
return {
blurred: false,
focused: false,
blurredEver: false,
};
},
computed: {
@ -57,7 +59,7 @@ export default Vue.extend({
return false;
}
if (this.blurred || this.showValidationWarnings) {
if (this.blurredEver || this.showValidationWarnings) {
if (this.$props.parameter.type === 'string') {
return !this.value;
}
@ -71,8 +73,12 @@ export default Vue.extend({
},
},
methods: {
onFocus() {
this.focused = true;
},
onBlur() {
this.blurred = true;
this.blurredEver = true;
this.focused = false;
},
valueChanged(parameterData: IUpdateInformation) {
this.$emit('change', parameterData);

View file

@ -1,16 +1,22 @@
<template>
<el-row class="parameter-wrapper" :class="{'multi-line': isMultiLineParameter}">
<el-col :span="isMultiLineParameter ? 24 : 10" class="parameter-name" :class="{'multi-line': isMultiLineParameter}">
<span class="title" :title="parameter.displayName">{{parameter.displayName}}</span>:
<n8n-tooltip class="parameter-info" placement="top" v-if="parameter.description" >
<div slot="content" v-html="addTargetBlank(parameter.description)"></div>
<font-awesome-icon icon="question-circle" />
</n8n-tooltip>
</el-col>
<el-col :span="isMultiLineParameter ? 24 : 14" class="parameter-value">
<parameter-input :parameter="parameter" :value="value" :displayOptions="displayOptions" :path="path" :isReadOnly="isReadOnly" @valueChanged="valueChanged" inputSize="small" />
</el-col>
</el-row>
<n8n-input-label
:label="parameter.displayName"
:tooltipText="parameter.description"
:showTooltip="focused"
:bold="false"
size="small"
>
<parameter-input
:parameter="parameter"
:value="value"
:displayOptions="displayOptions"
:path="path"
:isReadOnly="isReadOnly"
@valueChanged="valueChanged"
@focus="focused = true"
@blur="focused = false"
inputSize="small" />
</n8n-input-label>
</template>
<script lang="ts">
@ -21,7 +27,6 @@ import {
} from '@/Interface';
import ParameterInput from '@/components/ParameterInput.vue';
import { addTargetBlank } from './helpers';
export default Vue
.extend({
@ -29,21 +34,10 @@ export default Vue
components: {
ParameterInput,
},
computed: {
isMultiLineParameter () {
if (this.level > 4) {
return true;
}
const rows = this.getArgument('rows');
if (rows !== undefined && rows > 1) {
return true;
}
return false;
},
level (): number {
return this.path.split('.').length;
},
data() {
return {
focused: false,
};
},
props: [
'displayOptions',
@ -53,7 +47,6 @@ export default Vue
'value',
],
methods: {
addTargetBlank,
getArgument (argumentName: string): string | number | boolean | undefined {
if (this.parameter.typeOptions === undefined) {
return undefined;
@ -71,46 +64,3 @@ export default Vue
},
});
</script>
<style lang="scss">
.parameter-wrapper {
display: flex;
align-items: center;
&.multi-line {
flex-direction: column;
}
.option {
margin: 1em;
}
.parameter-info {
background-color: #ffffffaa;
display: none;
position: absolute;
right: 2px;
top: 1px;
}
.parameter-name {
position: relative;
&:hover {
.parameter-info {
display: inline;
}
}
&.multi-line {
line-height: 1.5em;
}
}
.title {
font-weight: 400;
}
}
</style>

View file

@ -1,6 +1,6 @@
<template>
<div class="paramter-input-list-wrapper">
<div v-for="parameter in filteredParameters" :key="parameter.name">
<div v-for="parameter in filteredParameters" :key="parameter.name" :class="{indent}">
<div
v-if="multipleValues(parameter) === true && parameter.type !== 'fixedCollection'"
class="parameter-item"
@ -14,13 +14,16 @@
/>
</div>
<div v-else-if="parameter.type === 'notice'" v-html="parameter.displayName" class="parameter-item parameter-notice"></div>
<div v-else-if="parameter.type === 'notice'" class="parameter-item parameter-notice">
<n8n-text size="small">
<span v-html="parameter.displayName"></span>
</n8n-text>
</div>
<div
v-else-if="['collection', 'fixedCollection'].includes(parameter.type)"
class="multi-parameter"
>
<div class="parameter-name" :title="parameter.displayName">
<div class="delete-option clickable" title="Delete" v-if="hideDelete !== true && !isReadOnly">
<font-awesome-icon
icon="trash"
@ -29,13 +32,13 @@
@click="deleteOption(parameter.name)"
/>
</div>
{{parameter.displayName}}:
<n8n-tooltip placement="top" class="parameter-info" v-if="parameter.description" >
<div slot="content" v-html="addTargetBlank(parameter.description)"></div>
<font-awesome-icon icon="question-circle"/>
</n8n-tooltip>
</div>
<div>
<n8n-input-label
:label="parameter.displayName"
:tooltipText="parameter.description"
size="small"
:underline="true"
:labelHoverableOnly="true"
>
<collection-parameter
v-if="parameter.type === 'collection'"
:parameter="parameter"
@ -52,7 +55,7 @@
:path="getPath(parameter.name)"
@valueChanged="valueChanged"
/>
</div>
</n8n-input-label>
</div>
<div v-else-if="displayNodeParameter(parameter)" class="parameter-item">
@ -93,8 +96,6 @@ import { genericHelpers } from '@/components/mixins/genericHelpers';
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
import ParameterInputFull from '@/components/ParameterInputFull.vue';
import { addTargetBlank } from './helpers';
import { get, set } from 'lodash';
import mixins from 'vue-typed-mixins';
@ -114,6 +115,7 @@ export default mixins(
'parameters', // INodeProperties
'path', // string
'hideDelete', // boolean
'indent',
],
computed: {
filteredParameters (): INodeProperties[] {
@ -124,7 +126,6 @@ export default mixins(
},
},
methods: {
addTargetBlank,
multipleValues (parameter: INodeProperties): boolean {
if (this.getArgument('multipleValues', parameter) === true) {
return true;
@ -260,50 +261,42 @@ export default mixins(
position: absolute;
z-index: 999;
color: #f56c6c;
font-size: var(--font-size-2xs);
&:hover {
color: #ff0000;
}
}
.indent > div {
padding-left: var(--spacing-s);
}
.multi-parameter {
position: relative;
margin: 0.5em 0;
padding: 0.5em 0;
>.parameter-name {
font-weight: 600;
border-bottom: 1px solid #999;
&:hover {
.parameter-info {
display: inline;
}
}
margin: var(--spacing-xs) 0;
.delete-option {
top: 0;
left: -0.9em;
left: 0;
}
.parameter-info {
display: none;
}
}
}
.parameter-item {
position: relative;
margin: 8px 0;
margin: var(--spacing-xs) 0;
>.delete-option {
left: -0.9em;
top: 0.6em;
top: var(--spacing-5xs);
left: 0;
}
}
.parameter-item:hover > .delete-option,
.parameter-name:hover > .delete-option {
.multi-parameter:hover > .delete-option {
display: block;
}
@ -311,9 +304,7 @@ export default mixins(
background-color: #fff5d3;
color: $--custom-font-black;
margin: 0.3em 0;
padding: 0.8em;
line-height: 1.5;
word-break: normal;
padding: 0.7em;
a {
font-weight: var(--font-weight-bold);

View file

@ -17,31 +17,31 @@
<div class="header">
<div class="title-text">
<strong v-if="dataCount < maxDisplayItems">
<n8n-text :bold="true" v-if="dataCount < maxDisplayItems">
Items: {{ dataCount }}
</strong>
</n8n-text>
<div v-else class="title-text">
<strong>Items:</strong>
<n8n-text :bold="true">Items:</n8n-text>
<span class="opts">
<n8n-select size="mini" v-model="maxDisplayItems" @click.stop>
<n8n-option v-for="option in maxDisplayItemsOptions" :label="option" :value="option" :key="option" />
</n8n-select>
</span>/
<strong>{{ dataCount }}</strong>
<n8n-text :bold="true">{{ dataCount }}</n8n-text>
</div>
<n8n-tooltip
v-if="runMetadata"
placement="right"
>
<div slot="content">
<strong>Start Time:</strong> {{runMetadata.startTime}}<br/>
<strong>Execution Time:</strong> {{runMetadata.executionTime}} ms
<n8n-text :bold="true" size="small">Start Time:</n8n-text> {{runMetadata.startTime}}<br/>
<n8n-text :bold="true" size="small">Execution Time:</n8n-text> {{runMetadata.executionTime}} ms
</div>
<font-awesome-icon icon="info-circle" class="primary-color" />
</n8n-tooltip>
<span v-if="maxOutputIndex > 0">
<n8n-text :bold="true" v-if="maxOutputIndex > 0">
| Output:
</span>
</n8n-text>
<span class="opts" v-if="maxOutputIndex > 0" >
<n8n-select size="mini" v-model="outputIndex" @click.stop>
<n8n-option v-for="option in (maxOutputIndex + 1)" :label="getOutputName(option-1)" :value="option -1" :key="option">
@ -49,9 +49,9 @@
</n8n-select>
</span>
<span v-if="maxRunIndex > 0">
<n8n-text :bold="true" v-if="maxRunIndex > 0">
| Data of Execution:
</span>
</n8n-text>
<span class="opts">
<n8n-select v-if="maxRunIndex > 0" size="mini" v-model="runIndex" @click.stop>
<n8n-option v-for="option in (maxRunIndex + 1)" :label="option + '/' + (maxRunIndex+1)" :value="option-1" :key="option">
@ -186,7 +186,7 @@
</span>
<div v-else class="message">
<div>
<strong>No data</strong><br />
<n8n-text :bold="true">No data</n8n-text ><br />
<br />
Data returned by this node will display here<br />
</div>
@ -659,6 +659,7 @@ export default mixins(
overflow-y: auto;
line-height: 1.5;
word-break: normal;
font-size: var(--font-size-s);
.binary-data-row {
display: inline-flex;

View file

@ -2,13 +2,12 @@
<div v-if="dialogVisible">
<el-dialog :visible="dialogVisible" append-to-body width="80%" :title="`Edit ${parameter.displayName}`" :before-close="closeDialog">
<div class="text-editor-wrapper ignore-key-press">
<div class="editor-description">
{{parameter.displayName}}:
</div>
<div class="text-editor" @keydown.stop @keydown.esc="closeDialog()">
<div class="ignore-key-press">
<n8n-input-label :label="parameter.displayName">
<div @keydown.stop @keydown.esc="closeDialog()">
<n8n-input v-model="tempValue" type="textarea" ref="inputField" :value="value" :placeholder="parameter.placeholder" @change="valueChanged" @keydown.stop="noOp" :rows="15" />
</div>
</n8n-input-label>
</div>
</el-dialog>
@ -60,10 +59,3 @@ export default Vue.extend({
},
});
</script>
<style scoped>
.editor-description {
font-weight: bold;
padding: 0 0 0.5em 0.2em;;
}
</style>

View file

@ -516,6 +516,7 @@ export default mixins(
<style scoped lang="scss">
.workflow-settings {
font-size: var(--font-size-s);
.el-row {
padding: 0.25em 0;
}

View file

@ -2,12 +2,6 @@ import dateformat from 'dateformat';
const KEYWORDS_TO_FILTER = ['API', 'OAuth1', 'OAuth2'];
export function addTargetBlank(html: string) {
return html.includes('href=')
? html.replace(/href=/g, 'target="_blank" href=')
: html;
}
export function convertToDisplayDate (epochTime: number) {
return dateformat(epochTime, 'yyyy-mm-dd HH:MM:ss');
}