mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
feat(editor): create new workflows page (#4267)
* feat(editor): extract credentials view into reusable layout components for workflows view * feat(editor): add workflow card and start work on empty state * feat: add hoverable card and finish workflows empty state * fix: undo workflows response interface changes * chore: fix linting issues. * fix: remove enterprise sharing env schema * fix(editor): fix workflows resource view when sharing is enabled * fix: change owner tag design and order * feat: add personalization survey on workflows page * fix: update component snapshots * feat: refactored workflow card to use workflow-activator properly * fix: fix workflow activator and proptypes * fix: hide owner tag for workflow card until sharing is available * fix: fixed ownedBy and sharedWith appearing for workflows list * feat: update tags component design * refactor: change resource filter select to n8n-user-select * fix: made telemetry messages reusable * chore: remove unused import * refactor: fix component name casing * refactor: use Vue.set to make workflow property reactive * feat: add support for clicking on tags for filtering * chore: fix tags linting issues * fix: fix resources list layout when title words are very long * refactor: add active and inactive status text to workflow activator * fix: fix credentials and workflows sorting when name contains leading whitespace * fix: remove wrongfully added style tag * feat: add translations and storybook examples for truncated tags * fix: remove enterprise sharing env from schema * refactor: fix workflows module and workflows field store naming conflict * fix: fix workflow activator wrapping * feat: updated empty workflows list cards design * feat: update workflow activator margins and workflow card * feat: add duplicate workflow functionality and update tags * feat: fix duplicate workflow flow * fix: fix status color for workflow activator with could not be started status * fix: remove createdAt and updatedAt from workflow duplication
This commit is contained in:
@@ -19,6 +19,25 @@ export const Default: StoryFn = (args, {argTypes}) => ({
|
||||
template: `<n8n-card v-bind="$props">This is a card.</n8n-card>`,
|
||||
});
|
||||
|
||||
export const Hoverable: StoryFn = (args, {argTypes}) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nCard,
|
||||
N8nIcon,
|
||||
N8nText,
|
||||
},
|
||||
template: `<div style="width: 140px; text-align: center;">
|
||||
<n8n-card v-bind="$props">
|
||||
<n8n-icon icon="plus" size="xlarge" />
|
||||
<n8n-text size="large" class="mt-2xs">Add</n8n-text>
|
||||
</n8n-card>
|
||||
</div>`,
|
||||
});
|
||||
|
||||
Hoverable.args = {
|
||||
hoverable: true,
|
||||
};
|
||||
|
||||
|
||||
export const WithSlots: StoryFn = (args, {argTypes}) => ({
|
||||
props: Object.keys(argTypes),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div :class="['card', $style.card]" v-on="$listeners">
|
||||
<div :class="classes" v-on="$listeners">
|
||||
<div :class="$style.icon" v-if="$slots.prepend">
|
||||
<slot name="prepend"/>
|
||||
</div>
|
||||
@@ -26,6 +26,21 @@ import Vue from 'vue';
|
||||
export default Vue.extend({
|
||||
name: 'n8n-card',
|
||||
inheritAttrs: true,
|
||||
props: {
|
||||
hoverable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
classes(): Record<string, boolean> {
|
||||
return {
|
||||
card: true,
|
||||
[this.$style.card]: true,
|
||||
[this.$style.hoverable]: this.hoverable,
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -73,4 +88,17 @@ export default Vue.extend({
|
||||
align-items: center;
|
||||
margin-right: var(--spacing-s);
|
||||
}
|
||||
|
||||
.hoverable {
|
||||
cursor: pointer;
|
||||
transition-property: border, color;
|
||||
transition-duration: 0.3s;
|
||||
transition-timing-function: ease;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Vitest Snapshot v1
|
||||
|
||||
exports[`components > N8nCard > should render correctly 1`] = `
|
||||
"<div class=\\"card _card_nk81s_1\\">
|
||||
"<div class=\\"card _card_10dnp_1\\">
|
||||
<!---->
|
||||
<div class=\\"_content_nk81s_20\\">
|
||||
<div class=\\"_content_10dnp_20\\">
|
||||
<!---->
|
||||
<div class=\\"_body_nk81s_28\\">This is a card.</div>
|
||||
<div class=\\"_body_10dnp_28\\">This is a card.</div>
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
@@ -13,12 +13,12 @@ exports[`components > N8nCard > should render correctly 1`] = `
|
||||
`;
|
||||
|
||||
exports[`components > N8nCard > should render correctly with header and footer 1`] = `
|
||||
"<div class=\\"card _card_nk81s_1\\">
|
||||
"<div class=\\"card _card_10dnp_1\\">
|
||||
<!---->
|
||||
<div class=\\"_content_nk81s_20\\">
|
||||
<div class=\\"_header_nk81s_12\\">Header</div>
|
||||
<div class=\\"_body_nk81s_28\\">This is a card.</div>
|
||||
<div class=\\"_footer_nk81s_13\\">Footer</div>
|
||||
<div class=\\"_content_10dnp_20\\">
|
||||
<div class=\\"_header_10dnp_12\\">Header</div>
|
||||
<div class=\\"_body_10dnp_28\\">This is a card.</div>
|
||||
<div class=\\"_footer_10dnp_13\\">Footer</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Vitest Snapshot v1
|
||||
|
||||
exports[`N8nInfoTip > should render correctly as note 1`] = `"<div class=\\"n8n-info-tip _info_3egb8_33 _note_3egb8_16 _base_3egb8_1 _bold_3egb8_12\\"><span class=\\"_iconText_3egb8_28\\"><span class=\\"n8n-icon n8n-text _compact_91pa9_34 _size-medium_91pa9_19 _regular_91pa9_5\\"></span><span>Need help doing something?<a href=\\"/docs\\" target=\\"_blank\\">Open docs</a></span></span></div>"`;
|
||||
exports[`N8nInfoTip > should render correctly as note 1`] = `"<div class=\\"n8n-info-tip _info_3egb8_33 _note_3egb8_16 _base_3egb8_1 _bold_3egb8_12\\"><span class=\\"_iconText_3egb8_28\\"><span class=\\"n8n-icon n8n-text _compact_odhsl_34 _size-medium_odhsl_19 _regular_odhsl_5\\"></span><span>Need help doing something?<a href=\\"/docs\\" target=\\"_blank\\">Open docs</a></span></span></div>"`;
|
||||
|
||||
exports[`N8nInfoTip > should render correctly as tooltip 1`] = `
|
||||
"<div class=\\"n8n-info-tip _info_3egb8_33 _tooltip_3egb8_23 _base_3egb8_1 _bold_3egb8_12\\">
|
||||
<n8n-tooltip-stub justifybuttons=\\"flex-end\\" buttons=\\"\\" placement=\\"top\\"><span class=\\"_iconText_3egb8_28\\"><span class=\\"n8n-icon n8n-text _compact_91pa9_34 _size-medium_91pa9_19 _regular_91pa9_5\\"></span></span><span>Need help doing something?<a href=\\"/docs\\" target=\\"_blank\\">Open docs</a></span></n8n-tooltip-stub>
|
||||
<n8n-tooltip-stub justifybuttons=\\"flex-end\\" buttons=\\"\\" placement=\\"top\\"><span class=\\"_iconText_3egb8_28\\"><span class=\\"n8n-icon n8n-text _compact_odhsl_34 _size-medium_odhsl_19 _regular_odhsl_5\\"></span></span><span>Need help doing something?<a href=\\"/docs\\" target=\\"_blank\\">Open docs</a></span></n8n-tooltip-stub>
|
||||
</div>"
|
||||
`;
|
||||
|
||||
@@ -107,6 +107,11 @@ export default Vue.extend({
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.text-underline {
|
||||
composes: text;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.danger-underline {
|
||||
composes: danger;
|
||||
text-decoration: underline;
|
||||
|
||||
@@ -73,7 +73,7 @@ export default Vue.extend({
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeTab: '',
|
||||
activeTab: this.value,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
@@ -101,6 +101,10 @@ export default Vue.extend({
|
||||
items: {
|
||||
type: Array as PropType<IMenuItem[]>,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.mode === 'router') {
|
||||
@@ -112,6 +116,8 @@ export default Vue.extend({
|
||||
} else {
|
||||
this.activeTab = this.items.length > 0 ? this.items[0].id : '';
|
||||
}
|
||||
|
||||
this.$emit('input', this.activeTab);
|
||||
},
|
||||
computed: {
|
||||
upperMenuItems(): IMenuItem[] {
|
||||
@@ -127,6 +133,12 @@ export default Vue.extend({
|
||||
this.activeTab = option;
|
||||
}
|
||||
this.$emit('select', option);
|
||||
this.$emit('input', this.activeTab);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value(value: string) {
|
||||
this.activeTab = value;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -148,7 +160,7 @@ export default Vue.extend({
|
||||
|
||||
& > div > :global(.el-menu) {
|
||||
background: none;
|
||||
padding: 12px;
|
||||
padding: var(--menu-padding, 12px);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,16 @@ export default Vue.extend({
|
||||
<style lang="scss" module>
|
||||
.tag {
|
||||
min-width: max-content;
|
||||
padding: var(--spacing-4xs);
|
||||
background-color: var(--color-foreground-base);
|
||||
padding: 1px var(--spacing-4xs);
|
||||
color: var(--color-text-dark);
|
||||
background-color: var(--color-background-base);
|
||||
border-radius: var(--border-radius-base);
|
||||
font-size: var(--font-size-2xs);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: hsl(var(--color-background-base-h), var(--color-background-base-s), calc(var(--color-background-base-l) - 4%));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -33,3 +33,31 @@ Tags.args = {
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
export const Truncated = Template.bind({});
|
||||
Truncated.args = {
|
||||
truncate: true,
|
||||
tags: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'very long tag name',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'tag1',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'tag2 yo',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'tag3',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'tag4',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,21 +1,65 @@
|
||||
<template>
|
||||
<div :class="['n8n-tags', $style.tags]">
|
||||
<n8n-tag v-for="tag in tags" :key="tag.id" :text="tag.name" @click="$emit('click', tag.id, $event)"/>
|
||||
<n8n-tag v-for="tag in visibleTags" :key="tag.id" :text="tag.name" @click="$emit('click', tag.id, $event)"/>
|
||||
<n8n-link
|
||||
v-if="truncate && !showAll && hiddenTagsLength > 0"
|
||||
theme="text"
|
||||
underline
|
||||
size="small"
|
||||
@click.stop.prevent="showAll = true"
|
||||
>
|
||||
{{ t('tags.showMore', hiddenTagsLength) }}
|
||||
</n8n-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import N8nTag from '../N8nTag';
|
||||
import Vue from 'vue';
|
||||
import N8nLink from '../N8nLink';
|
||||
import Locale from "../../mixins/locale";
|
||||
import Vue, {PropType} from 'vue';
|
||||
import mixins from "vue-typed-mixins";
|
||||
|
||||
export default Vue.extend({
|
||||
interface ITag {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default mixins(Locale).extend({
|
||||
name: 'n8n-tags',
|
||||
components: {
|
||||
N8nTag,
|
||||
N8nLink,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showAll: false,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
tags: {
|
||||
type: Array,
|
||||
type: Array as PropType<ITag[]>,
|
||||
default: () => [],
|
||||
},
|
||||
truncate: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
truncateAt: {
|
||||
type: Number,
|
||||
default: 3,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
visibleTags(): ITag[] {
|
||||
if (this.truncate && !this.showAll && this.tags.length > this.truncateAt) {
|
||||
return this.tags.slice(0, this.truncateAt);
|
||||
}
|
||||
|
||||
return this.tags;
|
||||
},
|
||||
hiddenTagsLength(): number {
|
||||
return this.tags.length - this.truncateAt;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -23,22 +67,24 @@ export default Vue.extend({
|
||||
|
||||
<style lang="scss" module>
|
||||
.tags {
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
overflow-x: scroll;
|
||||
align-items: center;
|
||||
overflow-x: scroll;
|
||||
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
|
||||
margin-top: calc(var(--spacing-4xs) * -1); // Cancel out top margin of first tags row
|
||||
|
||||
* {
|
||||
margin: 0 var(--spacing-4xs) var(--spacing-4xs) 0;
|
||||
margin: var(--spacing-4xs) var(--spacing-4xs) 0 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -13,7 +13,7 @@ export default {
|
||||
color: {
|
||||
control: {
|
||||
type: 'select',
|
||||
options: ['primary', 'text-dark', 'text-base', 'text-light', 'text-xlight'],
|
||||
options: ['primary', 'text-dark', 'text-base', 'text-light', 'text-xlight', 'danger', 'success'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -21,7 +21,7 @@ export default Vue.extend({
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
validator: (value: string): boolean => ['primary', 'text-dark', 'text-base', 'text-light', 'text-xlight', 'danger'].includes(value),
|
||||
validator: (value: string): boolean => ['primary', 'text-dark', 'text-base', 'text-light', 'text-xlight', 'danger', 'success'].includes(value),
|
||||
},
|
||||
align: {
|
||||
type: String,
|
||||
@@ -122,6 +122,10 @@ export default Vue.extend({
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
.success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-select
|
||||
<n8n-select
|
||||
:value="value"
|
||||
:filterable="true"
|
||||
:filterMethod="setFilter"
|
||||
@@ -16,16 +16,17 @@
|
||||
<template #prefix v-if="$slots.prefix">
|
||||
<slot name="prefix" />
|
||||
</template>
|
||||
<el-option
|
||||
<n8n-option
|
||||
v-for="user in sortedUsers"
|
||||
:key="user.id"
|
||||
:value="user.id"
|
||||
:class="$style.itemContainer"
|
||||
:label="getLabel(user)"
|
||||
:disabled="user.disabled"
|
||||
>
|
||||
<n8n-user-info v-bind="user" :isCurrentUser="currentUserId === user.id" />
|
||||
</el-option>
|
||||
</el-select>
|
||||
</n8n-option>
|
||||
</n8n-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
@@ -759,7 +759,7 @@ $cascader-height: 200px;
|
||||
/* Switch
|
||||
-------------------------- */
|
||||
/// color||Color|0
|
||||
$switch-on-color: var(--color-primary);
|
||||
$switch-on-color: #13ce66;
|
||||
/// color||Color|0
|
||||
$switch-off-color: var(--color-text-light);
|
||||
/// fontSize||Font|1
|
||||
|
||||
@@ -19,4 +19,5 @@ export default {
|
||||
}`),
|
||||
"formInput.validator.defaultPasswordRequirements": "8+ characters, at least 1 number and 1 capital letter",
|
||||
"sticky.markdownHint": `You can style with <a href="https://docs.n8n.io/workflows/sticky-notes/" target="_blank">Markdown</a>`,
|
||||
'tags.showMore': (count) => `+${count} more`,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user