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

View file

@ -16,13 +16,13 @@
<component <component
:is="$options.components.N8nSpinner" :is="$options.components.N8nSpinner"
v-if="props.loading" v-if="props.loading"
:size="props.iconSize" :size="props.size"
/> />
<component <component
:is="$options.components.N8nIcon" :is="$options.components.N8nIcon"
v-else-if="props.icon" v-else-if="props.icon"
:icon="props.icon" :icon="props.icon"
:size="props.iconSize" :size="props.size"
/> />
</span> </span>
<span v-if="props.label">{{ props.label }}</span> <span v-if="props.label">{{ props.label }}</span>
@ -58,7 +58,7 @@ export default {
type: String, type: String,
default: 'medium', default: 'medium',
validator: (value: string): boolean => validator: (value: string): boolean =>
['small', 'medium', 'large'].indexOf(value) !== -1, ['mini', 'small', 'medium', 'large', 'xlarge'].indexOf(value) !== -1,
}, },
loading: { loading: {
type: Boolean, type: Boolean,
@ -71,9 +71,6 @@ export default {
icon: { icon: {
type: String, type: String,
}, },
iconSize: {
type: String,
},
round: { round: {
type: Boolean, type: Boolean,
default: true, 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 */ /** Button icon, accepts an icon name of font awesome icon component */
icon: string; icon: string;
/** Size of icon */
iconSize: N8nComponentSize;
/** Full width */ /** Full width */
fullWidth: boolean; fullWidth: boolean;

View file

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

View file

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

View file

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

View file

@ -1,13 +1,13 @@
<template functional> <template functional>
<div :class="$style.inputLabel"> <div :class="{[$style.inputLabelContainer]: !props.labelHoverableOnly}">
<div :class="props.label ? $style.label: ''"> <div :class="{[$style.inputLabel]: props.labelHoverableOnly, [$options.methods.getLabelClass(props, $style)]: true}">
<component v-if="props.label" :is="$options.components.N8nText" :bold="true"> <component v-if="props.label" :is="$options.components.N8nText" :bold="props.bold" :size="props.size" :compact="!props.underline">
{{ props.label }} {{ 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> </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.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> <div slot="content" v-html="$options.methods.addTargetBlank(props.tooltipText)"></div>
</component> </component>
</span> </span>
@ -40,31 +40,97 @@ export default {
required: { required: {
type: Boolean, 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: { methods: {
addTargetBlank, 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> </script>
<style lang="scss" module> <style lang="scss" module>
.inputLabel { .inputLabelContainer:hover {
&:hover { > div > .infoIcon {
--info-icon-display: inline-block; display: inline-block;
} }
} }
.label { .inputLabel:hover {
margin-bottom: var(--spacing-2xs); > .infoIcon {
display: inline-block;
* {
margin-right: var(--spacing-4xs);
} }
} }
.infoIcon { .infoIcon {
color: var(--color-text-light); 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 { .tooltipPopper {

View file

@ -16,7 +16,7 @@ export default Vue.extend({
size: { size: {
type: String, type: String,
default: 'medium', default: 'medium',
validator: (value: string): boolean => ['large', 'medium', 'small'].includes(value), validator: (value: string): boolean => ['mini', 'small', 'medium', 'large', 'xlarge'].includes(value),
}, },
color: { color: {
type: String, type: String,
@ -26,16 +26,23 @@ export default Vue.extend({
type: String, type: String,
validator: (value: string): boolean => ['right', 'left', 'center'].includes(value), validator: (value: string): boolean => ['right', 'left', 'center'].includes(value),
}, },
compact: {
type: Boolean,
default: false,
},
}, },
methods: { methods: {
getClass(props: {size: string, bold: boolean}) { getClass(props: {size: string, bold: boolean}) {
return `body-${props.size}${props.bold ? '-bold' : '-regular'}`; 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; const styles = {} as any;
if (props.color) { if (props.color) {
styles.color = `var(--color-${props.color})`; styles.color = `var(--color-${props.color})`;
} }
if (props.compact) {
styles['line-height'] = 1;
}
if (props.align) { if (props.align) {
styles['text-align'] = props.align; styles['text-align'] = props.align;
} }
@ -54,6 +61,22 @@ export default Vue.extend({
font-weight: var(--font-weight-regular); 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 { .body-large {
font-size: var(--font-size-m); font-size: var(--font-size-m);
line-height: var(--font-line-height-xloose); 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 */ /** 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%; --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) { @include mixins.m(small) {
--button-padding-vertical: var(--spacing-3xs); --button-padding-vertical: var(--spacing-3xs);
--button-padding-horizontal: var(--spacing-xs); --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); --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) { @include mixins.e(body) {
padding: var.$dialog-padding-primary; padding: var.$dialog-padding-primary;
color: var(--color-text-base); color: var(--color-text-base);
font-size: var.$dialog-content-font-size;
word-break: break-all;
} }
@include mixins.e(footer) { @include mixins.e(footer) {

View file

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

View file

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

View file

@ -1,13 +1,12 @@
<template> <template>
<div v-if="dialogVisible"> <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"> <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="ignore-key-press">
<div class="editor-description"> <n8n-input-label :label="parameter.displayName">
{{parameter.displayName}}: <div :class="$style.editor" @keydown.stop>
</div> <prism-editor :lineNumbers="true" :code="value" :readonly="isReadOnly" @change="valueChanged" language="js"></prism-editor>
<div class="text-editor" @keydown.stop> </div>
<prism-editor :lineNumbers="true" :code="value" :readonly="isReadOnly" @change="valueChanged" language="js"></prism-editor> </n8n-input-label>
</div>
</div> </div>
</el-dialog> </el-dialog>
</div> </div>
@ -53,9 +52,8 @@ export default mixins(
}); });
</script> </script>
<style scoped> <style lang="scss" module>
.editor-description { .editor {
font-weight: bold; font-size: var(--font-size-s);
padding: 0 0 0.5em 0.2em;;
} }
</style> </style>

View file

@ -2,10 +2,10 @@
<div @keydown.stop class="collection-parameter"> <div @keydown.stop class="collection-parameter">
<div class="collection-parameter-wrapper"> <div class="collection-parameter-wrapper">
<div v-if="getProperties.length === 0" class="no-items-exist"> <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> </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"> <div v-if="parameterOptions.length > 0 && !isReadOnly" class="param-options">
<n8n-button <n8n-button
@ -184,14 +184,14 @@ export default mixins(
<style lang="scss"> <style lang="scss">
.collection-parameter { .collection-parameter {
padding-left: 2em; padding-left: var(--spacing-s);
.param-options { .param-options {
padding-top: 0.5em; margin-top: var(--spacing-xs);
} }
.no-items-exist { .no-items-exist {
margin: 0.8em 0 0.4em 0; margin: var(--spacing-xs) 0;
} }
.option { .option {
position: relative; position: relative;

View file

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

View file

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

View file

@ -2,7 +2,7 @@
<div :class="$style.container"> <div :class="$style.container">
<el-row> <el-row>
<el-col :span="8" :class="$style.accessLabel"> <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>
<el-col :span="16"> <el-col :span="16">
<div <div
@ -20,26 +20,26 @@
</el-row> </el-row>
<el-row v-if="currentCredential"> <el-row v-if="currentCredential">
<el-col :span="8" :class="$style.label"> <el-col :span="8" :class="$style.label">
<span>Created</span> <n8n-text :compact="true" :bold="true">Created</n8n-text>
</el-col> </el-col>
<el-col :span="16" :class="$style.valueLabel"> <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-col>
</el-row> </el-row>
<el-row v-if="currentCredential"> <el-row v-if="currentCredential">
<el-col :span="8" :class="$style.label"> <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>
<el-col :span="16" :class="$style.valueLabel"> <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-col>
</el-row> </el-row>
<el-row v-if="currentCredential"> <el-row v-if="currentCredential">
<el-col :span="8" :class="$style.label"> <el-col :span="8" :class="$style.label">
<span>ID</span> <n8n-text :compact="true" :bold="true">ID</n8n-text>
</el-col> </el-col>
<el-col :span="16" :class="$style.valueLabel"> <el-col :span="16" :class="$style.valueLabel">
<span>{{currentCredential.id}}</span> <n8n-text :compact="true">{{currentCredential.id}}</n8n-text>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,16 +1,22 @@
<template> <template>
<el-row class="parameter-wrapper" :class="{'multi-line': isMultiLineParameter}"> <n8n-input-label
<el-col :span="isMultiLineParameter ? 24 : 10" class="parameter-name" :class="{'multi-line': isMultiLineParameter}"> :label="parameter.displayName"
<span class="title" :title="parameter.displayName">{{parameter.displayName}}</span>: :tooltipText="parameter.description"
<n8n-tooltip class="parameter-info" placement="top" v-if="parameter.description" > :showTooltip="focused"
<div slot="content" v-html="addTargetBlank(parameter.description)"></div> :bold="false"
<font-awesome-icon icon="question-circle" /> size="small"
</n8n-tooltip> >
</el-col> <parameter-input
<el-col :span="isMultiLineParameter ? 24 : 14" class="parameter-value"> :parameter="parameter"
<parameter-input :parameter="parameter" :value="value" :displayOptions="displayOptions" :path="path" :isReadOnly="isReadOnly" @valueChanged="valueChanged" inputSize="small" /> :value="value"
</el-col> :displayOptions="displayOptions"
</el-row> :path="path"
:isReadOnly="isReadOnly"
@valueChanged="valueChanged"
@focus="focused = true"
@blur="focused = false"
inputSize="small" />
</n8n-input-label>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -21,7 +27,6 @@ import {
} from '@/Interface'; } from '@/Interface';
import ParameterInput from '@/components/ParameterInput.vue'; import ParameterInput from '@/components/ParameterInput.vue';
import { addTargetBlank } from './helpers';
export default Vue export default Vue
.extend({ .extend({
@ -29,21 +34,10 @@ export default Vue
components: { components: {
ParameterInput, ParameterInput,
}, },
computed: { data() {
isMultiLineParameter () { return {
if (this.level > 4) { focused: false,
return true; };
}
const rows = this.getArgument('rows');
if (rows !== undefined && rows > 1) {
return true;
}
return false;
},
level (): number {
return this.path.split('.').length;
},
}, },
props: [ props: [
'displayOptions', 'displayOptions',
@ -53,7 +47,6 @@ export default Vue
'value', 'value',
], ],
methods: { methods: {
addTargetBlank,
getArgument (argumentName: string): string | number | boolean | undefined { getArgument (argumentName: string): string | number | boolean | undefined {
if (this.parameter.typeOptions === undefined) { if (this.parameter.typeOptions === undefined) {
return undefined; return undefined;
@ -71,46 +64,3 @@ export default Vue
}, },
}); });
</script> </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> <template>
<div class="paramter-input-list-wrapper"> <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 <div
v-if="multipleValues(parameter) === true && parameter.type !== 'fixedCollection'" v-if="multipleValues(parameter) === true && parameter.type !== 'fixedCollection'"
class="parameter-item" class="parameter-item"
@ -14,28 +14,31 @@
/> />
</div> </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 <div
v-else-if="['collection', 'fixedCollection'].includes(parameter.type)" v-else-if="['collection', 'fixedCollection'].includes(parameter.type)"
class="multi-parameter" class="multi-parameter"
> >
<div class="parameter-name" :title="parameter.displayName"> <div class="delete-option clickable" title="Delete" v-if="hideDelete !== true && !isReadOnly">
<div class="delete-option clickable" title="Delete" v-if="hideDelete !== true && !isReadOnly"> <font-awesome-icon
<font-awesome-icon icon="trash"
icon="trash" class="reset-icon clickable"
class="reset-icon clickable" title="Parameter Options"
title="Parameter Options" @click="deleteOption(parameter.name)"
@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>
<div> <n8n-input-label
:label="parameter.displayName"
:tooltipText="parameter.description"
size="small"
:underline="true"
:labelHoverableOnly="true"
>
<collection-parameter <collection-parameter
v-if="parameter.type === 'collection'" v-if="parameter.type === 'collection'"
:parameter="parameter" :parameter="parameter"
@ -52,7 +55,7 @@
:path="getPath(parameter.name)" :path="getPath(parameter.name)"
@valueChanged="valueChanged" @valueChanged="valueChanged"
/> />
</div> </n8n-input-label>
</div> </div>
<div v-else-if="displayNodeParameter(parameter)" class="parameter-item"> <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 { workflowHelpers } from '@/components/mixins/workflowHelpers';
import ParameterInputFull from '@/components/ParameterInputFull.vue'; import ParameterInputFull from '@/components/ParameterInputFull.vue';
import { addTargetBlank } from './helpers';
import { get, set } from 'lodash'; import { get, set } from 'lodash';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
@ -114,6 +115,7 @@ export default mixins(
'parameters', // INodeProperties 'parameters', // INodeProperties
'path', // string 'path', // string
'hideDelete', // boolean 'hideDelete', // boolean
'indent',
], ],
computed: { computed: {
filteredParameters (): INodeProperties[] { filteredParameters (): INodeProperties[] {
@ -124,7 +126,6 @@ export default mixins(
}, },
}, },
methods: { methods: {
addTargetBlank,
multipleValues (parameter: INodeProperties): boolean { multipleValues (parameter: INodeProperties): boolean {
if (this.getArgument('multipleValues', parameter) === true) { if (this.getArgument('multipleValues', parameter) === true) {
return true; return true;
@ -260,50 +261,42 @@ export default mixins(
position: absolute; position: absolute;
z-index: 999; z-index: 999;
color: #f56c6c; color: #f56c6c;
font-size: var(--font-size-2xs);
&:hover { &:hover {
color: #ff0000; color: #ff0000;
} }
} }
.indent > div {
padding-left: var(--spacing-s);
}
.multi-parameter { .multi-parameter {
position: relative; position: relative;
margin: 0.5em 0; margin: var(--spacing-xs) 0;
padding: 0.5em 0;
>.parameter-name { .delete-option {
font-weight: 600; top: 0;
border-bottom: 1px solid #999; left: 0;
}
&:hover {
.parameter-info {
display: inline;
}
}
.delete-option {
top: 0;
left: -0.9em;
}
.parameter-info {
display: none;
}
.parameter-info {
display: none;
} }
} }
.parameter-item { .parameter-item {
position: relative; position: relative;
margin: 8px 0; margin: var(--spacing-xs) 0;
>.delete-option { >.delete-option {
left: -0.9em; top: var(--spacing-5xs);
top: 0.6em; left: 0;
} }
} }
.parameter-item:hover > .delete-option, .parameter-item:hover > .delete-option,
.parameter-name:hover > .delete-option { .multi-parameter:hover > .delete-option {
display: block; display: block;
} }
@ -311,9 +304,7 @@ export default mixins(
background-color: #fff5d3; background-color: #fff5d3;
color: $--custom-font-black; color: $--custom-font-black;
margin: 0.3em 0; margin: 0.3em 0;
padding: 0.8em; padding: 0.7em;
line-height: 1.5;
word-break: normal;
a { a {
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);

View file

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

View file

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

View file

@ -2,12 +2,6 @@ import dateformat from 'dateformat';
const KEYWORDS_TO_FILTER = ['API', 'OAuth1', 'OAuth2']; 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) { export function convertToDisplayDate (epochTime: number) {
return dateformat(epochTime, 'yyyy-mm-dd HH:MM:ss'); return dateformat(epochTime, 'yyyy-mm-dd HH:MM:ss');
} }