refactor(editor): Migrate part of the vuex store to pinia (#4484)

*  Added pinia support. Migrated community nodes module.
*  Added ui pinia store, moved some data from root store to it, updated modals to work with pinia stores
*  Added ui pinia store and migrated a part of the root store
*  Migrated `settings` store to pinia
*  Removing vuex store refs from router
*  Migrated `users` module to pinia store
*  Fixing errors after sync with master
*  One more error after merge
*  Created `workflows` pinia store. Moved large part of root store to it. Started updating references.
*  Finished migrating workflows store to pinia
*  Renaming some getters and actions to make more sense
*  Finished migrating the root store to pinia
*  Migrated ndv store to pinia
*  Renaming main panel dimensions getter so it doesn't clash with data prop name
* ✔️ Fixing lint errors
*  Migrated `templates` store to pinia
*  Migrated the `nodeTypes`store
*  Removed unused pieces of code and oold vuex modules
*  Adding vuex calls to pinia store, fi	xing wrong references
* 💄 Removing leftover $store refs
*  Added legacy getters and mutations to store to support webhooks
*  Added missing front-end hooks, updated vuex state subscriptions to pinia
* ✔️ Fixing linting errors
*  Removing vue composition api plugin
*  Fixing main sidebar state when loading node view
* 🐛 Fixing an error when activating workflows
* 🐛 Fixing isses with workflow settings and executions auto-refresh
* 🐛 Removing duplicate listeners which cause import error
* 🐛 Fixing route authentication
*  Updating freshly pulled $store refs
* Adding deleted const
*  Updating store references in ee features. Reseting NodeView credentials update flag when resetting workspace
*  Adding return type to email submission modal
*  Making NodeView only react to paste event when active
* 🐛 Fixing signup view errors
* 👌 Addressing PR review comments
* 👌 Addressing new PR comments
* 👌 Updating invite id logic in signup view
This commit is contained in:
Milorad FIlipović 2022-11-04 14:04:31 +01:00 committed by GitHub
parent c2c7927414
commit 40e413d958
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
160 changed files with 5141 additions and 4378 deletions

100
package-lock.json generated
View file

@ -9090,6 +9090,11 @@
"yallist": "^2.1.2" "yallist": "^2.1.2"
} }
}, },
"node_modules/@vue/devtools-api": {
"version": "6.4.5",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.4.5.tgz",
"integrity": "sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ=="
},
"node_modules/@vue/eslint-config-typescript": { "node_modules/@vue/eslint-config-typescript": {
"version": "11.0.2", "version": "11.0.2",
"resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-11.0.2.tgz", "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-11.0.2.tgz",
@ -27057,6 +27062,31 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/pinia": {
"version": "2.0.23",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.23.tgz",
"integrity": "sha512-N15hFf4o5STrxpNrib1IEb1GOArvPYf1zPvQVRGOO1G1d74Ak0J0lVyalX/SmrzdT4Q0nlEFjbURsmBmIGUR5Q==",
"dependencies": {
"@vue/devtools-api": "^6.4.4",
"vue-demi": "*"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"@vue/composition-api": "^1.4.0",
"typescript": ">=4.4.4",
"vue": "^2.6.14 || ^3.2.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/pinkie": { "node_modules/pinkie": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
@ -35718,6 +35748,31 @@
"tinycolor2": "^1.1.2" "tinycolor2": "^1.1.2"
} }
}, },
"node_modules/vue-demi": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/vue-docgen-api": { "node_modules/vue-docgen-api": {
"version": "4.54.2", "version": "4.54.2",
"resolved": "https://registry.npmjs.org/vue-docgen-api/-/vue-docgen-api-4.54.2.tgz", "resolved": "https://registry.npmjs.org/vue-docgen-api/-/vue-docgen-api-4.54.2.tgz",
@ -38517,6 +38572,7 @@
"n8n-design-system": "~0.40.0", "n8n-design-system": "~0.40.0",
"n8n-workflow": "~0.122.1", "n8n-workflow": "~0.122.1",
"normalize-wheel": "^1.0.1", "normalize-wheel": "^1.0.1",
"pinia": "^2.0.22",
"prismjs": "^1.17.1", "prismjs": "^1.17.1",
"quill": "2.0.0-dev.4", "quill": "2.0.0-dev.4",
"quill-autoformat": "^0.1.1", "quill-autoformat": "^0.1.1",
@ -42784,7 +42840,7 @@
"functional-red-black-tree": "^1.0.1", "functional-red-black-tree": "^1.0.1",
"glob-parent": "^6.0.1", "glob-parent": "^6.0.1",
"globals": "^13.15.0", "globals": "^13.15.0",
"globby": "^11.0.2", "globby": "^11.1.0",
"grapheme-splitter": "^1.0.4", "grapheme-splitter": "^1.0.4",
"ignore": "^5.2.0", "ignore": "^5.2.0",
"import-fresh": "^3.0.0", "import-fresh": "^3.0.0",
@ -43016,7 +43072,7 @@
"@oclif/errors": "^1.3.6", "@oclif/errors": "^1.3.6",
"@oclif/parser": "^3.8.8", "@oclif/parser": "^3.8.8",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.0.2", "globby": "^11.1.0",
"is-wsl": "^2.1.1", "is-wsl": "^2.1.1",
"tslib": "^2.3.1" "tslib": "^2.3.1"
}, },
@ -43042,10 +43098,10 @@
"clean-stack": "^3.0.1", "clean-stack": "^3.0.1",
"cli-progress": "^3.10.0", "cli-progress": "^3.10.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ejs": "^3.1.8", "ejs": "^3.1.6",
"fs-extra": "^9.1.0", "fs-extra": "^9.1.0",
"get-package-type": "^0.1.0", "get-package-type": "^0.1.0",
"globby": "^11.0.2", "globby": "^11.1.0",
"hyperlinker": "^1.0.0", "hyperlinker": "^1.0.0",
"indent-string": "^4.0.0", "indent-string": "^4.0.0",
"is-wsl": "^2.2.0", "is-wsl": "^2.2.0",
@ -43262,7 +43318,7 @@
"@oclif/errors": "^1.3.3", "@oclif/errors": "^1.3.3",
"@oclif/parser": "^3.8.0", "@oclif/parser": "^3.8.0",
"debug": "^4.1.1", "debug": "^4.1.1",
"globby": "^11.0.2", "globby": "^11.0.1",
"is-wsl": "^2.1.1", "is-wsl": "^2.1.1",
"tslib": "^2.0.0" "tslib": "^2.0.0"
} }
@ -43855,7 +43911,7 @@
"css-loader": "^3.6.0", "css-loader": "^3.6.0",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"find-up": "^5.0.0", "find-up": "^5.0.0",
"fork-ts-checker-webpack-plugin": "^6.0.4", "fork-ts-checker-webpack-plugin": "^4.1.6",
"glob": "^7.1.6", "glob": "^7.1.6",
"glob-promise": "^3.4.0", "glob-promise": "^3.4.0",
"global": "^4.4.0", "global": "^4.4.0",
@ -45971,7 +46027,7 @@
"@typescript-eslint/types": "5.42.0", "@typescript-eslint/types": "5.42.0",
"@typescript-eslint/visitor-keys": "5.42.0", "@typescript-eslint/visitor-keys": "5.42.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.0.2", "globby": "^11.1.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
"semver": "^7.3.7", "semver": "^7.3.7",
"tsutils": "^3.21.0" "tsutils": "^3.21.0"
@ -46249,6 +46305,11 @@
} }
} }
}, },
"@vue/devtools-api": {
"version": "6.4.5",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.4.5.tgz",
"integrity": "sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ=="
},
"@vue/eslint-config-typescript": { "@vue/eslint-config-typescript": {
"version": "11.0.2", "version": "11.0.2",
"resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-11.0.2.tgz", "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-11.0.2.tgz",
@ -47273,7 +47334,7 @@
"integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==", "integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==",
"dev": true, "dev": true,
"requires": { "requires": {
"browserslist": "^4.21.3", "browserslist": "^4.12.0",
"caniuse-lite": "^1.0.30001109", "caniuse-lite": "^1.0.30001109",
"normalize-range": "^0.1.2", "normalize-range": "^0.1.2",
"num2fraction": "^1.2.2", "num2fraction": "^1.2.2",
@ -49868,7 +49929,7 @@
"integrity": "sha512-piOX9Go+Z4f9ZiBFLnZ5VrOpBl0h7IGCkiFUN11QTe6LjAvOT3ifL/5TdoizMh99hcGy5SoLyWbapIY/PIb/3A==", "integrity": "sha512-piOX9Go+Z4f9ZiBFLnZ5VrOpBl0h7IGCkiFUN11QTe6LjAvOT3ifL/5TdoizMh99hcGy5SoLyWbapIY/PIb/3A==",
"dev": true, "dev": true,
"requires": { "requires": {
"browserslist": "^4.21.3" "browserslist": "^4.21.4"
} }
}, },
"core-js-pure": { "core-js-pure": {
@ -49936,7 +49997,7 @@
"requires": { "requires": {
"arrify": "^2.0.1", "arrify": "^2.0.1",
"cp-file": "^7.0.0", "cp-file": "^7.0.0",
"globby": "^11.0.2", "globby": "^9.2.0",
"has-glob": "^1.0.0", "has-glob": "^1.0.0",
"junk": "^3.1.0", "junk": "^3.1.0",
"nested-error-stacks": "^2.1.0", "nested-error-stacks": "^2.1.0",
@ -58963,6 +59024,7 @@
"n8n-design-system": "~0.40.0", "n8n-design-system": "~0.40.0",
"n8n-workflow": "~0.122.1", "n8n-workflow": "~0.122.1",
"normalize-wheel": "^1.0.1", "normalize-wheel": "^1.0.1",
"pinia": "^2.0.22",
"prismjs": "^1.17.1", "prismjs": "^1.17.1",
"quill": "2.0.0-dev.4", "quill": "2.0.0-dev.4",
"quill-autoformat": "^0.1.1", "quill-autoformat": "^0.1.1",
@ -61151,6 +61213,15 @@
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"dev": true "dev": true
}, },
"pinia": {
"version": "2.0.23",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.23.tgz",
"integrity": "sha512-N15hFf4o5STrxpNrib1IEb1GOArvPYf1zPvQVRGOO1G1d74Ak0J0lVyalX/SmrzdT4Q0nlEFjbURsmBmIGUR5Q==",
"requires": {
"@vue/devtools-api": "^6.4.4",
"vue-demi": "*"
}
},
"pinkie": { "pinkie": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
@ -61952,7 +62023,7 @@
"fs-extra": "^6.0.1", "fs-extra": "^6.0.1",
"get-stream": "^5.1.0", "get-stream": "^5.1.0",
"glob": "^7.1.2", "glob": "^7.1.2",
"globby": "^11.0.2", "globby": "^10.0.1",
"http-call": "^5.1.2", "http-call": "^5.1.2",
"load-json-file": "^6.2.0", "load-json-file": "^6.2.0",
"pkg-dir": "^4.2.0", "pkg-dir": "^4.2.0",
@ -67643,7 +67714,7 @@
"consola": "^2.15.3", "consola": "^2.15.3",
"dotenv": "^16.0.0", "dotenv": "^16.0.0",
"dotenv-expand": "^8.0.2", "dotenv-expand": "^8.0.2",
"ejs": "^3.1.8", "ejs": "^3.1.6",
"fast-glob": "^3.2.11", "fast-glob": "^3.2.11",
"fs-extra": "^10.0.1", "fs-extra": "^10.0.1",
"html-minifier-terser": "^6.1.0", "html-minifier-terser": "^6.1.0",
@ -67820,6 +67891,11 @@
"tinycolor2": "^1.1.2" "tinycolor2": "^1.1.2"
} }
}, },
"vue-demi": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A=="
},
"vue-docgen-api": { "vue-docgen-api": {
"version": "4.54.2", "version": "4.54.2",
"resolved": "https://registry.npmjs.org/vue-docgen-api/-/vue-docgen-api-4.54.2.tgz", "resolved": "https://registry.npmjs.org/vue-docgen-api/-/vue-docgen-api-4.54.2.tgz",

View file

@ -57,6 +57,7 @@
"n8n-design-system": "~0.41.0", "n8n-design-system": "~0.41.0",
"n8n-workflow": "~0.123.0", "n8n-workflow": "~0.123.0",
"normalize-wheel": "^1.0.1", "normalize-wheel": "^1.0.1",
"pinia": "^2.0.22",
"prismjs": "^1.17.1", "prismjs": "^1.17.1",
"quill": "2.0.0-dev.4", "quill": "2.0.0-dev.4",
"quill-autoformat": "^0.1.1", "quill-autoformat": "^0.1.1",

View file

@ -6,7 +6,7 @@
id="app" id="app"
:class="{ :class="{
[$style.container]: true, [$style.container]: true,
[$style.sidebarCollapsed]: sidebarMenuCollapsed [$style.sidebarCollapsed]: uiStore.sidebarMenuCollapsed
}" }"
> >
<div id="header" :class="$style.header"> <div id="header" :class="$style.header">
@ -35,11 +35,17 @@ import { HIRING_BANNER, LOCAL_STORAGE_THEME, VIEWS } from './constants';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { showMessage } from './components/mixins/showMessage'; import { showMessage } from './components/mixins/showMessage';
import { IUser } from './Interface'; import { IUser } from './Interface';
import { mapGetters } from 'vuex';
import { userHelpers } from './components/mixins/userHelpers'; import { userHelpers } from './components/mixins/userHelpers';
import { loadLanguage } from './plugins/i18n'; import { loadLanguage } from './plugins/i18n';
import { restApi } from '@/components/mixins/restApi'; import { restApi } from '@/components/mixins/restApi';
import { globalLinkActions } from '@/components/mixins/globalLinkActions'; import { globalLinkActions } from '@/components/mixins/globalLinkActions';
import { mapStores } from 'pinia';
import { useUIStore } from './stores/ui';
import { useSettingsStore } from './stores/settings';
import { useUsersStore } from './stores/users';
import { useRootStore } from './stores/n8nRootStore';
import { useTemplatesStore } from './stores/templates';
import { useNodeTypesStore } from './stores/nodeTypes';
export default mixins( export default mixins(
showMessage, showMessage,
@ -54,11 +60,16 @@ export default mixins(
Modals, Modals,
}, },
computed: { computed: {
...mapGetters('settings', ['isHiringBannerEnabled', 'isTemplatesEnabled', 'isTemplatesEndpointReachable', 'isUserManagementEnabled', 'showSetupPage']), ...mapStores(
...mapGetters('users', ['currentUser']), useNodeTypesStore,
...mapGetters('ui', ['sidebarMenuCollapsed']), useRootStore,
useSettingsStore,
useTemplatesStore,
useUIStore,
useUsersStore,
),
defaultLocale (): string { defaultLocale (): string {
return this.$store.getters.defaultLocale; return this.rootStore.defaultLocale;
}, },
}, },
data() { data() {
@ -69,7 +80,7 @@ export default mixins(
methods: { methods: {
async initSettings(): Promise<void> { async initSettings(): Promise<void> {
try { try {
await this.$store.dispatch('settings/getSettings'); await this.settingsStore.getSettings();
} catch (e) { } catch (e) {
this.$showToast({ this.$showToast({
title: this.$locale.baseText('startupError'), title: this.$locale.baseText('startupError'),
@ -81,23 +92,22 @@ export default mixins(
throw e; throw e;
} }
}, },
async loginWithCookie(): Promise<void> { loginWithCookie(): void {
try { try {
await this.$store.dispatch('users/loginWithCookie'); this.usersStore.loginWithCookie();
} catch (e) {} } catch (e) {}
}, },
async initTemplates(): Promise<void> { async initTemplates(): Promise<void> {
if (!this.isTemplatesEnabled) { if (!this.settingsStore.isTemplatesEnabled) {
return; return;
} }
try { try {
await this.$store.dispatch('settings/testTemplatesEndpoint'); await this.settingsStore.testTemplatesEndpoint();
} catch (e) { } catch (e) {
} }
}, },
logHiringBanner() { logHiringBanner() {
if (this.isHiringBannerEnabled && this.$route.name !== VIEWS.DEMO) { if (this.settingsStore.isHiringBannerEnabled && this.$route.name !== VIEWS.DEMO) {
console.log(HIRING_BANNER); // eslint-disable-line no-console console.log(HIRING_BANNER); // eslint-disable-line no-console
} }
}, },
@ -105,20 +115,20 @@ export default mixins(
await this.initSettings(); await this.initSettings();
await Promise.all([this.loginWithCookie(), this.initTemplates()]); await Promise.all([this.loginWithCookie(), this.initTemplates()]);
}, },
trackPage() { trackPage(): void {
this.$store.commit('ui/setCurrentView', this.$route.name); this.uiStore.currentView = this.$route.name || '';
if (this.$route && this.$route.meta && this.$route.meta.templatesEnabled) { if (this.$route && this.$route.meta && this.$route.meta.templatesEnabled) {
this.$store.commit('templates/setSessionId'); this.templatesStore.setSessionId();
} }
else { else {
this.$store.commit('templates/resetSessionId'); // reset telemetry session id when user leaves template pages this.templatesStore.resetSessionId(); // reset telemetry session id when user leaves template pages
} }
this.$telemetry.page(this.$route); this.$telemetry.page(this.$route);
}, },
authenticate() { authenticate() {
// redirect to setup page. user should be redirected to this only once // redirect to setup page. user should be redirected to this only once
if (this.isUserManagementEnabled && this.showSetupPage) { if (this.settingsStore.isUserManagementEnabled && this.settingsStore.showSetupPage) {
if (this.$route.name === VIEWS.SETUP) { if (this.$route.name === VIEWS.SETUP) {
return; return;
} }
@ -132,7 +142,7 @@ export default mixins(
} }
// if cannot access page and not logged in, ask to sign in // if cannot access page and not logged in, ask to sign in
const user = this.currentUser as IUser | null; const user = this.usersStore.currentUser;
if (!user) { if (!user) {
const redirect = const redirect =
this.$route.query.redirect || this.$route.query.redirect ||
@ -154,7 +164,7 @@ export default mixins(
this.$router.replace({ name: VIEWS.HOMEPAGE }); this.$router.replace({ name: VIEWS.HOMEPAGE });
}, },
redirectIfNecessary() { redirectIfNecessary() {
const redirect = this.$route.meta && typeof this.$route.meta.getRedirect === 'function' && this.$route.meta.getRedirect(this.$store); const redirect = this.$route.meta && typeof this.$route.meta.getRedirect === 'function' && this.$route.meta.getRedirect();
if (redirect) { if (redirect) {
this.$router.replace(redirect); this.$router.replace(redirect);
} }
@ -176,10 +186,11 @@ export default mixins(
this.loading = false; this.loading = false;
this.trackPage(); this.trackPage();
// TODO: Un-comment once front-end hooks are updated to work with pinia store
this.$externalHooks().run('app.mount'); this.$externalHooks().run('app.mount');
if (this.defaultLocale !== 'en') { if (this.defaultLocale !== 'en') {
void this.$store.dispatch('nodeTypes/getNodeTranslationHeaders'); await this.nodeTypesStore.getNodeTranslationHeaders();
} }
}, },
watch: { watch: {

View file

@ -1,3 +1,4 @@
import { IMenuItem } from 'n8n-design-system';
import { import {
jsPlumbInstance, jsPlumbInstance,
DragOptions, DragOptions,
@ -33,6 +34,7 @@ import {
ILoadOptions, ILoadOptions,
INodeCredentials, INodeCredentials,
INodeListSearchItems, INodeListSearchItems,
NodeParameterValueType,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { FAKE_DOOR_FEATURES } from './constants'; import { FAKE_DOOR_FEATURES } from './constants';
@ -152,8 +154,8 @@ export type IJsPlumbInstance = Omit<jsPlumbInstance, 'addEndpoint' | 'draggable'
export interface IUpdateInformation { export interface IUpdateInformation {
name: string; name: string;
key: string; key?: string;
value: string | number | { [key: string]: string | number | boolean }; // with null makes problems in NodeSettings.vue value: string | number | { [key: string]: string | number | boolean } | NodeParameterValueType | INodeParameters; // with null makes problems in NodeSettings.vue
node?: string; node?: string;
oldValue?: string | number; oldValue?: string | number;
} }
@ -887,6 +889,47 @@ export interface INodeMetadata {
parametersLastUpdatedAt?: number; parametersLastUpdatedAt?: number;
} }
export interface WorkflowsState {
activeExecutions: IExecutionsCurrentSummaryExtended[];
activeWorkflows: string[];
activeWorkflowExecution: IExecutionsSummary | null;
currentWorkflowExecutions: IExecutionsSummary[];
activeExecutionId: string | null;
executingNode: string | null;
executionWaitingForWebhook: boolean;
finishedExecutionsCount: number;
nodeMetadata: NodeMetadataMap;
subWorkflowExecutionError: Error | null;
workflow: IWorkflowDb;
workflowExecutionData: IExecutionResponse | null;
workflowExecutionPairedItemMappings: {[itemId: string]: Set<string>};
workflowsById: IWorkflowsMap;
}
export interface RootState {
baseUrl: string;
defaultLocale: string;
endpointWebhook: string;
endpointWebhookTest: string;
pushConnectionActive: boolean;
timezone: string;
executionTimeout: number;
maxExecutionTimeout: number;
versionCli: string;
oauthCallbackUrls: object;
n8nMetadata: {
[key: string]: string | number | undefined;
};
sessionId: string;
urlBaseWebhook: string;
urlBaseEditor: string;
instanceId: string;
isNpmAvailable: boolean;
}
export interface NodeMetadataMap {
[nodeName: string]: INodeMetadata;
}
export interface IRootState { export interface IRootState {
activeExecutions: IExecutionsCurrentSummaryExtended[]; activeExecutions: IExecutionsCurrentSummaryExtended[];
activeWorkflows: string[]; activeWorkflows: string[];
@ -924,12 +967,12 @@ export interface IRootState {
workflowsById: IWorkflowsMap; workflowsById: IWorkflowsMap;
sidebarMenuItems: IMenuItem[]; sidebarMenuItems: IMenuItem[];
instanceId: string; instanceId: string;
nodeMetadata: {[nodeName: string]: INodeMetadata}; nodeMetadata: NodeMetadataMap;
isNpmAvailable: boolean; isNpmAvailable: boolean;
subworkflowExecutionError: Error | null; subworkflowExecutionError: Error | null;
} }
export interface ICommunityPackageMap { export interface CommunityPackageMap {
[name: string]: PublicInstalledPackage; [name: string]: PublicInstalledPackage;
} }
@ -964,6 +1007,7 @@ export interface IModalState {
} }
export type IRunDataDisplayMode = 'table' | 'json' | 'binary'; export type IRunDataDisplayMode = 'table' | 'json' | 'binary';
export type nodePanelType = 'input' | 'output';
export interface TargetItem { export interface TargetItem {
nodeName: string; nodeName: string;
@ -1023,6 +1067,37 @@ export interface IUiState {
executionSidebarAutoRefresh: boolean; executionSidebarAutoRefresh: boolean;
} }
export interface UIState {
activeActions: string[];
activeCredentialType: string | null;
sidebarMenuCollapsed: boolean;
modalStack: string[];
modals: {
[key: string]: IModalState;
};
isPageLoading: boolean;
currentView: string;
mainPanelPosition: number;
fakeDoorFeatures: IFakeDoor[];
draggable: {
isDragging: boolean;
type: string;
data: string;
canDrop: boolean;
stickyPosition: null | XYPosition;
};
stateIsDirty: boolean;
lastSelectedNode: string | null;
lastSelectedNodeOutputIndex: number | null;
nodeViewOffsetPosition: XYPosition;
nodeViewMoveInProgress: boolean;
selectedNodes: INodeUi[];
sidebarMenuItems: IMenuItem[];
nodeViewInitialized: boolean;
addFirstStepOnLoad: boolean;
executionSidebarAutoRefresh: boolean;
}
export type ILogLevel = 'info' | 'debug' | 'warn' | 'error' | 'verbose'; export type ILogLevel = 'info' | 'debug' | 'warn' | 'error' | 'verbose';
export type IFakeDoor = { export type IFakeDoor = {
@ -1059,6 +1134,9 @@ export interface ISettingsState {
path: string; path: string;
}; };
onboardingCallPromptEnabled: boolean; onboardingCallPromptEnabled: boolean;
saveDataErrorExecution: string;
saveDataSuccessExecution: string;
saveManualExecutions: boolean;
} }
export interface INodeTypesState { export interface INodeTypesState {
@ -1109,11 +1187,9 @@ export interface IWorkflowsState {
[name: string]: IWorkflowDb; [name: string]: IWorkflowDb;
} }
export interface IWorkflowsState {} export interface CommunityNodesState {
export interface ICommunityNodesState {
availablePackageCount: number; availablePackageCount: number;
installedPackages: ICommunityPackageMap; installedPackages: CommunityPackageMap;
} }
export interface IRestApiContext { export interface IRestApiContext {
@ -1149,8 +1225,8 @@ export interface IOnboardingCallPromptResponse {
export interface IOnboardingCallPrompt { export interface IOnboardingCallPrompt {
title: string; title: string;
body: string; description: string;
index: number; toast_sequence_number: number;
} }
export interface ITab { export interface ITab {
@ -1182,3 +1258,14 @@ export interface IResourceLocatorReqParams {
export interface IResourceLocatorResultExpanded extends INodeListSearchItems { export interface IResourceLocatorResultExpanded extends INodeListSearchItems {
linkAlt?: string; linkAlt?: string;
} }
export interface CurlToJSONResponse {
"parameters.url": string;
"parameters.authentication": string;
"parameters.method": string;
"parameters.sendHeaders": boolean;
"parameters.headerParameters.parameters.0.name": string;
"parameters.headerParameters.parameters.0.value": string;
"parameters.sendQuery": boolean;
"parameters.sendBody": boolean;
}

View file

@ -15,6 +15,6 @@ export async function uninstallPackage(context: IRestApiContext, name: string):
return await makeRestApiRequest(context, 'DELETE', '/nodes', { name }); return await makeRestApiRequest(context, 'DELETE', '/nodes', { name });
} }
export async function updatePackage(context: IRestApiContext, name: string): Promise<void> { export async function updatePackage(context: IRestApiContext, name: string): Promise<PublicInstalledPackage> {
return await makeRestApiRequest(context, 'PATCH', '/nodes', { name }); return await makeRestApiRequest(context, 'PATCH', '/nodes', { name });
} }

View file

@ -1,6 +1,6 @@
import {IRestApiContext} from "@/Interface"; import {CurlToJSONResponse, IRestApiContext} from "@/Interface";
import {makeRestApiRequest} from "@/api/helpers"; import {makeRestApiRequest} from "@/api/helpers";
export function getCurlToJson(context: IRestApiContext, curlCommand: string): Promise<{ curlCommand: string | null }> { export function getCurlToJson(context: IRestApiContext, curlCommand: string): Promise<CurlToJSONResponse> {
return makeRestApiRequest(context, 'POST', '/curl-to-json', { curlCommand }); return makeRestApiRequest(context, 'POST', '/curl-to-json', { curlCommand });
} }

View file

@ -1,4 +1,4 @@
import { IRestApiContext, IN8nPrompts, IN8nValueSurveyData, IN8nUISettings } from '../Interface'; import { IRestApiContext, IN8nPrompts, IN8nValueSurveyData, IN8nUISettings, IN8nPromptResponse } from '../Interface';
import { makeRestApiRequest, get, post } from './helpers'; import { makeRestApiRequest, get, post } from './helpers';
import { N8N_IO_BASE_URL, NPM_COMMUNITY_NODE_SEARCH_API_URL } from '@/constants'; import { N8N_IO_BASE_URL, NPM_COMMUNITY_NODE_SEARCH_API_URL } from '@/constants';
@ -10,11 +10,11 @@ export async function getPromptsData(instanceId: string, userId: string): Promis
return await get(N8N_IO_BASE_URL, '/prompts', {}, {'n8n-instance-id': instanceId, 'n8n-user-id': userId}); return await get(N8N_IO_BASE_URL, '/prompts', {}, {'n8n-instance-id': instanceId, 'n8n-user-id': userId});
} }
export async function submitContactInfo(instanceId: string, userId: string, email: string): Promise<void> { export async function submitContactInfo(instanceId: string, userId: string, email: string): Promise<IN8nPromptResponse> {
return await post(N8N_IO_BASE_URL, '/prompt', { email }, {'n8n-instance-id': instanceId, 'n8n-user-id': userId}); return await post(N8N_IO_BASE_URL, '/prompt', { email }, {'n8n-instance-id': instanceId, 'n8n-user-id': userId});
} }
export async function submitValueSurvey(instanceId: string, userId: string, params: IN8nValueSurveyData): Promise<IN8nPrompts> { export async function submitValueSurvey(instanceId: string, userId: string, params: IN8nValueSurveyData): Promise<IN8nPromptResponse> {
return await post(N8N_IO_BASE_URL, '/value-survey', params, {'n8n-instance-id': instanceId, 'n8n-user-id': userId}); return await post(N8N_IO_BASE_URL, '/value-survey', params, {'n8n-instance-id': instanceId, 'n8n-user-id': userId});
} }

View file

@ -1,11 +1,11 @@
import { IOnboardingCallPromptResponse, IUser } from "@/Interface"; import { IOnboardingCallPrompt, IOnboardingCallPromptResponse, IUser } from "@/Interface";
import { get, post } from "./helpers"; import { get, post } from "./helpers";
const N8N_API_BASE_URL = 'https://api.n8n.io/api'; const N8N_API_BASE_URL = 'https://api.n8n.io/api';
const ONBOARDING_PROMPTS_ENDPOINT = '/prompts/onboarding'; const ONBOARDING_PROMPTS_ENDPOINT = '/prompts/onboarding';
const CONTACT_EMAIL_SUBMISSION_ENDPOINT = '/accounts/onboarding'; const CONTACT_EMAIL_SUBMISSION_ENDPOINT = '/accounts/onboarding';
export async function fetchNextOnboardingPrompt(instanceId: string, currentUer: IUser): Promise<IOnboardingCallPromptResponse> { export async function fetchNextOnboardingPrompt(instanceId: string, currentUer: IUser): Promise<IOnboardingCallPrompt> {
return await get( return await get(
N8N_API_BASE_URL, N8N_API_BASE_URL,
ONBOARDING_PROMPTS_ENDPOINT, ONBOARDING_PROMPTS_ENDPOINT,
@ -35,7 +35,7 @@ export async function applyForOnboardingCall(instanceId: string, currentUer: IUs
} }
} }
export async function submitEmailOnSignup(instanceId: string, currentUer: IUser, email: string, agree: boolean): Promise<string> { export async function submitEmailOnSignup(instanceId: string, currentUer: IUser, email: string | undefined, agree: boolean): Promise<string> {
return await post( return await post(
N8N_API_BASE_URL, N8N_API_BASE_URL,
CONTACT_EMAIL_SUBMISSION_ENDPOINT, CONTACT_EMAIL_SUBMISSION_ENDPOINT,

View file

@ -13,7 +13,7 @@
<n8n-text>{{ $locale.baseText('about.n8nVersion') }}</n8n-text> <n8n-text>{{ $locale.baseText('about.n8nVersion') }}</n8n-text>
</el-col> </el-col>
<el-col :span="16"> <el-col :span="16">
<n8n-text>{{ versionCli }}</n8n-text> <n8n-text>{{ rootStore.versionCli }}</n8n-text>
</el-col> </el-col>
</el-row> </el-row>
<el-row> <el-row>
@ -39,7 +39,7 @@
<n8n-text>{{ $locale.baseText('about.instanceID') }}</n8n-text> <n8n-text>{{ $locale.baseText('about.instanceID') }}</n8n-text>
</el-col> </el-col>
<el-col :span="16"> <el-col :span="16">
<n8n-text>{{ instanceId }}</n8n-text> <n8n-text>{{ rootStore.instanceId }}</n8n-text>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
@ -55,9 +55,11 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { mapGetters } from 'vuex';
import Modal from './Modal.vue'; import Modal from './Modal.vue';
import { ABOUT_MODAL_KEY } from '../constants'; import { ABOUT_MODAL_KEY } from '../constants';
import { mapStores } from 'pinia';
import { useSettingsStore } from '@/stores/settings';
import { useRootStore } from '@/stores/n8nRootStore';
export default Vue.extend({ export default Vue.extend({
name: 'About', name: 'About',
@ -71,8 +73,10 @@ export default Vue.extend({
}; };
}, },
computed: { computed: {
...mapGetters('settings', ['versionCli']), ...mapStores(
...mapGetters(['instanceId']), useRootStore,
useSettingsStore,
),
}, },
methods: { methods: {
closeDialog() { closeDialog() {

View file

@ -37,9 +37,13 @@
import Vue from 'vue'; import Vue from 'vue';
import Modal from '@/components/Modal.vue'; import Modal from '@/components/Modal.vue';
import { WORKFLOW_ACTIVE_MODAL_KEY, EXECUTIONS_MODAL_KEY, WORKFLOW_SETTINGS_MODAL_KEY, LOCAL_STORAGE_ACTIVATION_FLAG, VIEWS } from '../constants'; import { WORKFLOW_ACTIVE_MODAL_KEY, WORKFLOW_SETTINGS_MODAL_KEY, LOCAL_STORAGE_ACTIVATION_FLAG, VIEWS } from '../constants';
import { getActivatableTriggerNodes, getTriggerNodeServiceName } from './helpers'; import { getActivatableTriggerNodes, getTriggerNodeServiceName } from './helpers';
import { INodeTypeDescription } from 'n8n-workflow'; import { INodeTypeDescription } from 'n8n-workflow';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default Vue.extend({ export default Vue.extend({
name: 'ActivationModal', name: 'ActivationModal',
@ -58,8 +62,8 @@ export default Vue.extend({
}, },
methods: { methods: {
async showExecutionsList () { async showExecutionsList () {
const activeExecution = this.$store.getters['workflows/getActiveWorkflowExecution']; const activeExecution = this.workflowsStore.activeWorkflowExecution;
const currentWorkflow = this.$store.getters.workflowId; const currentWorkflow = this.workflowsStore.workflowId;
if (activeExecution) { if (activeExecution) {
this.$router.push({ this.$router.push({
@ -69,10 +73,10 @@ export default Vue.extend({
} else { } else {
this.$router.push({ name: VIEWS.EXECUTION_HOME, params: { name: currentWorkflow } }).catch(() => {}); this.$router.push({ name: VIEWS.EXECUTION_HOME, params: { name: currentWorkflow } }).catch(() => {});
} }
this.$store.commit('ui/closeModal', WORKFLOW_ACTIVE_MODAL_KEY); this.uiStore.closeModal(WORKFLOW_ACTIVE_MODAL_KEY);
}, },
async showSettings() { async showSettings() {
this.$store.dispatch('ui/openModal', WORKFLOW_SETTINGS_MODAL_KEY); this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
}, },
handleCheckboxChange (checkboxValue: boolean) { handleCheckboxChange (checkboxValue: boolean) {
this.checked = checkboxValue; this.checked = checkboxValue;
@ -80,8 +84,13 @@ export default Vue.extend({
}, },
}, },
computed: { computed: {
...mapStores(
useNodeTypesStore,
useUIStore,
useWorkflowsStore,
),
triggerContent (): string { triggerContent (): string {
const foundTriggers = getActivatableTriggerNodes(this.$store.getters.workflowTriggerNodes); const foundTriggers = getActivatableTriggerNodes(this.workflowsStore.workflowTriggerNodes);
if (!foundTriggers.length) { if (!foundTriggers.length) {
return ''; return '';
} }
@ -92,27 +101,28 @@ export default Vue.extend({
const trigger = foundTriggers[0]; const trigger = foundTriggers[0];
const triggerNodeType = this.$store.getters['nodeTypes/getNodeType'](trigger.type, trigger.typeVersion) as INodeTypeDescription; const triggerNodeType = this.nodeTypesStore.getNodeType(trigger.type, trigger.typeVersion);
if (triggerNodeType.activationMessage) { if (triggerNodeType) {
return triggerNodeType.activationMessage; if (triggerNodeType.activationMessage) {
} return triggerNodeType.activationMessage;
}
const serviceName = getTriggerNodeServiceName(triggerNodeType); const serviceName = getTriggerNodeServiceName(triggerNodeType);
if (trigger.webhookId) { if (trigger.webhookId) {
return this.$locale.baseText('activationModal.yourWorkflowWillNowListenForEvents', { return this.$locale.baseText('activationModal.yourWorkflowWillNowListenForEvents', {
interpolate: { interpolate: {
serviceName, serviceName,
}, },
}); });
} else if (triggerNodeType.polling) { } else if (triggerNodeType.polling) {
return this.$locale.baseText('activationModal.yourWorkflowWillNowRegularlyCheck', { return this.$locale.baseText('activationModal.yourWorkflowWillNowRegularlyCheck', {
interpolate: { interpolate: {
serviceName, serviceName,
}, },
}); });
} else { }
return this.$locale.baseText('activationModal.yourTriggerWillNowFire');
} }
return this.$locale.baseText('activationModal.yourTriggerWillNowFire');
}, },
}, },
}); });

View file

@ -32,6 +32,8 @@ import { nodeHelpers } from '@/components/mixins/nodeHelpers';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { restApi } from '@/components/mixins/restApi'; import { restApi } from '@/components/mixins/restApi';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
export default mixins( export default mixins(
nodeHelpers, nodeHelpers,
@ -47,6 +49,9 @@ export default mixins(
'windowVisible', // boolean 'windowVisible', // boolean
], ],
computed: { computed: {
...mapStores(
useWorkflowsStore,
),
binaryData (): IBinaryData | null { binaryData (): IBinaryData | null {
const binaryData = this.getBinaryData(this.workflowRunData, this.displayData.node, this.displayData.runIndex, this.displayData.outputIndex); const binaryData = this.getBinaryData(this.workflowRunData, this.displayData.node, this.displayData.runIndex, this.displayData.outputIndex);
@ -72,12 +77,12 @@ export default mixins(
}, },
workflowRunData (): IRunData | null { workflowRunData (): IRunData | null {
const workflowExecution = this.$store.getters.getWorkflowExecution; const workflowExecution = this.workflowsStore.getWorkflowExecution;
if (workflowExecution === null) { if (workflowExecution === null) {
return null; return null;
} }
const executionData: IRunExecutionData = workflowExecution.data; const executionData = workflowExecution.data;
return executionData.resultData.runData; return executionData? executionData.resultData.runData : null;
}, },
}, },

View file

@ -31,6 +31,8 @@ import Modal from "./Modal.vue";
import Vue from "vue"; import Vue from "vue";
import { IFormInputs } from "@/Interface"; import { IFormInputs } from "@/Interface";
import { CHANGE_PASSWORD_MODAL_KEY } from '../constants'; import { CHANGE_PASSWORD_MODAL_KEY } from '../constants';
import { mapStores } from "pinia";
import { useUsersStore } from "@/stores/users";
export default mixins(showMessage).extend({ export default mixins(showMessage).extend({
components: { Modal }, components: { Modal },
@ -50,6 +52,9 @@ export default mixins(showMessage).extend({
CHANGE_PASSWORD_MODAL_KEY, CHANGE_PASSWORD_MODAL_KEY,
}; };
}, },
computed: {
...mapStores(useUsersStore),
},
mounted() { mounted() {
this.config = [ this.config = [
{ {
@ -115,7 +120,7 @@ export default mixins(showMessage).extend({
async onSubmit(values: {[key: string]: string}) { async onSubmit(values: {[key: string]: string}) {
try { try {
this.loading = true; this.loading = true;
await this.$store.dispatch('users/updateCurrentUserPassword', values); await this.usersStore.updateCurrentUserPassword(values);
this.$showMessage({ this.$showMessage({
type: 'success', type: 'success',

View file

@ -32,6 +32,10 @@ import {
PLACEHOLDER_FILLED_AT_EXECUTION_TIME, PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
} from '@/constants'; } from '@/constants';
import { CodeEditor } from './forms'; import { CodeEditor } from './forms';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useRootStore } from '@/stores/n8nRootStore';
import { useNDVStore } from '@/stores/ndv';
export default mixins( export default mixins(
genericHelpers, genericHelpers,
@ -42,6 +46,13 @@ export default mixins(
CodeEditor, CodeEditor,
}, },
props: ['codeAutocomplete', 'parameter', 'path', 'type', 'value', 'readonly'], props: ['codeAutocomplete', 'parameter', 'path', 'type', 'value', 'readonly'],
computed: {
...mapStores(
useNDVStore,
useRootStore,
useWorkflowsStore,
),
},
methods: { methods: {
loadAutocompleteData(): string[] { loadAutocompleteData(): string[] {
if (['function', 'functionItem'].includes(this.codeAutocomplete)) { if (['function', 'functionItem'].includes(this.codeAutocomplete)) {
@ -50,16 +61,16 @@ export default mixins(
const mode = 'manual'; const mode = 'manual';
let runIndex = 0; let runIndex = 0;
const executedWorkflow: IExecutionResponse | null = this.$store.getters.getWorkflowExecution; const executedWorkflow = this.workflowsStore.getWorkflowExecution;
const workflow = this.getCurrentWorkflow(); const workflow = this.getCurrentWorkflow();
const activeNode: INodeUi | null = this.$store.getters['ndv/activeNode']; const activeNode: INodeUi | null = this.ndvStore.activeNode;
const parentNode = workflow.getParentNodes(activeNode!.name, inputName, 1); const parentNode = workflow.getParentNodes(activeNode!.name, inputName, 1);
const nodeConnection = workflow.getNodeConnectionIndexes(activeNode!.name, parentNode[0]) || { const nodeConnection = workflow.getNodeConnectionIndexes(activeNode!.name, parentNode[0]) || {
sourceIndex: 0, sourceIndex: 0,
destinationIndex: 0, destinationIndex: 0,
}; };
const executionData = this.$store.getters.getWorkflowExecution as IExecutionResponse | null; const executionData = this.workflowsStore.getWorkflowExecution;
let runExecutionData: IRunExecutionData; let runExecutionData: IRunExecutionData;
if (!executionData || !executionData.data) { if (!executionData || !executionData.data) {
@ -89,7 +100,7 @@ export default mixins(
$resumeWebhookUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME, $resumeWebhookUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
}; };
const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, activeNode!.name, connectionInputData || [], {}, mode, this.$store.getters.timezone, additionalProxyKeys); const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, activeNode!.name, connectionInputData || [], {}, mode, this.rootStore.timezone, additionalProxyKeys);
const proxy = dataProxy.getDataProxy(); const proxy = dataProxy.getDataProxy();
const autoCompleteItems = [ const autoCompleteItems = [

View file

@ -17,6 +17,8 @@ import { workflowHelpers } from '../mixins/workflowHelpers'; // for json field c
import { codeNodeEditorEventBus } from '@/event-bus/code-node-editor-event-bus'; import { codeNodeEditorEventBus } from '@/event-bus/code-node-editor-event-bus';
import { CODE_NODE_TYPE } from '@/constants'; import { CODE_NODE_TYPE } from '@/constants';
import { ALL_ITEMS_PLACEHOLDER, EACH_ITEM_PLACEHOLDER } from './constants'; import { ALL_ITEMS_PLACEHOLDER, EACH_ITEM_PLACEHOLDER } from './constants';
import { mapStores } from 'pinia';
import { useRootStore } from '@/stores/n8nRootStore';
export default mixins(linterExtension, completerExtension, workflowHelpers).extend({ export default mixins(linterExtension, completerExtension, workflowHelpers).extend({
name: 'code-node-editor', name: 'code-node-editor',
@ -47,6 +49,9 @@ export default mixins(linterExtension, completerExtension, workflowHelpers).exte
}, },
}, },
computed: { computed: {
...mapStores(
useRootStore,
),
content(): string { content(): string {
if (!this.editor) return ''; if (!this.editor) return '';
@ -119,7 +124,7 @@ export default mixins(linterExtension, completerExtension, workflowHelpers).exte
} }
this.$telemetry.track('User autocompleted code', { this.$telemetry.track('User autocompleted code', {
instance_id: this.$store.getters.instanceId, instance_id: this.rootStore.instanceId,
node_type: CODE_NODE_TYPE, node_type: CODE_NODE_TYPE,
field_name: this.mode === 'runOnceForAllItems' ? 'jsCodeAllItems' : 'jsCodeEachItem', field_name: this.mode === 'runOnceForAllItems' ? 'jsCodeAllItems' : 'jsCodeEachItem',
field_type: 'code', field_type: 'code',

View file

@ -4,6 +4,8 @@ import { addVarType } from '../utils';
import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete'; import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
import type { INodeUi } from '@/Interface'; import type { INodeUi } from '@/Interface';
import type { CodeNodeEditorMixin } from '../types'; import type { CodeNodeEditorMixin } from '../types';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
function getAutocompletableNodeNames(nodes: INodeUi[]) { function getAutocompletableNodeNames(nodes: INodeUi[]) {
return nodes return nodes
@ -12,6 +14,11 @@ function getAutocompletableNodeNames(nodes: INodeUi[]) {
} }
export const baseCompletions = (Vue as CodeNodeEditorMixin).extend({ export const baseCompletions = (Vue as CodeNodeEditorMixin).extend({
computed: {
...mapStores(
useWorkflowsStore,
),
},
methods: { methods: {
/** /**
* - Complete `$` to `$execution $input $prevNode $runIndex $workflow $now $today * - Complete `$` to `$execution $input $prevNode $runIndex $workflow $now $today
@ -58,7 +65,7 @@ export const baseCompletions = (Vue as CodeNodeEditorMixin).extend({
const options: Completion[] = TOP_LEVEL_COMPLETIONS_IN_BOTH_MODES.map(addVarType); const options: Completion[] = TOP_LEVEL_COMPLETIONS_IN_BOTH_MODES.map(addVarType);
options.push( options.push(
...getAutocompletableNodeNames(this.$store.getters.allNodes).map((nodeName) => { ...getAutocompletableNodeNames(this.workflowsStore.allNodes).map((nodeName) => {
return { return {
label: `$('${nodeName}')`, label: `$('${nodeName}')`,
type: 'variable', type: 'variable',
@ -96,7 +103,7 @@ export const baseCompletions = (Vue as CodeNodeEditorMixin).extend({
if (!preCursor || (preCursor.from === preCursor.to && !context.explicit)) return null; if (!preCursor || (preCursor.from === preCursor.to && !context.explicit)) return null;
const options: Completion[] = getAutocompletableNodeNames(this.$store.getters.allNodes).map( const options: Completion[] = getAutocompletableNodeNames(this.workflowsStore.allNodes).map(
(nodeName) => { (nodeName) => {
return { return {
label: `$('${nodeName}')`, label: `$('${nodeName}')`,

View file

@ -3,8 +3,17 @@ import { isAllowedInDotNotation, escape, toVariableOption } from '../utils';
import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete'; import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
import type { IDataObject, IPinData, IRunData } from 'n8n-workflow'; import type { IDataObject, IPinData, IRunData } from 'n8n-workflow';
import type { CodeNodeEditorMixin } from '../types'; import type { CodeNodeEditorMixin } from '../types';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
export const jsonFieldCompletions = (Vue as CodeNodeEditorMixin).extend({ export const jsonFieldCompletions = (Vue as CodeNodeEditorMixin).extend({
computed: {
...mapStores(
useNDVStore,
useWorkflowsStore,
),
},
methods: { methods: {
/** /**
* - Complete `x.first().json.` to `.field`. * - Complete `x.first().json.` to `.field`.
@ -206,11 +215,13 @@ export const jsonFieldCompletions = (Vue as CodeNodeEditorMixin).extend({
getInputNodeName() { getInputNodeName() {
try { try {
const activeNode = this.$store.getters['ndv/activeNode']; const activeNode = this.ndvStore.activeNode;
const workflow = this.getCurrentWorkflow(); if (activeNode) {
const input = workflow.connectionsByDestinationNode[activeNode.name]; const workflow = this.getCurrentWorkflow();
const input = workflow.connectionsByDestinationNode[activeNode.name];
return input.main[0][0].node; return input.main[0][0].node;
}
} catch (_) { } catch (_) {
return null; return null;
} }
@ -263,7 +274,7 @@ export const jsonFieldCompletions = (Vue as CodeNodeEditorMixin).extend({
getJsonOutput(quotedNodeName: string, options?: { accessor?: string; index?: number }) { getJsonOutput(quotedNodeName: string, options?: { accessor?: string; index?: number }) {
const nodeName = quotedNodeName.replace(/['"]/g, ''); const nodeName = quotedNodeName.replace(/['"]/g, '');
const pinData: IPinData | undefined = this.$store.getters.pinData; const pinData: IPinData | undefined = this.workflowsStore.getPinData;
const nodePinData = pinData && pinData[nodeName]; const nodePinData = pinData && pinData[nodeName];
@ -279,7 +290,7 @@ export const jsonFieldCompletions = (Vue as CodeNodeEditorMixin).extend({
} catch (_) {} } catch (_) {}
} }
const runData: IRunData | null = this.$store.getters.getWorkflowRunData; const runData: IRunData | null = this.workflowsStore.getWorkflowRunData;
const nodeRunData = runData && runData[nodeName]; const nodeRunData = runData && runData[nodeName];

View file

@ -2,6 +2,7 @@ import Vue from 'vue';
import { AUTOCOMPLETABLE_BUILT_IN_MODULES } from '../constants'; import { AUTOCOMPLETABLE_BUILT_IN_MODULES } from '../constants';
import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete'; import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
import type { CodeNodeEditorMixin } from '../types'; import type { CodeNodeEditorMixin } from '../types';
import { useSettingsStore } from '@/stores/settings';
export const requireCompletions = (Vue as CodeNodeEditorMixin).extend({ export const requireCompletions = (Vue as CodeNodeEditorMixin).extend({
methods: { methods: {
@ -14,22 +15,26 @@ export const requireCompletions = (Vue as CodeNodeEditorMixin).extend({
if (!preCursor || (preCursor.from === preCursor.to && !context.explicit)) return null; if (!preCursor || (preCursor.from === preCursor.to && !context.explicit)) return null;
const options: Completion[] = []; const options: Completion[] = [];
const settingsStore = useSettingsStore();
const allowedModules = this.$store.getters['settings/allowedModules']; const allowedModules = settingsStore.allowedModules;
const toOption = (moduleName: string) => ({ const toOption = (moduleName: string) => ({
label: `require('${moduleName}');`, label: `require('${moduleName}');`,
type: 'variable', type: 'variable',
}); });
if (allowedModules?.builtIn?.includes('*')) { if (allowedModules.builtIn) {
options.push(...AUTOCOMPLETABLE_BUILT_IN_MODULES.map(toOption)); if (allowedModules.builtIn.includes('*')) {
} else if (allowedModules?.builtIn?.length > 0) { options.push(...AUTOCOMPLETABLE_BUILT_IN_MODULES.map(toOption));
options.push(...allowedModules.builtIn.map(toOption)); } else if (allowedModules?.builtIn?.length > 0) {
options.push(...allowedModules.builtIn.map(toOption));
}
} }
if (allowedModules?.external?.length > 0) { if (allowedModules.external) {
options.push(...allowedModules.external.map(toOption)); if (allowedModules?.external?.length > 0) {
options.push(...allowedModules.external.map(toOption));
}
} }
return { return {

View file

@ -49,6 +49,8 @@ import { get } from 'lodash';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import {Component} from "vue"; import {Component} from "vue";
import { mapStores } from 'pinia';
import { useNDVStore } from '@/stores/ndv';
export default mixins( export default mixins(
nodeHelpers, nodeHelpers,
@ -72,6 +74,9 @@ export default mixins(
}; };
}, },
computed: { computed: {
...mapStores(
useNDVStore,
),
getPlaceholderText (): string { getPlaceholderText (): string {
const placeholder = this.$locale.nodeText().placeholder(this.parameter, this.path); const placeholder = this.$locale.nodeText().placeholder(this.parameter, this.path);
return placeholder ? placeholder : this.$locale.baseText('collectionParameter.choose'); return placeholder ? placeholder : this.$locale.baseText('collectionParameter.choose');
@ -93,8 +98,8 @@ export default mixins(
return this.displayNodeParameter(option as INodeProperties); return this.displayNodeParameter(option as INodeProperties);
}); });
}, },
node (): INodeUi { node (): INodeUi | null {
return this.$store.getters['ndv/activeNode']; return this.ndvStore.activeNode;
}, },
// Returns all the options which did not get added already // Returns all the options which did not get added already
parameterOptions (): Array<INodePropertyOptions | INodeProperties> { parameterOptions (): Array<INodePropertyOptions | INodeProperties> {

View file

@ -55,7 +55,9 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { useUIStore } from '@/stores/ui';
import { PublicInstalledPackage } from 'n8n-workflow'; import { PublicInstalledPackage } from 'n8n-workflow';
import { mapStores } from 'pinia';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { import {
NPM_PACKAGE_DOCS_BASE_URL, NPM_PACKAGE_DOCS_BASE_URL,
@ -91,6 +93,9 @@ export default mixins(
], ],
}; };
}, },
computed: {
...mapStores(useUIStore),
},
methods: { methods: {
async onAction(value: string) { async onAction(value: string) {
switch (value) { switch (value) {
@ -102,14 +107,14 @@ export default mixins(
window.open(`${NPM_PACKAGE_DOCS_BASE_URL}${this.communityPackage.packageName}`, '_blank'); window.open(`${NPM_PACKAGE_DOCS_BASE_URL}${this.communityPackage.packageName}`, '_blank');
break; break;
case COMMUNITY_PACKAGE_MANAGE_ACTIONS.UNINSTALL: case COMMUNITY_PACKAGE_MANAGE_ACTIONS.UNINSTALL:
this.$store.dispatch('ui/openCommunityPackageUninstallConfirmModal', this.communityPackage.packageName); this.uiStore.openCommunityPackageUninstallConfirmModal(this.communityPackage.packageName);
break; break;
default: default:
break; break;
} }
}, },
onUpdateClick() { onUpdateClick() {
this.$store.dispatch('ui/openCommunityPackageUpdateConfirmModal', this.communityPackage.packageName); this.uiStore.openCommunityPackageUpdateConfirmModal(this.communityPackage.packageName);
}, },
}, },
}); });

View file

@ -92,6 +92,8 @@ import {
} from '../constants'; } from '../constants';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { showMessage } from './mixins/showMessage'; import { showMessage } from './mixins/showMessage';
import { mapStores } from 'pinia';
import { useCommunityNodesStore } from '@/stores/communityNodes';
export default mixins( export default mixins(
showMessage, showMessage,
@ -114,6 +116,9 @@ export default mixins(
COMMUNITY_NODES_RISKS_DOCS_URL, COMMUNITY_NODES_RISKS_DOCS_URL,
}; };
}, },
computed: {
...mapStores(useCommunityNodesStore),
},
methods: { methods: {
openNPMPage() { openNPMPage() {
this.$telemetry.track('user clicked cnr browse button', { source: 'cnr install modal' }); this.$telemetry.track('user clicked cnr browse button', { source: 'cnr install modal' });
@ -127,9 +132,9 @@ export default mixins(
this.$telemetry.track('user started cnr package install', { input_string: this.packageName, source: 'cnr settings page' }); this.$telemetry.track('user started cnr package install', { input_string: this.packageName, source: 'cnr settings page' });
this.infoTextErrorMessage = ''; this.infoTextErrorMessage = '';
this.loading = true; this.loading = true;
await this.$store.dispatch('communityNodes/installPackage', this.packageName); await this.communityNodesStore.installPackage(this.packageName);
// TODO: We need to fetch a fresh list of installed packages until proper response is implemented on the back-end // TODO: We need to fetch a fresh list of installed packages until proper response is implemented on the back-end
await this.$store.dispatch('communityNodes/fetchInstalledPackages'); await this.communityNodesStore.fetchInstalledPackages();
this.loading = false; this.loading = false;
this.modalBus.$emit('close'); this.modalBus.$emit('close');
this.$showMessage({ this.$showMessage({

View file

@ -37,6 +37,8 @@ import mixins from 'vue-typed-mixins';
import Modal from './Modal.vue'; import Modal from './Modal.vue';
import { COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, COMMUNITY_PACKAGE_MANAGE_ACTIONS } from '../constants'; import { COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, COMMUNITY_PACKAGE_MANAGE_ACTIONS } from '../constants';
import { showMessage } from './mixins/showMessage'; import { showMessage } from './mixins/showMessage';
import { mapStores } from 'pinia';
import { useCommunityNodesStore } from '@/stores/communityNodes';
export default mixins(showMessage).extend({ export default mixins(showMessage).extend({
name: 'CommunityPackageManageConfirmModal', name: 'CommunityPackageManageConfirmModal',
@ -65,8 +67,9 @@ export default mixins(showMessage).extend({
}; };
}, },
computed: { computed: {
...mapStores(useCommunityNodesStore),
activePackage() { activePackage() {
return this.$store.getters['communityNodes/getInstalledPackageByName'](this.activePackageName); return this.communityNodesStore.getInstalledPackageByName(this.activePackageName);
}, },
getModalContent() { getModalContent() {
if (this.mode === COMMUNITY_PACKAGE_MANAGE_ACTIONS.UNINSTALL) { if (this.mode === COMMUNITY_PACKAGE_MANAGE_ACTIONS.UNINSTALL) {
@ -120,7 +123,7 @@ export default mixins(showMessage).extend({
package_author_email: this.activePackage.authorEmail, package_author_email: this.activePackage.authorEmail,
}); });
this.loading = true; this.loading = true;
await this.$store.dispatch('communityNodes/uninstallPackage', this.activePackageName); await this.communityNodesStore.uninstallPackage(this.activePackageName);
this.$showMessage({ this.$showMessage({
title: this.$locale.baseText('settings.communityNodes.messages.uninstall.success.title'), title: this.$locale.baseText('settings.communityNodes.messages.uninstall.success.title'),
type: 'success', type: 'success',
@ -144,7 +147,7 @@ export default mixins(showMessage).extend({
}); });
this.loading = true; this.loading = true;
const updatedVersion = this.activePackage.updateAvailable; const updatedVersion = this.activePackage.updateAvailable;
await this.$store.dispatch('communityNodes/updatePackage', this.activePackageName); await this.communityNodesStore.updatePackage(this.activePackageName);
this.$showMessage({ this.$showMessage({
title: this.$locale.baseText('settings.communityNodes.messages.update.success.title'), title: this.$locale.baseText('settings.communityNodes.messages.update.success.title'),
message: this.$locale.baseText('settings.communityNodes.messages.update.success.message', { message: this.$locale.baseText('settings.communityNodes.messages.update.success.message', {

View file

@ -35,12 +35,14 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { mapGetters } from 'vuex';
import { IN8nPromptResponse } from '@/Interface'; import { IN8nPromptResponse } from '@/Interface';
import { VALID_EMAIL_REGEX } from '@/constants'; import { VALID_EMAIL_REGEX } from '@/constants';
import { workflowHelpers } from '@/components/mixins/workflowHelpers'; import { workflowHelpers } from '@/components/mixins/workflowHelpers';
import Modal from './Modal.vue'; import Modal from './Modal.vue';
import { mapStores } from 'pinia';
import { useSettingsStore } from '@/stores/settings';
import { useRootStore } from '@/stores/n8nRootStore';
export default mixins(workflowHelpers).extend({ export default mixins(workflowHelpers).extend({
components: { Modal }, components: { Modal },
@ -53,19 +55,20 @@ export default mixins(workflowHelpers).extend({
}; };
}, },
computed: { computed: {
...mapGetters({ ...mapStores(
promptsData: 'settings/getPromptsData', useRootStore,
}), useSettingsStore,
),
title(): string { title(): string {
if (this.promptsData && this.promptsData.title) { if (this.settingsStore.promptsData && this.settingsStore.promptsData.title) {
return this.promptsData.title; return this.settingsStore.promptsData.title;
} }
return 'Youre a power user 💪'; return 'Youre a power user 💪';
}, },
description(): string { description(): string {
if (this.promptsData && this.promptsData.message) { if (this.settingsStore.promptsData && this.settingsStore.promptsData.message) {
return this.promptsData.message; return this.settingsStore.promptsData.message;
} }
return 'Your experience with n8n can help us improve — for you and our entire community.'; return 'Your experience with n8n can help us improve — for you and our entire community.';
@ -78,21 +81,18 @@ export default mixins(workflowHelpers).extend({
closeDialog(): void { closeDialog(): void {
if (!this.isEmailValid) { if (!this.isEmailValid) {
this.$telemetry.track('User closed email modal', { this.$telemetry.track('User closed email modal', {
instance_id: this.$store.getters.instanceId, instance_id: this.rootStore.instanceId,
email: null, email: null,
}); });
} }
}, },
async send() { async send() {
if (this.isEmailValid) { if (this.isEmailValid) {
const response: IN8nPromptResponse = await this.$store.dispatch( const response = await this.settingsStore.submitContactInfo(this.email) as IN8nPromptResponse;
'settings/submitContactInfo',
this.email,
);
if (response.updated) { if (response.updated) {
this.$telemetry.track('User closed email modal', { this.$telemetry.track('User closed email modal', {
instance_id: this.$store.getters.instanceId, instance_id: this.rootStore.instanceId,
email: this.email, email: this.email,
}); });
this.$showMessage({ this.$showMessage({

View file

@ -46,8 +46,10 @@ import {EnterpriseEditionFeature} from '@/constants';
import {showMessage} from "@/components/mixins/showMessage"; import {showMessage} from "@/components/mixins/showMessage";
import CredentialIcon from '@/components/CredentialIcon.vue'; import CredentialIcon from '@/components/CredentialIcon.vue';
import {getCredentialPermissions, IPermissions} from "@/permissions"; import {getCredentialPermissions, IPermissions} from "@/permissions";
import {mapGetters} from "vuex";
import dateformat from "dateformat"; import dateformat from "dateformat";
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useUsersStore } from '@/stores/users';
export const CREDENTIAL_LIST_ITEM_ACTIONS = { export const CREDENTIAL_LIST_ITEM_ACTIONS = {
OPEN: 'open', OPEN: 'open',
@ -86,8 +88,12 @@ export default mixins(
}, },
}, },
computed: { computed: {
...mapStores(
useUIStore,
useUsersStore,
),
currentUser (): IUser { currentUser (): IUser {
return this.$store.getters['users/currentUser']; return this.usersStore.currentUser || {} as IUser;
}, },
credentialType(): ICredentialType { credentialType(): ICredentialType {
return this.$store.getters['credentials/getCredentialTypeByName'](this.data.type); return this.$store.getters['credentials/getCredentialTypeByName'](this.data.type);
@ -114,7 +120,7 @@ export default mixins(
}, },
methods: { methods: {
async onClick() { async onClick() {
this.$store.dispatch('ui/openExistingCredential', { id: this.data.id}); this.uiStore.openExistingCredential(this.data.id);
}, },
async onAction(action: string) { async onAction(action: string) {
if (action === CREDENTIAL_LIST_ITEM_ACTIONS.OPEN) { if (action === CREDENTIAL_LIST_ITEM_ACTIONS.OPEN) {

View file

@ -102,6 +102,11 @@ import { addCredentialTranslation } from '@/plugins/i18n';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { BUILTIN_CREDENTIALS_DOCS_URL, EnterpriseEditionFeature } from '@/constants'; import { BUILTIN_CREDENTIALS_DOCS_URL, EnterpriseEditionFeature } from '@/constants';
import { IPermissions } from "@/permissions"; import { IPermissions } from "@/permissions";
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { useRootStore } from '@/stores/n8nRootStore';
import { useNDVStore } from '@/stores/ndv';
export default mixins(restApi).extend({ export default mixins(restApi).extend({
name: 'CredentialConfig', name: 'CredentialConfig',
@ -160,9 +165,9 @@ export default mixins(restApi).extend({
}; };
}, },
async beforeMount() { async beforeMount() {
if (this.$store.getters.defaultLocale === 'en') return; if (this.rootStore.defaultLocale === 'en') return;
this.$store.commit('setActiveCredentialType', this.credentialType.name); this.uiStore.activeCredentialType = this.credentialType.name;
const key = `n8n-nodes-base.credentials.${this.credentialType.name}`; const key = `n8n-nodes-base.credentials.${this.credentialType.name}`;
@ -172,10 +177,16 @@ export default mixins(restApi).extend({
addCredentialTranslation( addCredentialTranslation(
{ [this.credentialType.name]: credTranslation }, { [this.credentialType.name]: credTranslation },
this.$store.getters.defaultLocale, this.rootStore.defaultLocale,
); );
}, },
computed: { computed: {
...mapStores(
useNDVStore,
useRootStore,
useUIStore,
useWorkflowsStore,
),
appName(): string { appName(): string {
if (!this.credentialType) { if (!this.credentialType) {
return ''; return '';
@ -195,7 +206,7 @@ export default mixins(restApi).extend({
}, },
documentationUrl(): string { documentationUrl(): string {
const type = this.credentialType as ICredentialType; const type = this.credentialType as ICredentialType;
const activeNode = this.$store.getters['ndv/activeNode']; const activeNode = this.ndvStore.activeNode;
const isCommunityNode = activeNode ? isCommunityPackageName(activeNode.type) : false; const isCommunityNode = activeNode ? isCommunityPackageName(activeNode.type) : false;
if (!type || !type.documentationUrl) { if (!type || !type.documentationUrl) {
@ -219,7 +230,7 @@ export default mixins(restApi).extend({
this.parentTypes.includes('oAuth2Api') this.parentTypes.includes('oAuth2Api')
? 'oauth2' ? 'oauth2'
: 'oauth1'; : 'oauth1';
return this.$store.getters.oauthCallbackUrls[oauthType]; return this.rootStore.oauthCallbackUrls[oauthType as keyof {}];
}, },
showOAuthSuccessBanner(): boolean { showOAuthSuccessBanner(): boolean {
return this.isOAuthType && this.requiredPropertiesFilled && this.isOAuthConnected && !this.authError; return this.isOAuthType && this.requiredPropertiesFilled && this.isOAuthConnected && !this.authError;
@ -234,7 +245,7 @@ export default mixins(restApi).extend({
docs_link: this.documentationUrl, docs_link: this.documentationUrl,
credential_type: this.credentialTypeName, credential_type: this.credentialTypeName,
source: 'modal', source: 'modal',
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
}); });
}, },
}, },

View file

@ -109,6 +109,7 @@ import {
ICredentialsDecryptedResponse, ICredentialsDecryptedResponse,
ICredentialsResponse, ICredentialsResponse,
IFakeDoor, IFakeDoor,
IUser,
} from '@/Interface'; } from '@/Interface';
import { import {
@ -123,7 +124,6 @@ import {
INodeProperties, INodeProperties,
INodeTypeDescription, INodeTypeDescription,
ITelemetryTrackProperties, ITelemetryTrackProperties,
IUser,
NodeHelpers, NodeHelpers,
} from 'n8n-workflow'; } from 'n8n-workflow';
import CredentialIcon from '../CredentialIcon.vue'; import CredentialIcon from '../CredentialIcon.vue';
@ -141,10 +141,15 @@ import InlineNameEdit from '../InlineNameEdit.vue';
import {EnterpriseEditionFeature} from "@/constants"; import {EnterpriseEditionFeature} from "@/constants";
import {IDataObject} from "n8n-workflow"; import {IDataObject} from "n8n-workflow";
import FeatureComingSoon from '../FeatureComingSoon.vue'; import FeatureComingSoon from '../FeatureComingSoon.vue';
import {mapGetters} from "vuex";
import {getCredentialPermissions, IPermissions} from "@/permissions"; import {getCredentialPermissions, IPermissions} from "@/permissions";
import { IMenuItem } from 'n8n-design-system'; import { IMenuItem } from 'n8n-design-system';
import { BaseTextKey } from '@/plugins/i18n'; import { BaseTextKey } from '@/plugins/i18n';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useSettingsStore } from '@/stores/settings';
import { useUsersStore } from '@/stores/users';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
interface NodeAccessMap { interface NodeAccessMap {
[nodeType: string]: ICredentialNodeAccess | null; [nodeType: string]: ICredentialNodeAccess | null;
@ -236,7 +241,7 @@ export default mixins(showMessage, nodeHelpers).extend({
this.$externalHooks().run('credentialsEdit.credentialModalOpened', { this.$externalHooks().run('credentialsEdit.credentialModalOpened', {
credentialType: this.credentialTypeName, credentialType: this.credentialTypeName,
isEditingCredential: this.mode === 'edit', isEditingCredential: this.mode === 'edit',
activeNode: this.$store.getters['ndv/activeNode'], activeNode: this.ndvStore.activeNode,
}); });
setTimeout(() => { setTimeout(() => {
@ -253,7 +258,16 @@ export default mixins(showMessage, nodeHelpers).extend({
this.loading = false; this.loading = false;
}, },
computed: { computed: {
...mapGetters('users', ['currentUser']), ...mapStores(
useNDVStore,
useSettingsStore,
useUIStore,
useUsersStore,
useWorkflowsStore,
),
currentUser(): IUser {
return this.usersStore.currentUser || {} as IUser;
},
currentCredential(): ICredentialsResponse | null { currentCredential(): ICredentialsResponse | null {
if (!this.credentialId) { if (!this.credentialId) {
return null; return null;
@ -387,7 +401,7 @@ export default mixins(showMessage, nodeHelpers).extend({
return true; return true;
}, },
credentialsFakeDoorFeatures(): IFakeDoor[] { credentialsFakeDoorFeatures(): IFakeDoor[] {
return this.$store.getters['ui/getFakeDoorByLocation']('credentialsModal'); return this.uiStore.getFakeDoorByLocation('credentialsModal');
}, },
credentialPermissions(): IPermissions { credentialPermissions(): IPermissions {
if (this.loading) { if (this.loading) {
@ -431,7 +445,7 @@ export default mixins(showMessage, nodeHelpers).extend({
return items; return items;
}, },
isSharingAvailable(): boolean { isSharingAvailable(): boolean {
return this.$store.getters['settings/isEnterpriseFeatureEnabled'](EnterpriseEditionFeature.Sharing) === true; return this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing);
}, },
}, },
methods: { methods: {
@ -561,13 +575,13 @@ export default mixins(showMessage, nodeHelpers).extend({
this.activeTab = tab; this.activeTab = tab;
const tabName: string = tab.replaceAll('coming-soon/', ''); const tabName: string = tab.replaceAll('coming-soon/', '');
const credType: string = this.credentialType ? this.credentialType.name : ''; const credType: string = this.credentialType ? this.credentialType.name : '';
const activeNode: INode | null = this.$store.getters['ndv/activeNode']; const activeNode: INode | null = this.ndvStore.activeNode;
this.$telemetry.track('User viewed credential tab', { this.$telemetry.track('User viewed credential tab', {
credential_type: credType, credential_type: credType,
node_type: activeNode ? activeNode.type : null, node_type: activeNode ? activeNode.type : null,
tab: tabName, tab: tabName,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
credential_id: this.credentialId, credential_id: this.credentialId,
sharing_enabled: EnterpriseEditionFeature.Sharing, sharing_enabled: EnterpriseEditionFeature.Sharing,
}); });
@ -715,7 +729,7 @@ export default mixins(showMessage, nodeHelpers).extend({
let sharedWith: IUser[] | undefined; let sharedWith: IUser[] | undefined;
let ownedBy: IUser | undefined; let ownedBy: IUser | undefined;
if (this.$store.getters['settings/isEnterpriseFeatureEnabled'](EnterpriseEditionFeature.Sharing)) { if (this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
sharedWith = this.credentialData.sharedWith as unknown as IUser[]; sharedWith = this.credentialData.sharedWith as unknown as IUser[];
ownedBy = this.credentialData.ownedBy as unknown as IUser; ownedBy = this.credentialData.ownedBy as unknown as IUser;
} }
@ -765,7 +779,7 @@ export default mixins(showMessage, nodeHelpers).extend({
const trackProperties: ITelemetryTrackProperties = { const trackProperties: ITelemetryTrackProperties = {
credential_type: credentialDetails.type, credential_type: credentialDetails.type,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
credential_id: credential.id, credential_id: credential.id,
is_complete: !!this.requiredPropertiesFilled, is_complete: !!this.requiredPropertiesFilled,
is_new: isNewCredential, is_new: isNewCredential,
@ -777,8 +791,8 @@ export default mixins(showMessage, nodeHelpers).extend({
trackProperties.is_valid = !!this.testedSuccessfully; trackProperties.is_valid = !!this.testedSuccessfully;
} }
if (this.$store.getters['ndv/activeNode']) { if (this.ndvStore.activeNode) {
trackProperties.node_type = this.$store.getters['ndv/activeNode'].type; trackProperties.node_type = this.ndvStore.activeNode.type;
} }
if (this.authError && this.authError !== '') { if (this.authError && this.authError !== '') {
@ -821,7 +835,7 @@ export default mixins(showMessage, nodeHelpers).extend({
this.$telemetry.track('User created credentials', { this.$telemetry.track('User created credentials', {
credential_type: credentialDetails.type, credential_type: credentialDetails.type,
credential_id: credential.id, credential_id: credential.id,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
}); });
return credential; return credential;

View file

@ -15,7 +15,7 @@
v-if="credentialPermissions.updateSharing" v-if="credentialPermissions.updateSharing"
size="large" size="large"
:users="usersList" :users="usersList"
:currentUserId="currentUser.id" :currentUserId="usersStore.currentUser.id"
:placeholder="$locale.baseText('credentialEdit.credentialSharing.select.placeholder')" :placeholder="$locale.baseText('credentialEdit.credentialSharing.select.placeholder')"
@input="onAddSharee" @input="onAddSharee"
> >
@ -25,7 +25,7 @@
</n8n-user-select> </n8n-user-select>
<n8n-users-list <n8n-users-list
:users="sharedWithList" :users="sharedWithList"
:currentUserId="currentUser.id" :currentUserId="usersStore.currentUser.id"
:delete-label="$locale.baseText('credentialEdit.credentialSharing.list.delete')" :delete-label="$locale.baseText('credentialEdit.credentialSharing.list.delete')"
:readonly="!credentialPermissions.updateSharing" :readonly="!credentialPermissions.updateSharing"
@delete="onRemoveSharee" @delete="onRemoveSharee"
@ -34,11 +34,11 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue';
import { mapGetters } from 'vuex';
import {IUser} from "@/Interface"; import {IUser} from "@/Interface";
import mixins from "vue-typed-mixins"; import mixins from "vue-typed-mixins";
import {showMessage} from "@/components/mixins/showMessage"; import {showMessage} from "@/components/mixins/showMessage";
import { mapStores } from 'pinia';
import { useUsersStore } from '@/stores/users';
export default mixins( export default mixins(
showMessage, showMessage,
@ -46,10 +46,10 @@ export default mixins(
name: 'CredentialSharing', name: 'CredentialSharing',
props: ['credential', 'credentialId', 'credentialData', 'sharedWith', 'credentialPermissions'], props: ['credential', 'credentialId', 'credentialData', 'sharedWith', 'credentialPermissions'],
computed: { computed: {
...mapGetters('users', ['allUsers', 'currentUser']), ...mapStores(useUsersStore),
usersList(): IUser[] { usersList(): IUser[] {
return this.allUsers.filter((user: IUser) => { return this.usersStore.allUsers.filter((user: IUser) => {
const isCurrentUser = user.id === this.currentUser.id; const isCurrentUser = user.id === this.usersStore.currentUser?.id;
const isAlreadySharedWithUser = (this.credentialData.sharedWith || []).find((sharee: IUser) => sharee.id === user.id); const isAlreadySharedWithUser = (this.credentialData.sharedWith || []).find((sharee: IUser) => sharee.id === user.id);
return !isCurrentUser && !isAlreadySharedWithUser; return !isCurrentUser && !isAlreadySharedWithUser;
@ -58,7 +58,7 @@ export default mixins(
sharedWithList(): IUser[] { sharedWithList(): IUser[] {
return [ return [
{ {
...(this.credential ? this.credential.ownedBy : this.currentUser), ...(this.credential ? this.credential.ownedBy : this.usersStore.currentUser),
isOwner: true, isOwner: true,
}, },
].concat(this.credentialData.sharedWith || []); ].concat(this.credentialData.sharedWith || []);
@ -69,30 +69,30 @@ export default mixins(
}, },
methods: { methods: {
async onAddSharee(userId: string) { async onAddSharee(userId: string) {
const { id, firstName, lastName, email } = this.$store.getters['users/getUserById'](userId); const sharee = this.usersStore.getUserById(userId);
const sharee = { id, firstName, lastName, email };
this.$emit('change', (this.credentialData.sharedWith || []).concat(sharee)); this.$emit('change', (this.credentialData.sharedWith || []).concat(sharee));
}, },
async onRemoveSharee(userId: string) { async onRemoveSharee(userId: string) {
const user = this.$store.getters['users/getUserById'](userId); const user = this.usersStore.getUserById(userId);
const confirm = await this.confirmMessage( if (user) {
this.$locale.baseText('credentialEdit.credentialSharing.list.delete.confirm.message', { interpolate: { name: user.fullName } }), const confirm = await this.confirmMessage(
this.$locale.baseText('credentialEdit.credentialSharing.list.delete.confirm.title'), this.$locale.baseText('credentialEdit.credentialSharing.list.delete.confirm.message', { interpolate: { name: user.fullName || '' } }),
null, this.$locale.baseText('credentialEdit.credentialSharing.list.delete.confirm.title'),
this.$locale.baseText('credentialEdit.credentialSharing.list.delete.confirm.confirmButtonText'), null,
this.$locale.baseText('credentialEdit.credentialSharing.list.delete.confirm.cancelButtonText'), this.$locale.baseText('credentialEdit.credentialSharing.list.delete.confirm.confirmButtonText'),
); this.$locale.baseText('credentialEdit.credentialSharing.list.delete.confirm.cancelButtonText'),
);
if (confirm) { if (confirm) {
this.$emit('change', this.credentialData.sharedWith.filter((sharee: IUser) => { this.$emit('change', this.credentialData.sharedWith.filter((sharee: IUser) => {
return sharee.id !== user.id; return sharee.id !== user.id;
})); }));
}
} }
}, },
async loadUsers() { async loadUsers() {
await this.$store.dispatch('users/fetchUsers'); await this.usersStore.fetchUsers();
}, },
}, },
mounted() { mounted() {

View file

@ -17,6 +17,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { useRootStore } from '@/stores/n8nRootStore';
import { mapStores } from 'pinia';
import Vue from 'vue'; import Vue from 'vue';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
@ -27,8 +29,11 @@ export default Vue.extend({
}, },
}, },
computed: { computed: {
...mapStores(
useRootStore,
),
basePath(): string { basePath(): string {
return this.$store.getters.getBaseUrl; return this.rootStore.baseUrl;
}, },
}, },
}); });

View file

@ -7,7 +7,10 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { useRootStore } from '@/stores/n8nRootStore';
import { useNodeTypesStore } from '@/stores/nodeTypes';
import { ICredentialType, INodeTypeDescription } from 'n8n-workflow'; import { ICredentialType, INodeTypeDescription } from 'n8n-workflow';
import { mapStores } from 'pinia';
import Vue from 'vue'; import Vue from 'vue';
export default Vue.extend({ export default Vue.extend({
@ -17,6 +20,10 @@ export default Vue.extend({
}, },
}, },
computed: { computed: {
...mapStores(
useNodeTypesStore,
useRootStore,
),
credentialWithIcon(): ICredentialType | null { credentialWithIcon(): ICredentialType | null {
return this.credentialTypeName ? this.getCredentialWithIcon(this.credentialTypeName) : null; return this.credentialTypeName ? this.getCredentialWithIcon(this.credentialTypeName) : null;
}, },
@ -26,15 +33,14 @@ export default Vue.extend({
return null; return null;
} }
const restUrl = this.$store.getters.getRestUrl; const restUrl = this.rootStore.getRestUrl;
return `${restUrl}/credential-icon/${this.credentialWithIcon.name}`; return `${restUrl}/credential-icon/${this.credentialWithIcon.name}`;
}, },
relevantNode(): INodeTypeDescription | null { relevantNode(): INodeTypeDescription | null {
if (this.credentialWithIcon && this.credentialWithIcon.icon && this.credentialWithIcon.icon.startsWith('node:')) { if (this.credentialWithIcon && this.credentialWithIcon.icon && this.credentialWithIcon.icon.startsWith('node:')) {
const nodeType = this.credentialWithIcon.icon.replace('node:', ''); const nodeType = this.credentialWithIcon.icon.replace('node:', '');
return this.nodeTypesStore.getNodeType(nodeType);
return this.$store.getters['nodeTypes/getNodeType'](nodeType);
} }
const nodesWithAccess = this.$store.getters['credentials/getNodesWithAccess'](this.credentialTypeName); const nodesWithAccess = this.$store.getters['credentials/getNodesWithAccess'](this.credentialTypeName);

View file

@ -56,6 +56,9 @@ import mixins from 'vue-typed-mixins';
import Modal from './Modal.vue'; import Modal from './Modal.vue';
import { CREDENTIAL_SELECT_MODAL_KEY } from '../constants'; import { CREDENTIAL_SELECT_MODAL_KEY } from '../constants';
import { externalHooks } from '@/components/mixins/externalHooks'; import { externalHooks } from '@/components/mixins/externalHooks';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
export default mixins(externalHooks).extend({ export default mixins(externalHooks).extend({
name: 'CredentialsSelectModal', name: 'CredentialsSelectModal',
@ -85,6 +88,10 @@ export default mixins(externalHooks).extend({
}; };
}, },
computed: { computed: {
...mapStores(
useUIStore,
useWorkflowsStore,
),
...mapGetters('credentials', ['allCredentialTypes']), ...mapGetters('credentials', ['allCredentialTypes']),
}, },
methods: { methods: {
@ -93,13 +100,13 @@ export default mixins(externalHooks).extend({
}, },
openCredentialType () { openCredentialType () {
this.modalBus.$emit('close'); this.modalBus.$emit('close');
this.$store.dispatch('ui/openNewCredential', { type: this.selected }); this.uiStore.openNewCredential(this.selected);
const telemetryPayload = { const telemetryPayload = {
credential_type: this.selected, credential_type: this.selected,
source: 'primary_menu', source: 'primary_menu',
new_credential: true, new_credential: true,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
}; };
this.$telemetry.track('User opened Credential modal', telemetryPayload); this.$telemetry.track('User opened Credential modal', telemetryPayload);

View file

@ -20,10 +20,10 @@
<div :class="$style.optionInput" v-if="operation === 'transfer'"> <div :class="$style.optionInput" v-if="operation === 'transfer'">
<n8n-input-label :label="$locale.baseText('settings.users.userToTransferTo')"> <n8n-input-label :label="$locale.baseText('settings.users.userToTransferTo')">
<n8n-user-select <n8n-user-select
:users="allUsers" :users="usersStore.allUsers"
:value="transferId" :value="transferId"
:ignoreIds="ignoreIds" :ignoreIds="ignoreIds"
:currentUserId="currentUserId" :currentUserId="usersStore.currentUserId"
@input="setTransferId" @input="setTransferId"
/> />
</n8n-input-label> </n8n-input-label>
@ -53,7 +53,8 @@ import { showMessage } from "@/components/mixins/showMessage";
import Modal from "./Modal.vue"; import Modal from "./Modal.vue";
import Vue from "vue"; import Vue from "vue";
import { IUser } from "../Interface"; import { IUser } from "../Interface";
import { mapGetters } from "vuex"; import { mapStores } from "pinia";
import { useUsersStore } from '@/stores/users';
export default mixins(showMessage).extend({ export default mixins(showMessage).extend({
components: { components: {
@ -79,13 +80,12 @@ export default mixins(showMessage).extend({
}; };
}, },
computed: { computed: {
...mapGetters('users', ['allUsers', 'currentUserId']), ...mapStores(useUsersStore),
userToDelete(): IUser { userToDelete(): IUser | null {
const getUserById = this.$store.getters['users/getUserById']; return this.usersStore.getUserById(this.activeId);
return getUserById(this.activeId);
}, },
isPending(): boolean { isPending(): boolean {
return this.userToDelete && !this.userToDelete.firstName; return this.userToDelete ? this.userToDelete && !this.userToDelete.firstName : false;
}, },
title(): string { title(): string {
const user = this.userToDelete && (this.userToDelete.fullName || this.userToDelete.email) || ''; const user = this.userToDelete && (this.userToDelete.fullName || this.userToDelete.email) || '';
@ -133,16 +133,17 @@ export default mixins(showMessage).extend({
params.transferId = this.transferId; params.transferId = this.transferId;
} }
await this.$store.dispatch('users/deleteUser', params); await this.usersStore.deleteUser(params);
let message = ''; let message = '';
if (this.transferId) { if (this.transferId) {
const getUserById = this.$store.getters['users/getUserById']; const transferUser: IUser | null = this.usersStore.getUserById(this.transferId);
const transferUser: IUser = getUserById(this.transferId); if (transferUser) {
message = this.$locale.baseText( message = this.$locale.baseText(
'settings.users.transferredToUser', 'settings.users.transferredToUser',
{ interpolate: { user: transferUser.fullName || '' }}, { interpolate: { user: transferUser.fullName || '' }},
); );
}
} }
this.$showMessage({ this.$showMessage({

View file

@ -21,6 +21,8 @@
<script lang="ts"> <script lang="ts">
import { XYPosition } from '@/Interface'; import { XYPosition } from '@/Interface';
import { useNDVStore } from '@/stores/ndv';
import { mapStores } from 'pinia';
import Vue from 'vue'; import Vue from 'vue';
// @ts-ignore // @ts-ignore
@ -66,11 +68,14 @@ export default Vue.extend({
}; };
}, },
computed: { computed: {
...mapStores(
useNDVStore,
),
canDrop(): boolean { canDrop(): boolean {
return this.$store.getters['ndv/canDraggableDrop']; return this.ndvStore.canDraggableDrop;
}, },
stickyPosition(): XYPosition | null { stickyPosition(): XYPosition | null {
return this.$store.getters['ndv/draggableStickyPos']; return this.ndvStore.draggableStickyPos;
}, },
}, },
methods: { methods: {
@ -111,7 +116,7 @@ export default Vue.extend({
this.isDragging = true; this.isDragging = true;
const data = this.targetDataKey && this.draggingEl ? this.draggingEl.dataset.value : (this.data || ''); const data = this.targetDataKey && this.draggingEl ? this.draggingEl.dataset.value : (this.data || '');
this.$store.commit('ndv/draggableStartDragging', {type: this.type, data }); this.ndvStore.draggableStartDragging({type: this.type, data: data || '' });
this.$emit('dragstart', this.draggingEl); this.$emit('dragstart', this.draggingEl);
document.body.style.cursor = 'grabbing'; document.body.style.cursor = 'grabbing';
@ -141,7 +146,7 @@ export default Vue.extend({
this.$emit('dragend', this.draggingEl); this.$emit('dragend', this.draggingEl);
this.isDragging = false; this.isDragging = false;
this.draggingEl = null; this.draggingEl = null;
this.$store.commit('ndv/draggableStopDragging'); this.ndvStore.draggableStopDragging();
}, 0); }, 0);
}, },
}, },

View file

@ -5,6 +5,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { useNDVStore } from '@/stores/ndv';
import { mapStores } from 'pinia';
import Vue from 'vue'; import Vue from 'vue';
export default Vue.extend({ export default Vue.extend({
@ -37,11 +39,14 @@ export default Vue.extend({
window.removeEventListener('mouseup', this.onMouseUp); window.removeEventListener('mouseup', this.onMouseUp);
}, },
computed: { computed: {
...mapStores(
useNDVStore,
),
isDragging(): boolean { isDragging(): boolean {
return this.$store.getters['ndv/isDraggableDragging']; return this.ndvStore.isDraggableDragging;
}, },
draggableType(): string { draggableType(): string {
return this.$store.getters['ndv/draggableType']; return this.ndvStore.draggableType;
}, },
droppable(): boolean { droppable(): boolean {
return !this.disabled && this.isDragging && this.draggableType === this.type; return !this.disabled && this.isDragging && this.draggableType === this.type;
@ -60,20 +65,20 @@ export default Vue.extend({
this.hovering = e.clientX >= dim.left && e.clientX <= dim.right && e.clientY >= dim.top && e.clientY <= dim.bottom; this.hovering = e.clientX >= dim.left && e.clientX <= dim.right && e.clientY >= dim.top && e.clientY <= dim.bottom;
if (!this.disabled && this.sticky && this.hovering) { if (!this.disabled && this.sticky && this.hovering) {
this.$store.commit('ndv/setDraggableStickyPos', [dim.left + this.stickyOffset, dim.top + this.stickyOffset]); this.ndvStore.setDraggableStickyPos([dim.left + this.stickyOffset, dim.top + this.stickyOffset]);
} }
} }
}, },
onMouseUp(e: MouseEvent) { onMouseUp(e: MouseEvent) {
if (this.activeDrop) { if (this.activeDrop) {
const data = this.$store.getters['ndv/draggableData']; const data = this.ndvStore.draggableData;
this.$emit('drop', data); this.$emit('drop', data);
} }
}, },
}, },
watch: { watch: {
activeDrop(active) { activeDrop(active) {
this.$store.commit('ndv/setDraggableCanDrop', active); this.ndvStore.setDraggableCanDrop(active);
}, },
}, },
}); });

View file

@ -16,7 +16,7 @@
:maxlength="MAX_WORKFLOW_NAME_LENGTH" :maxlength="MAX_WORKFLOW_NAME_LENGTH"
/> />
<TagsDropdown <TagsDropdown
v-if="areTagsEnabled" v-if="settingsStore.areTagsEnabled"
:createEnabled="true" :createEnabled="true"
:currentTagIds="currentTagIds" :currentTagIds="currentTagIds"
:eventBus="dropdownBus" :eventBus="dropdownBus"
@ -46,9 +46,10 @@ import { workflowHelpers } from "@/components/mixins/workflowHelpers";
import { showMessage } from "@/components/mixins/showMessage"; import { showMessage } from "@/components/mixins/showMessage";
import TagsDropdown from "@/components/TagsDropdown.vue"; import TagsDropdown from "@/components/TagsDropdown.vue";
import Modal from "./Modal.vue"; import Modal from "./Modal.vue";
import { mapGetters } from "vuex";
import {restApi} from "@/components/mixins/restApi"; import {restApi} from "@/components/mixins/restApi";
import {IWorkflowDb} from "@/Interface"; import { mapStores } from "pinia";
import { useSettingsStore } from "@/stores/settings";
import { useWorkflowsStore } from "@/stores/workflows";
export default mixins(showMessage, workflowHelpers, restApi).extend({ export default mixins(showMessage, workflowHelpers, restApi).extend({
components: { TagsDropdown, Modal }, components: { TagsDropdown, Modal },
@ -68,11 +69,14 @@ export default mixins(showMessage, workflowHelpers, restApi).extend({
}; };
}, },
async mounted() { async mounted() {
this.name = await this.$store.dispatch('workflows/getDuplicateCurrentWorkflowName', this.data.name); this.name = await this.workflowsStore.getDuplicateCurrentWorkflowName(this.data.name);
this.$nextTick(() => this.focusOnNameInput()); this.$nextTick(() => this.focusOnNameInput());
}, },
computed: { computed: {
...mapGetters('settings', ['areTagsEnabled']), ...mapStores(
useSettingsStore,
useWorkflowsStore,
),
}, },
watch: { watch: {
isActive(active) { isActive(active) {

View file

@ -8,6 +8,8 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import {EnterpriseEditionFeature} from "@/constants"; import {EnterpriseEditionFeature} from "@/constants";
import { mapStores } from 'pinia';
import { useSettingsStore } from '@/stores/settings';
export default Vue.extend({ export default Vue.extend({
name: 'EnterpriseEdition', name: 'EnterpriseEdition',
@ -18,9 +20,10 @@ export default Vue.extend({
}, },
}, },
computed: { computed: {
...mapStores(useSettingsStore),
canAccess(): boolean { canAccess(): boolean {
return this.features.reduce((acc: boolean, feature) => { return this.features.reduce((acc: boolean, feature) => {
return acc && !!this.$store.getters['settings/isEnterpriseFeatureEnabled'](feature); return acc && !!this.settingsStore.isEnterpriseFeatureEnabled(feature as EnterpriseEditionFeature);
}, true); }, true);
}, },
}, },

View file

@ -109,6 +109,9 @@ import {
INodeTypeDescription, INodeTypeDescription,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { sanitizeHtml } from '@/utils'; import { sanitizeHtml } from '@/utils';
import { mapStores } from 'pinia';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins( export default mixins(
copyPaste, copyPaste,
@ -122,15 +125,19 @@ export default mixins(
VueJsonPretty, VueJsonPretty,
}, },
computed: { computed: {
...mapStores(
useNodeTypesStore,
useNDVStore,
),
displayCause(): boolean { displayCause(): boolean {
return JSON.stringify(this.error.cause).length < MAX_DISPLAY_DATA_SIZE; return JSON.stringify(this.error.cause).length < MAX_DISPLAY_DATA_SIZE;
}, },
parameters (): INodeProperties[] { parameters (): INodeProperties[] {
const node = this.$store.getters['ndv/activeNode']; const node = this.ndvStore.activeNode;
if (!node) { if (!node) {
return []; return [];
} }
const nodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion); const nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
if (nodeType === null) { if (nodeType === null) {
return []; return [];

View file

@ -203,6 +203,9 @@ import {
} from 'lodash'; } from 'lodash';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
export default mixins( export default mixins(
externalHooks, externalHooks,
@ -249,7 +252,7 @@ export default mixins(
this.handleAutoRefreshToggle(); this.handleAutoRefreshToggle();
this.$externalHooks().run('executionsList.openDialog'); this.$externalHooks().run('executionsList.openDialog');
this.$telemetry.track('User opened Executions log', { workflow_id: this.$store.getters.workflowId }); this.$telemetry.track('User opened Executions log', { workflow_id: this.workflowsStore.workflowId });
}, },
beforeDestroy() { beforeDestroy() {
if (this.autoRefreshInterval) { if (this.autoRefreshInterval) {
@ -258,6 +261,10 @@ export default mixins(
} }
}, },
computed: { computed: {
...mapStores(
useUIStore,
useWorkflowsStore,
),
statuses () { statuses () {
return [ return [
{ {
@ -283,7 +290,7 @@ export default mixins(
]; ];
}, },
activeExecutions (): IExecutionsCurrentSummaryExtended[] { activeExecutions (): IExecutionsCurrentSummaryExtended[] {
return this.$store.getters.getActiveExecutions; return this.workflowsStore.activeExecutions;
}, },
combinedExecutions (): IExecutionsSummary[] { combinedExecutions (): IExecutionsSummary[] {
const returnData: IExecutionsSummary[] = []; const returnData: IExecutionsSummary[] = [];
@ -408,27 +415,28 @@ export default mixins(
await this.restApi().deleteExecutions(sendData); await this.restApi().deleteExecutions(sendData);
let removedCurrentlyLoadedExecution = false; let removedCurrentlyLoadedExecution = false;
let removedActiveExecution = false; let removedActiveExecution = false;
const currentWorkflow: string = this.$store.getters.workflowId; const currentWorkflow: string = this.workflowsStore.workflowId;
const activeExecution: IExecutionsSummary = this.$store.getters['workflows/getActiveWorkflowExecution']; const activeExecution: IExecutionsSummary | null = this.workflowsStore.activeWorkflowExecution;
// Also update current workflow executions view if needed // Also update current workflow executions view if needed
for (const selectedId of Object.keys(this.selectedItems)) { for (const selectedId of Object.keys(this.selectedItems)) {
const execution: IExecutionsSummary = this.$store.getters['workflows/getExecutionDataById'](selectedId); const execution: IExecutionsSummary | undefined = this.workflowsStore.getExecutionDataById(selectedId);
if (execution && execution.workflowId === currentWorkflow) { if (execution && execution.workflowId === currentWorkflow) {
this.$store.commit('workflows/deleteExecution', execution); this.workflowsStore.deleteExecution(execution);
removedCurrentlyLoadedExecution = true; removedCurrentlyLoadedExecution = true;
} }
if (execution.id === activeExecution.id) { if ((execution !== undefined && activeExecution !== null) && execution.id === activeExecution.id) {
removedActiveExecution = true; removedActiveExecution = true;
} }
} }
// Also update route if needed // Also update route if needed
if (removedCurrentlyLoadedExecution) { if (removedCurrentlyLoadedExecution) {
const currentWorkflowExecutions: IExecutionsSummary[] = this.$store.getters['workflows/currentWorkflowExecutions']; const currentWorkflowExecutions: IExecutionsSummary[] = this.workflowsStore.currentWorkflowExecutions;
if (currentWorkflowExecutions.length === 0) { if (currentWorkflowExecutions.length === 0) {
this.$store.commit('workflows/setActiveWorkflowExecution', null); this.workflowsStore.activeWorkflowExecution = null;
this.$router.push({ name: VIEWS.EXECUTION_HOME, params: { name: currentWorkflow } }); this.$router.push({ name: VIEWS.EXECUTION_HOME, params: { name: currentWorkflow } });
} else if (removedActiveExecution) { } else if (removedActiveExecution) {
this.$store.commit('workflows/setActiveWorkflowExecution', currentWorkflowExecutions[0]); this.workflowsStore.activeWorkflowExecution = currentWorkflowExecutions[0];
this.$router.push({ this.$router.push({
name: VIEWS.EXECUTION_PREVIEW, name: VIEWS.EXECUTION_PREVIEW,
params: { name: currentWorkflow, executionId: currentWorkflowExecutions[0].id }, params: { name: currentWorkflow, executionId: currentWorkflowExecutions[0].id },
@ -468,7 +476,7 @@ export default mixins(
this.retryExecution(commandData.row, loadWorkflow); this.retryExecution(commandData.row, loadWorkflow);
this.$telemetry.track('User clicked retry execution button', { this.$telemetry.track('User clicked retry execution button', {
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
execution_id: commandData.row.id, execution_id: commandData.row.id,
retry_type: loadWorkflow ? 'current' : 'original', retry_type: loadWorkflow ? 'current' : 'original',
}); });
@ -497,7 +505,7 @@ export default mixins(
} }
} }
this.$store.commit('setActiveExecutions', activeExecutions); this.workflowsStore.activeExecutions = activeExecutions;
}, },
async loadAutoRefresh () : Promise<void> { async loadAutoRefresh () : Promise<void> {
const filter = this.workflowFilterPast; const filter = this.workflowFilterPast;
@ -517,7 +525,7 @@ export default mixins(
} }
} }
this.$store.commit('setActiveExecutions', results[1]); this.workflowsStore.activeExecutions = results[1];
// execution IDs are typed as string, int conversion is necessary so we can order. // execution IDs are typed as string, int conversion is necessary so we can order.
const alreadyPresentExecutionIds = this.finishedExecutions.map(exec => parseInt(exec.id, 10)); const alreadyPresentExecutionIds = this.finishedExecutions.map(exec => parseInt(exec.id, 10));

View file

@ -65,6 +65,8 @@ import { showMessage } from '../mixins/showMessage';
import WorkflowPreview from '@/components/WorkflowPreview.vue'; import WorkflowPreview from '@/components/WorkflowPreview.vue';
import { executionHelpers, IExecutionUIData } from '../mixins/executionsHelpers'; import { executionHelpers, IExecutionUIData } from '../mixins/executionsHelpers';
import { VIEWS } from '../../constants'; import { VIEWS } from '../../constants';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
export default mixins(restApi, showMessage, executionHelpers).extend({ export default mixins(restApi, showMessage, executionHelpers).extend({
name: 'execution-preview', name: 'execution-preview',
@ -77,11 +79,14 @@ export default mixins(restApi, showMessage, executionHelpers).extend({
}; };
}, },
computed: { computed: {
...mapStores(
useUIStore,
),
executionUIDetails(): IExecutionUIData | null { executionUIDetails(): IExecutionUIData | null {
return this.activeExecution ? this.getExecutionUIDetails(this.activeExecution) : null; return this.activeExecution ? this.getExecutionUIDetails(this.activeExecution) : null;
}, },
sidebarCollapsed(): boolean { sidebarCollapsed(): boolean {
return this.$store.getters['ui/sidebarMenuCollapsed']; return this.uiStore.sidebarMenuCollapsed;
}, },
}, },
methods: { methods: {

View file

@ -26,6 +26,11 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { useRootStore } from '@/stores/n8nRootStore';
import { useSettingsStore } from '@/stores/settings';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { mapStores } from 'pinia';
import { PLACEHOLDER_EMPTY_WORKFLOW_ID, WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants'; import { PLACEHOLDER_EMPTY_WORKFLOW_ID, WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants';
import { deepCopy, IWorkflowSettings } from 'n8n-workflow'; import { deepCopy, IWorkflowSettings } from 'n8n-workflow';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
@ -60,9 +65,9 @@ export default mixins(workflowHelpers).extend({
}; };
}, },
mounted() { mounted() {
this.defaultValues.saveFailedExecutions = this.$store.getters.saveDataErrorExecution; this.defaultValues.saveFailedExecutions = this.settingsStore.saveDataErrorExecution;
this.defaultValues.saveSuccessfulExecutions = this.$store.getters.saveDataSuccessExecution; this.defaultValues.saveSuccessfulExecutions = this.settingsStore.saveDataSuccessExecution;
this.defaultValues.saveManualExecutions = this.$store.getters.saveManualExecutions; this.defaultValues.saveManualExecutions = this.settingsStore.saveManualExecutions;
this.updateSettings(this.workflowSettings); this.updateSettings(this.workflowSettings);
}, },
watch: { watch: {
@ -71,7 +76,13 @@ export default mixins(workflowHelpers).extend({
}, },
}, },
computed: { computed: {
accordionItems(): Object[] { ...mapStores(
useRootStore,
useSettingsStore,
useUIStore,
useWorkflowsStore,
),
accordionItems(): Object[] {
return [ return [
{ {
id: 'productionExecutions', id: 'productionExecutions',
@ -115,7 +126,7 @@ export default mixins(workflowHelpers).extend({
} }
}, },
workflowSettings(): IWorkflowSettings { workflowSettings(): IWorkflowSettings {
const workflowSettings = deepCopy(this.$store.getters.workflowSettings); const workflowSettings = deepCopy(this.workflowsStore.workflowSettings);
return workflowSettings; return workflowSettings;
}, },
accordionIcon(): { icon: string, color: string }|null { accordionIcon(): { icon: string, color: string }|null {
@ -125,16 +136,16 @@ export default mixins(workflowHelpers).extend({
return null; return null;
}, },
currentWorkflowId(): string { currentWorkflowId(): string {
return this.$store.getters.workflowId; return this.workflowsStore.workflowId;
}, },
isNewWorkflow(): boolean { isNewWorkflow(): boolean {
return !this.currentWorkflowId || (this.currentWorkflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID || this.currentWorkflowId === 'new'); return !this.currentWorkflowId || (this.currentWorkflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID || this.currentWorkflowId === 'new');
}, },
workflowName(): string { workflowName(): string {
return this.$store.getters.workflowName; return this.workflowsStore.workflowName;
}, },
currentWorkflowTagIds(): string[] { currentWorkflowTagIds(): string[] {
return this.$store.getters.workflowTags; return this.workflowsStore.workflowTags;
}, },
}, },
methods: { methods: {
@ -146,17 +157,17 @@ export default mixins(workflowHelpers).extend({
onAccordionClick(event: MouseEvent): void { onAccordionClick(event: MouseEvent): void {
if (event.target instanceof HTMLAnchorElement) { if (event.target instanceof HTMLAnchorElement) {
event.preventDefault(); event.preventDefault();
this.$store.dispatch('ui/openModal', WORKFLOW_SETTINGS_MODAL_KEY); this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
} }
}, },
onItemTooltipClick(item: string, event: MouseEvent): void { onItemTooltipClick(item: string, event: MouseEvent): void {
if (item === 'productionExecutions' && event.target instanceof HTMLAnchorElement) { if (item === 'productionExecutions' && event.target instanceof HTMLAnchorElement) {
event.preventDefault(); event.preventDefault();
this.$store.dispatch('ui/openModal', WORKFLOW_SETTINGS_MODAL_KEY); this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
} }
}, },
openWorkflowSettings(event: MouseEvent): void { openWorkflowSettings(event: MouseEvent): void {
this.$store.dispatch('ui/openModal', WORKFLOW_SETTINGS_MODAL_KEY); this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
}, },
async onSaveWorkflowClick(event: MouseEvent): void { async onSaveWorkflowClick(event: MouseEvent): void {
let currentId = undefined; let currentId = undefined;
@ -166,7 +177,7 @@ export default mixins(workflowHelpers).extend({
currentId = this.$route.params.name; currentId = this.$route.params.name;
} }
const saved = await this.saveCurrentWorkflow({ id: currentId, name: this.workflowName, tags: this.currentWorkflowTagIds }); const saved = await this.saveCurrentWorkflow({ id: currentId, name: this.workflowName, tags: this.currentWorkflowTagIds });
if (saved) this.$store.dispatch('settings/fetchPromptsData'); if (saved) this.settingsStore.fetchPromptsData();
}, },
}, },
}); });

View file

@ -27,7 +27,9 @@
<script lang="ts"> <script lang="ts">
import { PLACEHOLDER_EMPTY_WORKFLOW_ID, VIEWS } from '@/constants'; import { PLACEHOLDER_EMPTY_WORKFLOW_ID, VIEWS } from '@/constants';
import { IExecutionsSummary } from '@/Interface'; import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { mapStores } from 'pinia';
import Vue from 'vue'; import Vue from 'vue';
import ExecutionsInfoAccordion from './ExecutionsInfoAccordion.vue'; import ExecutionsInfoAccordion from './ExecutionsInfoAccordion.vue';
@ -37,24 +39,25 @@ export default Vue.extend({
ExecutionsInfoAccordion, ExecutionsInfoAccordion,
}, },
computed: { computed: {
...mapStores(
useUIStore,
useWorkflowsStore,
),
executionCount(): number { executionCount(): number {
return (this.$store.getters['workflows/currentWorkflowExecutions'] as IExecutionsSummary[]).length; return this.workflowsStore.currentWorkflowExecutions.length;
}, },
containsTrigger(): boolean { containsTrigger(): boolean {
return this.$store.getters.workflowTriggerNodes.length > 0; return this.workflowsStore.workflowTriggerNodes.length > 0;
},
currentWorkflowId(): string {
return this.$store.getters.workflowId;
}, },
}, },
methods: { methods: {
onSetupFirstStep(event: MouseEvent): void { onSetupFirstStep(event: MouseEvent): void {
this.$store.commit('ui/setAddFirstStepOnLoad', true); this.uiStore.addFirstStepOnLoad = true;
const workflowRoute = this.getWorkflowRoute(); const workflowRoute = this.getWorkflowRoute();
this.$router.push(workflowRoute); this.$router.push(workflowRoute);
}, },
getWorkflowRoute(): { name: string, params: {}} { getWorkflowRoute(): { name: string, params: {}} {
const workflowId = this.currentWorkflowId || this.$route.params.name; const workflowId = this.workflowsStore.workflowId || this.$route.params.name;
if (workflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID) { if (workflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID) {
return { name: VIEWS.NEW_WORKFLOW, params: {} }; return { name: VIEWS.NEW_WORKFLOW, params: {} };
} else { } else {

View file

@ -90,6 +90,8 @@ import { IExecutionsSummary } from "@/Interface";
import { Route } from 'vue-router'; import { Route } from 'vue-router';
import Vue from 'vue'; import Vue from 'vue';
import { PropType } from 'vue'; import { PropType } from 'vue';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
export default Vue.extend({ export default Vue.extend({
name: 'executions-sidebar', name: 'executions-sidebar',
@ -122,6 +124,9 @@ export default Vue.extend({
}; };
}, },
computed: { computed: {
...mapStores(
useUIStore,
),
statusFilterApplied(): boolean { statusFilterApplied(): boolean {
return this.filter.status !== ''; return this.filter.status !== '';
}, },
@ -143,7 +148,7 @@ export default Vue.extend({
}, },
}, },
mounted() { mounted() {
this.autoRefresh = this.$store.getters['ui/isExecutionSidebarAutoRefreshOn']; this.autoRefresh = this.uiStore.executionSidebarAutoRefresh === true;
if (this.autoRefresh) { if (this.autoRefresh) {
this.autoRefreshInterval = setInterval(() => this.onRefresh(), 4000); this.autoRefreshInterval = setInterval(() => this.onRefresh(), 4000);
} }
@ -179,7 +184,7 @@ export default Vue.extend({
this.$emit('reloadExecutions'); this.$emit('reloadExecutions');
}, },
onAutoRefreshToggle(): void { onAutoRefreshToggle(): void {
this.$store.commit('ui/setExecutionsSidebarAutoRefresh', this.autoRefresh); this.uiStore.executionSidebarAutoRefresh = this.autoRefresh;
if (this.autoRefreshInterval) { if (this.autoRefreshInterval) {
// Clear any previously existing intervals (if any - there shouldn't) // Clear any previously existing intervals (if any - there shouldn't)
clearInterval(this.autoRefreshInterval); clearInterval(this.autoRefreshInterval);

View file

@ -37,6 +37,11 @@ import { range as _range } from 'lodash';
import { debounceHelper } from '../mixins/debounce'; import { debounceHelper } from '../mixins/debounce';
import { getNodeViewTab } from '../helpers'; import { getNodeViewTab } from '../helpers';
import { workflowHelpers } from '../mixins/workflowHelpers'; import { workflowHelpers } from '../mixins/workflowHelpers';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useUIStore } from '@/stores/ui';
import { useSettingsStore } from '@/stores/settings';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins(restApi, showMessage, executionHelpers, debounceHelper, workflowHelpers).extend({ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, workflowHelpers).extend({
name: 'executions-page', name: 'executions-page',
@ -51,6 +56,12 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
}; };
}, },
computed: { computed: {
...mapStores(
useNodeTypesStore,
useSettingsStore,
useUIStore,
useWorkflowsStore,
),
hidePreview(): boolean { hidePreview(): boolean {
const nothingToShow = this.executions.length === 0 && this.filterApplied; const nothingToShow = this.executions.length === 0 && this.filterApplied;
const activeNotPresent = this.filterApplied && (this.executions as IExecutionsSummary[]).find(ex => ex.id === this.activeExecution.id) === undefined; const activeNotPresent = this.filterApplied && (this.executions as IExecutionsSummary[]).find(ex => ex.id === this.activeExecution.id) === undefined;
@ -66,13 +77,13 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
return this.filter.status !== ''; return this.filter.status !== '';
}, },
workflowDataNotLoaded(): boolean { workflowDataNotLoaded(): boolean {
return this.$store.getters.workflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID && this.$store.getters.workflowName === ''; return this.workflowsStore.workflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID && this.workflowsStore.workflowName === '';
}, },
loadedFinishedExecutionsCount(): number { loadedFinishedExecutionsCount(): number {
return (this.$store.getters['workflows/getAllLoadedFinishedExecutions'] as IExecutionsSummary[]).length; return this.workflowsStore.getAllLoadedFinishedExecutions.length;
}, },
totalFinishedExecutionsCount(): number { totalFinishedExecutionsCount(): number {
return this.$store.getters['workflows/getTotalFinishedExecutionsCount']; return this.workflowsStore.getTotalFinishedExecutionsCount;
}, },
}, },
watch:{ watch:{
@ -81,9 +92,9 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
this.initView(workflowChanged); this.initView(workflowChanged);
if (to.params.executionId) { if (to.params.executionId) {
const execution = this.$store.getters['workflows/getExecutionDataById'](to.params.executionId); const execution = this.workflowsStore.getExecutionDataById(to.params.executionId);
if (execution) { if (execution) {
this.$store.commit('workflows/setActiveWorkflowExecution', execution); this.workflowsStore.activeWorkflowExecution = execution;
} }
} }
}, },
@ -92,7 +103,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
const nextTab = getNodeViewTab(to); const nextTab = getNodeViewTab(to);
// When leaving for a page that's not a workflow view tab, ask to save changes // When leaving for a page that's not a workflow view tab, ask to save changes
if (!nextTab) { if (!nextTab) {
const result = this.$store.getters.getStateIsDirty; const result = this.uiStore.stateIsDirty;
if (result) { if (result) {
const confirmModal = await this.confirmModal( const confirmModal = await this.confirmModal(
this.$locale.baseText('generic.unsavedWork.confirmMessage.message'), this.$locale.baseText('generic.unsavedWork.confirmMessage.message'),
@ -105,11 +116,11 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
if (confirmModal === MODAL_CONFIRMED) { if (confirmModal === MODAL_CONFIRMED) {
const saved = await this.saveCurrentWorkflow({}, false); const saved = await this.saveCurrentWorkflow({}, false);
if (saved) this.$store.dispatch('settings/fetchPromptsData'); if (saved) this.settingsStore.fetchPromptsData();
this.$store.commit('setStateDirty', false); this.uiStore.stateIsDirty = false;
next(); next();
} else if (confirmModal === MODAL_CANCEL) { } else if (confirmModal === MODAL_CANCEL) {
this.$store.commit('setStateDirty', false); this.uiStore.stateIsDirty = false;
next(); next();
} else if (confirmModal === MODAL_CLOSE) { } else if (confirmModal === MODAL_CLOSE) {
next(false); next(false);
@ -122,8 +133,8 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
}, },
async mounted() { async mounted() {
this.loading = true; this.loading = true;
const workflowUpdated = this.$route.params.name !== this.$store.getters.workflowId; const workflowUpdated = this.$route.params.name !== this.workflowsStore.workflowId;
const onNewWorkflow = this.$route.params.name === 'new' && this.$store.getters.workflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID; const onNewWorkflow = this.$route.params.name === 'new' && this.workflowsStore.workflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID;
const shouldUpdate = workflowUpdated && !onNewWorkflow; const shouldUpdate = workflowUpdated && !onNewWorkflow;
await this.initView(shouldUpdate); await this.initView(shouldUpdate);
if (!shouldUpdate) { if (!shouldUpdate) {
@ -134,11 +145,11 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
methods: { methods: {
async initView(loadWorkflow: boolean) : Promise<void> { async initView(loadWorkflow: boolean) : Promise<void> {
if (loadWorkflow) { if (loadWorkflow) {
if (this.$store.getters['nodeTypes/allNodeTypes'].length === 0) { if (this.nodeTypesStore.allNodeTypes.length === 0) {
await this.$store.dispatch('nodeTypes/getNodeTypes'); await this.nodeTypesStore.getNodeTypes();
} }
await this.openWorkflow(this.$route.params.name); await this.openWorkflow(this.$route.params.name);
this.$store.commit('ui/setNodeViewInitialized', false); this.uiStore.nodeViewInitialized = false;
this.setExecutions(); this.setExecutions();
if (this.activeExecution) { if (this.activeExecution) {
this.$router.push({ this.$router.push({
@ -193,7 +204,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
currentExecutions.push(newExecution); currentExecutions.push(newExecution);
} }
} }
this.$store.commit('workflows/setCurrentWorkflowExecutions', currentExecutions); this.workflowsStore.currentWorkflowExecutions = currentExecutions;
this.loadingMore = false; this.loadingMore = false;
}, },
async onDeleteCurrentExecution(): Promise<void> { async onDeleteCurrentExecution(): Promise<void> {
@ -203,13 +214,13 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
await this.setExecutions(); await this.setExecutions();
// Select first execution in the list after deleting the current one // Select first execution in the list after deleting the current one
if (this.executions.length > 0) { if (this.executions.length > 0) {
this.$store.commit('workflows/setActiveWorkflowExecution', this.executions[0]); this.workflowsStore.activeWorkflowExecution = this.executions[0];
this.$router.push({ this.$router.push({
name: VIEWS.EXECUTION_PREVIEW, name: VIEWS.EXECUTION_PREVIEW,
params: { name: this.currentWorkflow, executionId: this.executions[0].id }, params: { name: this.currentWorkflow, executionId: this.executions[0].id },
}).catch(()=>{});; }).catch(()=>{});;
} else { // If there are no executions left, show empty state and clear active execution from the store } else { // If there are no executions left, show empty state and clear active execution from the store
this.$store.commit('workflows/setActiveWorkflowExecution', null); this.workflowsStore.activeWorkflowExecution = null;
this.$router.push({ name: VIEWS.EXECUTION_HOME, params: { name: this.currentWorkflow } }); this.$router.push({ name: VIEWS.EXECUTION_HOME, params: { name: this.currentWorkflow } });
} }
} catch (error) { } catch (error) {
@ -233,7 +244,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
}, },
async setExecutions(): Promise<void> { async setExecutions(): Promise<void> {
const workflowExecutions = await this.loadExecutions(); const workflowExecutions = await this.loadExecutions();
this.$store.commit('workflows/setCurrentWorkflowExecutions', workflowExecutions); this.workflowsStore.currentWorkflowExecutions = workflowExecutions;
this.setActiveExecution(); this.setActiveExecution();
}, },
async loadAutoRefresh(): Promise<void> { async loadAutoRefresh(): Promise<void> {
@ -284,12 +295,12 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
} }
existingExecutions = existingExecutions.filter(execution => !gaps.includes(parseInt(execution.id, 10)) && lastId >= parseInt(execution.id, 10)); existingExecutions = existingExecutions.filter(execution => !gaps.includes(parseInt(execution.id, 10)) && lastId >= parseInt(execution.id, 10));
this.$store.commit('workflows/setCurrentWorkflowExecutions', existingExecutions); this.workflowsStore.currentWorkflowExecutions = existingExecutions;
if (updatedActiveExecution !== null) { if (updatedActiveExecution !== null) {
this.$store.commit('workflows/setActiveWorkflowExecution', updatedActiveExecution); this.workflowsStore.activeWorkflowExecution = updatedActiveExecution;
} else { } else {
const activeNotInTheList = existingExecutions.find(ex => ex.id === this.activeExecution.id) === undefined; const activeNotInTheList = existingExecutions.find(ex => ex.id === this.activeExecution.id) === undefined;
if (activeNotInTheList) { if (activeNotInTheList && this.executions.length > 0) {
this.$router.push({ this.$router.push({
name: VIEWS.EXECUTION_PREVIEW, name: VIEWS.EXECUTION_PREVIEW,
params: { name: this.currentWorkflow, executionId: this.executions[0].id }, params: { name: this.currentWorkflow, executionId: this.executions[0].id },
@ -303,7 +314,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
} }
try { try {
const executions: IExecutionsSummary[] = const executions: IExecutionsSummary[] =
await this.$store.dispatch('workflows/loadCurrentWorkflowExecutions', this.filter); await this.workflowsStore.loadCurrentWorkflowExecutions(this.filter);
return executions; return executions;
} catch (error) { } catch (error) {
this.$showError( this.$showError(
@ -316,14 +327,14 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
setActiveExecution(): void { setActiveExecution(): void {
const activeExecutionId = this.$route.params.executionId; const activeExecutionId = this.$route.params.executionId;
if (activeExecutionId) { if (activeExecutionId) {
const execution = this.$store.getters['workflows/getExecutionDataById'](activeExecutionId); const execution = this.workflowsStore.getExecutionDataById(activeExecutionId);
if (execution) { if (execution) {
this.$store.commit('workflows/setActiveWorkflowExecution', execution); this.workflowsStore.activeWorkflowExecution = execution;
} }
} }
// If there is no execution in the route, select the first one // If there is no execution in the route, select the first one
if (this.$store.getters['workflows/getActiveWorkflowExecution'] === null && this.executions.length > 0) { if (this.workflowsStore.activeWorkflowExecution === null && this.executions.length > 0) {
this.$store.commit('workflows/setActiveWorkflowExecution', this.executions[0]); this.workflowsStore.activeWorkflowExecution = this.executions[0];
this.$router.push({ this.$router.push({
name: VIEWS.EXECUTION_PREVIEW, name: VIEWS.EXECUTION_PREVIEW,
params: { name: this.currentWorkflow, executionId: this.executions[0].id }, params: { name: this.currentWorkflow, executionId: this.executions[0].id },
@ -353,19 +364,20 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
} }
await this.addNodes(data.nodes, data.connections); await this.addNodes(data.nodes, data.connections);
this.$store.commit('setActive', data.active || false); this.workflowsStore.setActive(data.active || false);
this.$store.commit('setWorkflowId', workflowId); this.workflowsStore.setWorkflowId(workflowId);
this.$store.commit('setWorkflowName', { newName: data.name, setStateDirty: false }); this.workflowsStore.setWorkflowName({ newName: data.name, setStateDirty: false });
this.$store.commit('setWorkflowSettings', data.settings || {}); this.workflowsStore.setWorkflowSettings(data.settings || {});
this.$store.commit('setWorkflowPinData', data.pinData || {}); this.workflowsStore.setWorkflowPinData(data.pinData || {});
const tags = (data.tags || []) as ITag[]; const tags = (data.tags || []) as ITag[];
this.$store.commit('tags/upsertTags', tags);
const tagIds = tags.map((tag) => tag.id); const tagIds = tags.map((tag) => tag.id);
this.$store.commit('setWorkflowTagIds', tagIds || []); this.workflowsStore.setWorkflowTagIds(tagIds || []);
this.$store.commit('setWorkflowHash', data.hash); this.workflowsStore.setWorkflowHash(data.hash);
this.$store.commit('tags/upsertTags', tags);
this.$externalHooks().run('workflow.open', { workflowId, workflowName: data.name }); this.$externalHooks().run('workflow.open', { workflowId, workflowName: data.name });
this.$store.commit('setStateDirty', false); this.uiStore.stateIsDirty = false;
}, },
async addNodes(nodes: INodeUi[], connections?: IConnections) { async addNodes(nodes: INodeUi[], connections?: IConnections) {
if (!nodes || !nodes.length) { if (!nodes || !nodes.length) {
@ -380,7 +392,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
node.id = uuid(); node.id = uuid();
} }
nodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion) as INodeTypeDescription | null; nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
// Make sure that some properties always exist // Make sure that some properties always exist
if (!node.hasOwnProperty('disabled')) { if (!node.hasOwnProperty('disabled')) {
@ -409,7 +421,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
} }
} }
this.$store.commit('addNode', node); this.workflowsStore.addNode(node);
}); });
// Load the connections // Load the connections
@ -438,7 +450,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
}, },
] as [IConnection, IConnection]; ] as [IConnection, IConnection];
this.$store.commit('addConnection', { connection: connectionData, setStateDirty: false }); this.workflowsStore.addConnection({ connection: connectionData, setStateDirty: false });
}); });
} }
} }
@ -446,7 +458,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
} }
}, },
async loadNodesProperties(nodeInfos: INodeTypeNameVersion[]): Promise<void> { async loadNodesProperties(nodeInfos: INodeTypeNameVersion[]): Promise<void> {
const allNodes: INodeTypeDescription[] = this.$store.getters['nodeTypes/allNodeTypes']; const allNodes: INodeTypeDescription[] = this.nodeTypesStore.allNodeTypes;
const nodesToBeFetched: INodeTypeNameVersion[] = []; const nodesToBeFetched: INodeTypeNameVersion[] = [];
allNodes.forEach(node => { allNodes.forEach(node => {
@ -463,12 +475,12 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
if (nodesToBeFetched.length > 0) { if (nodesToBeFetched.length > 0) {
// Only call API if node information is actually missing // Only call API if node information is actually missing
await this.$store.dispatch('nodeTypes/getNodesInformation', nodesToBeFetched); await this.nodeTypesStore.getNodesInformation(nodesToBeFetched);
} }
}, },
async loadActiveWorkflows(): Promise<void> { async loadActiveWorkflows(): Promise<void> {
const activeWorkflows = await this.restApi().getActiveWorkflows(); const activeWorkflows = await this.restApi().getActiveWorkflows();
this.$store.commit('setActiveWorkflows', activeWorkflows); this.workflowsStore.activeWorkflows = activeWorkflows;
}, },
async onRetryExecution(payload: { execution: IExecutionsSummary, command: string }) { async onRetryExecution(payload: { execution: IExecutionsSummary, command: string }) {
const loadWorkflow = payload.command === 'current-workflow'; const loadWorkflow = payload.command === 'current-workflow';
@ -482,7 +494,7 @@ export default mixins(restApi, showMessage, executionHelpers, debounceHelper, wo
this.loadAutoRefresh(); this.loadAutoRefresh();
this.$telemetry.track('User clicked retry execution button', { this.$telemetry.track('User clicked retry execution button', {
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
execution_id: payload.execution.id, execution_id: payload.execution.id,
retry_type: loadWorkflow ? 'current' : 'original', retry_type: loadWorkflow ? 'current' : 'original',
}); });

View file

@ -54,6 +54,9 @@ import { genericHelpers } from '@/components/mixins/genericHelpers';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { hasExpressionMapping } from './helpers'; import { hasExpressionMapping } from './helpers';
import { debounceHelper } from './mixins/debounce'; import { debounceHelper } from './mixins/debounce';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
export default mixins( export default mixins(
externalHooks, externalHooks,
@ -78,6 +81,12 @@ export default mixins(
latestValue: '', latestValue: '',
}; };
}, },
computed: {
...mapStores(
useNDVStore,
useWorkflowsStore,
),
},
methods: { methods: {
valueChanged (value: string, forceUpdate = false) { valueChanged (value: string, forceUpdate = false) {
this.latestValue = value; this.latestValue = value;
@ -120,11 +129,11 @@ export default mixins(
node_name: string; node_name: string;
} = { } = {
event_version: '2', event_version: '2',
node_type_dest: this.$store.getters['ndv/activeNode'].type, node_type_dest: this.ndvStore.activeNode? this.ndvStore.activeNode.type : '',
parameter_name_dest: this.parameter.displayName, parameter_name_dest: this.parameter.displayName,
is_immediate_input: false, is_immediate_input: false,
variable_expression: eventData.variable, variable_expression: eventData.variable,
node_name: this.$store.getters['ndv/activeNode'].name, node_name: this.ndvStore.activeNode? this.ndvStore.activeNode.name : '',
}; };
if (eventData.variable) { if (eventData.variable) {
@ -142,9 +151,9 @@ export default mixins(
if (splitVar[0].startsWith('$node')) { if (splitVar[0].startsWith('$node')) {
const sourceNodeName = splitVar[0].split('"')[1]; const sourceNodeName = splitVar[0].split('"')[1];
trackProperties.node_type_source = this.$store.getters.getNodeByName(sourceNodeName).type; trackProperties.node_type_source = this.workflowsStore.getNodeByName(sourceNodeName)?.type;
const nodeConnections: Array<Array<{ node: string }>> = this.$store.getters.outgoingConnectionsByNodeName(sourceNodeName).main; const nodeConnections: Array<Array<{ node: string }>> = this.workflowsStore.outgoingConnectionsByNodeName(sourceNodeName).main;
trackProperties.is_immediate_input = (nodeConnections && nodeConnections[0] && !!nodeConnections[0].find(({ node }) => node === this.$store.getters['ndv/activeNode'].name)) ? true : false; trackProperties.is_immediate_input = (nodeConnections && nodeConnections[0] && !!nodeConnections[0].find(({ node }) => node === this.ndvStore.activeNode?.name || '')) ? true : false;
if (splitVar[1].startsWith('parameter')) { if (splitVar[1].startsWith('parameter')) {
trackProperties.parameter_name_source = splitVar[1].split('"')[1]; trackProperties.parameter_name_source = splitVar[1].split('"')[1];
@ -173,9 +182,9 @@ export default mixins(
if (!newValue) { if (!newValue) {
const telemetryPayload = { const telemetryPayload = {
empty_expression: (this.value === '=') || (this.value === '={{}}') || !this.value, empty_expression: (this.value === '=') || (this.value === '={{}}') || !this.value,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
source: this.eventSource, source: this.eventSource,
session_id: this.$store.getters['ndv/ndvSessionId'], session_id: this.ndvStore.sessionId,
has_parameter: this.value.includes('$parameter'), has_parameter: this.value.includes('$parameter'),
has_mapping: hasExpressionMapping(this.value), has_mapping: hasExpressionMapping(this.value),
}; };

View file

@ -28,6 +28,11 @@
<script lang="ts"> <script lang="ts">
import {IFakeDoor} from '@/Interface'; import {IFakeDoor} from '@/Interface';
import { useRootStore } from '@/stores/n8nRootStore';
import { useSettingsStore } from '@/stores/settings';
import { useUIStore } from '@/stores/ui';
import { useUsersStore } from '@/stores/users';
import { mapStores } from 'pinia';
import Vue from 'vue'; import Vue from 'vue';
export default Vue.extend({ export default Vue.extend({
@ -43,23 +48,28 @@ export default Vue.extend({
}, },
}, },
computed: { computed: {
...mapStores(
useRootStore,
useSettingsStore,
useUIStore,
useUsersStore,
),
userId(): string { userId(): string {
return this.$store.getters['users/currentUserId']; return this.usersStore.currentUserId || '';
},
versionCli(): string {
return this.$store.getters['settings/versionCli'];
}, },
instanceId(): string { instanceId(): string {
return this.$store.getters.instanceId; return this.rootStore.instanceId;
}, },
featureInfo(): IFakeDoor { featureInfo(): IFakeDoor | undefined {
return this.$store.getters['ui/getFakeDoorById'](this.featureId); return this.uiStore.getFakeDoorById(this.featureId);
}, },
}, },
methods: { methods: {
openLinkPage() { openLinkPage() {
window.open(`${this.featureInfo.linkURL}&u=${this.instanceId}#${this.userId}&v=${this.versionCli}`, '_blank'); if (this.featureInfo) {
this.$telemetry.track('user clicked feature waiting list button', {feature: this.featureId}); window.open(`${this.featureInfo.linkURL}&u=${this.instanceId}#${this.userId}&v=${this.rootStore.versionCli}`, '_blank');
this.$telemetry.track('user clicked feature waiting list button', {feature: this.featureId});
}
}, },
}, },
}); });

View file

@ -10,7 +10,7 @@ import { VIEWS } from '@/constants';
import Vue from 'vue'; import Vue from 'vue';
export default Vue.extend({ export default Vue.extend({
name: 'TemplateList', name: 'GoBackButton',
data() { data() {
return { return {
routeHasHistory: false, routeHasHistory: false,

View file

@ -42,6 +42,8 @@ import Vue from 'vue';
import { ITemplatesNode } from '@/Interface'; import { ITemplatesNode } from '@/Interface';
import { INodeTypeDescription } from 'n8n-workflow'; import { INodeTypeDescription } from 'n8n-workflow';
import { mapStores } from 'pinia';
import { useRootStore } from '@/stores/n8nRootStore';
interface NodeIconData { interface NodeIconData {
type: string; type: string;
@ -72,6 +74,9 @@ export default Vue.extend({
}, },
}, },
computed: { computed: {
...mapStores(
useRootStore,
),
fontStyleData(): object { fontStyleData(): object {
return { return {
'max-width': this.size + 'px', 'max-width': this.size + 'px',
@ -115,7 +120,7 @@ export default Vue.extend({
return (nodeType as ITemplatesNode).iconData; return (nodeType as ITemplatesNode).iconData;
} }
const restUrl = this.$store.getters.getRestUrl; const restUrl = this.rootStore.getRestUrl;
if (nodeType.icon) { if (nodeType.icon) {
const [type, path] = nodeType.icon.split(':'); const [type, path] = nodeType.icon.split(':');

View file

@ -50,6 +50,9 @@ import {
import { showMessage } from './mixins/showMessage'; import { showMessage } from './mixins/showMessage';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { INodeUi } from '@/Interface'; import { INodeUi } from '@/Interface';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useNDVStore } from '@/stores/ndv';
export default mixins(showMessage).extend({ export default mixins(showMessage).extend({
name: 'ImportCurlModal', name: 'ImportCurlModal',
@ -64,8 +67,12 @@ export default mixins(showMessage).extend({
}; };
}, },
computed: { computed: {
node(): INodeUi { ...mapStores(
return this.$store.getters['ndv/activeNode']; useNDVStore,
useUIStore,
),
node(): INodeUi | null {
return this.ndvStore.activeNode;
}, },
}, },
methods: { methods: {
@ -80,8 +87,7 @@ export default mixins(showMessage).extend({
if (curlCommand === '') return; if (curlCommand === '') return;
try { try {
const parameters = await this.$store.dispatch('ui/getCurlToJson', curlCommand); const parameters = await this.uiStore.getCurlToJson(curlCommand);
const url = parameters['parameters.url']; const url = parameters['parameters.url'];
const invalidProtocol = CURL_IMPORT_NOT_SUPPORTED_PROTOCOLS.find((p) => const invalidProtocol = CURL_IMPORT_NOT_SUPPORTED_PROTOCOLS.find((p) =>
@ -89,7 +95,8 @@ export default mixins(showMessage).extend({
); );
if (!invalidProtocol) { if (!invalidProtocol) {
this.$store.dispatch('ui/setHttpNodeParameters', { this.uiStore.setHttpNodeParameters({
name: IMPORT_CURL_MODAL_KEY,
parameters: JSON.stringify(parameters), parameters: JSON.stringify(parameters),
}); });
@ -114,7 +121,7 @@ export default mixins(showMessage).extend({
this.sendTelemetry({ success: false, invalidProtocol: false }); this.sendTelemetry({ success: false, invalidProtocol: false });
} finally { } finally {
this.$store.dispatch('ui/setCurlCommand', { command: this.curlCommand }); this.uiStore.setCurlCommand({ name: IMPORT_CURL_MODAL_KEY, command: this.curlCommand });
} }
}, },
showProtocolErrorWithSupportedNode(protocol: string, node: string): void { showProtocolErrorWithSupportedNode(protocol: string, node: string): void {
@ -168,7 +175,7 @@ export default mixins(showMessage).extend({
}, },
}, },
mounted() { mounted() {
this.curlCommand = this.$store.getters['ui/getCurlCommand']; this.curlCommand = this.uiStore.getCurlCommand || '';
setTimeout(() => { setTimeout(() => {
(this.$refs.input as HTMLTextAreaElement).focus(); (this.$refs.input as HTMLTextAreaElement).focus();
}); });

View file

@ -12,6 +12,8 @@
<script lang="ts"> <script lang="ts">
import { IMPORT_CURL_MODAL_KEY } from '@/constants'; import { IMPORT_CURL_MODAL_KEY } from '@/constants';
import { useUIStore } from '@/stores/ui';
import { mapStores } from 'pinia';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { showMessage } from './mixins/showMessage'; import { showMessage } from './mixins/showMessage';
@ -23,9 +25,12 @@ export default mixins(showMessage).extend({
default: false, default: false,
}, },
}, },
computed: {
...mapStores(useUIStore),
},
methods: { methods: {
onImportCurlClicked() { onImportCurlClicked() {
this.$store.dispatch('ui/openModal', IMPORT_CURL_MODAL_KEY); this.uiStore.openModal(IMPORT_CURL_MODAL_KEY);
}, },
}, },
}); });

View file

@ -76,6 +76,10 @@ import mixins from 'vue-typed-mixins';
import NodeExecuteButton from './NodeExecuteButton.vue'; import NodeExecuteButton from './NodeExecuteButton.vue';
import WireMeUp from './WireMeUp.vue'; import WireMeUp from './WireMeUp.vue';
import { CRON_NODE_TYPE, INTERVAL_NODE_TYPE, LOCAL_STORAGE_MAPPING_FLAG, MANUAL_TRIGGER_NODE_TYPE, SCHEDULE_TRIGGER_NODE_TYPE, START_NODE_TYPE } from '@/constants'; import { CRON_NODE_TYPE, INTERVAL_NODE_TYPE, LOCAL_STORAGE_MAPPING_FLAG, MANUAL_TRIGGER_NODE_TYPE, SCHEDULE_TRIGGER_NODE_TYPE, START_NODE_TYPE } from '@/constants';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins( export default mixins(
workflowHelpers, workflowHelpers,
@ -111,8 +115,13 @@ export default mixins(
}; };
}, },
computed: { computed: {
...mapStores(
useNodeTypesStore,
useNDVStore,
useWorkflowsStore,
),
focusedMappableInput(): string { focusedMappableInput(): string {
return this.$store.getters['ndv/focusedMappableInput']; return this.ndvStore.focusedMappableInput;
}, },
isUserOnboarded(): boolean { isUserOnboarded(): boolean {
return window.localStorage.getItem(LOCAL_STORAGE_MAPPING_FLAG) === 'true'; return window.localStorage.getItem(LOCAL_STORAGE_MAPPING_FLAG) === 'true';
@ -129,8 +138,8 @@ export default mixins(
if (!this.workflowRunning) { if (!this.workflowRunning) {
return false; return false;
} }
const triggeredNode = this.$store.getters.executedNode; const triggeredNode = this.workflowsStore.executedNode;
const executingNode = this.$store.getters.executingNode; const executingNode = this.workflowsStore.executingNode;
if (this.activeNode && triggeredNode === this.activeNode.name && this.activeNode.name !== executingNode) { if (this.activeNode && triggeredNode === this.activeNode.name && this.activeNode.name !== executingNode) {
return true; return true;
} }
@ -141,16 +150,16 @@ export default mixins(
return false; return false;
}, },
workflowRunning (): boolean { workflowRunning (): boolean {
return this.$store.getters.isActionActive('workflowRunning'); return this.uiStore.isActionActive('workflowRunning');
}, },
currentWorkflow(): Workflow { currentWorkflow(): Workflow {
return this.workflow as Workflow; return this.workflow as Workflow;
}, },
activeNode (): INodeUi | null { activeNode (): INodeUi | null {
return this.$store.getters['ndv/activeNode']; return this.ndvStore.activeNode;
}, },
currentNode (): INodeUi | null { currentNode (): INodeUi | null {
return this.$store.getters.getNodeByName(this.currentNodeName); return this.workflowsStore.getNodeByName(this.currentNodeName);
}, },
connectedCurrentNodeOutputs(): number[] | undefined { connectedCurrentNodeOutputs(): number[] | undefined {
const search = this.parentNodes.find(({name}) => name === this.currentNodeName); const search = this.parentNodes.find(({name}) => name === this.currentNodeName);
@ -174,7 +183,7 @@ export default mixins(
activeNodeType () : INodeTypeDescription | null { activeNodeType () : INodeTypeDescription | null {
if (!this.activeNode) return null; if (!this.activeNode) return null;
return this.$store.getters['nodeTypes/getNodeType'](this.activeNode.type, this.activeNode.typeVersion); return this.nodeTypesStore.getNodeType(this.activeNode.type, this.activeNode.typeVersion);
}, },
isMultiInputNode (): boolean { isMultiInputNode (): boolean {
return this.activeNodeType !== null && this.activeNodeType.inputs.length > 1; return this.activeNodeType !== null && this.activeNodeType.inputs.length > 1;
@ -214,7 +223,7 @@ export default mixins(
if (this.activeNode) { if (this.activeNode) {
this.$telemetry.track('User clicked ndv button', { this.$telemetry.track('User clicked ndv button', {
node_type: this.activeNode.type, node_type: this.activeNode.type,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId, session_id: this.sessionId,
pane: 'input', pane: 'input',
type: 'executePrevious', type: 'executePrevious',
@ -238,7 +247,7 @@ export default mixins(
if (this.activeNode) { if (this.activeNode) {
this.$telemetry.track('User clicked ndv link', { this.$telemetry.track('User clicked ndv link', {
node_type: this.activeNode.type, node_type: this.activeNode.type,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId, session_id: this.sessionId,
pane: 'input', pane: 'input',
type: 'not-connected-help', type: 'not-connected-help',

View file

@ -32,6 +32,8 @@ import Vue from "vue";
import { IFormInputs, IInviteResponse } from "@/Interface"; import { IFormInputs, IInviteResponse } from "@/Interface";
import { VALID_EMAIL_REGEX, INVITE_USER_MODAL_KEY } from "@/constants"; import { VALID_EMAIL_REGEX, INVITE_USER_MODAL_KEY } from "@/constants";
import { ROLE } from "@/modules/userHelpers"; import { ROLE } from "@/modules/userHelpers";
import { mapStores } from "pinia";
import { useUsersStore } from "@/stores/users";
const NAME_EMAIL_FORMAT_REGEX = /^.* <(.*)>$/; const NAME_EMAIL_FORMAT_REGEX = /^.* <(.*)>$/;
@ -101,6 +103,7 @@ export default mixins(showMessage).extend({
]; ];
}, },
computed: { computed: {
...mapStores(useUsersStore),
emailsCount(): number { emailsCount(): number {
return this.emails.split(',').filter((email: string) => !!email.trim()).length; return this.emails.split(',').filter((email: string) => !!email.trim()).length;
}, },
@ -156,7 +159,7 @@ export default mixins(showMessage).extend({
throw new Error(this.$locale.baseText('settings.users.noUsersToInvite')); throw new Error(this.$locale.baseText('settings.users.noUsersToInvite'));
} }
const invited: IInviteResponse[] = await this.$store.dispatch('users/inviteUsers', emails); const invited: IInviteResponse[] = await this.usersStore.inviteUsers(emails);
const invitedEmails = invited.reduce((accu, {user, error}) => { const invitedEmails = invited.reduce((accu, {user, error}) => {
if (error) { if (error) {
accu.error.push(user.email); accu.error.push(user.email);

View file

@ -7,12 +7,17 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { useRootStore } from '@/stores/n8nRootStore';
import { mapStores } from 'pinia';
import Vue from 'vue'; import Vue from 'vue';
export default Vue.extend({ export default Vue.extend({
computed: { computed: {
...mapStores(
useRootStore,
),
basePath(): string { basePath(): string {
return this.$store.getters.getBaseUrl; return this.rootStore.baseUrl;
}, },
}, },
}); });

View file

@ -43,12 +43,14 @@
<script lang="ts"> <script lang="ts">
import mixins from "vue-typed-mixins"; import mixins from "vue-typed-mixins";
import { IExecutionResponse } from "../../../Interface"; import { IExecutionResponse, IExecutionsSummary } from "../../../Interface";
import { titleChange } from "@/components/mixins/titleChange"; import { titleChange } from "@/components/mixins/titleChange";
import ShortenName from "@/components/ShortenName.vue"; import ShortenName from "@/components/ShortenName.vue";
import ReadOnly from "@/components/MainHeader/ExecutionDetails/ReadOnly.vue"; import ReadOnly from "@/components/MainHeader/ExecutionDetails/ReadOnly.vue";
import { mapStores } from "pinia";
import { useWorkflowsStore } from "@/stores/workflows";
export default mixins(titleChange).extend({ export default mixins(titleChange).extend({
name: "ExecutionDetails", name: "ExecutionDetails",
@ -57,24 +59,27 @@ export default mixins(titleChange).extend({
ReadOnly, ReadOnly,
}, },
computed: { computed: {
...mapStores(
useWorkflowsStore,
),
executionId(): string | undefined { executionId(): string | undefined {
return this.$route.params.id; return this.$route.params.id;
}, },
executionFinished(): boolean { executionFinished(): boolean {
const fullExecution = this.$store.getters.getWorkflowExecution; const fullExecution = this.workflowsStore.getWorkflowExecution;
return !!fullExecution && fullExecution.finished; return !!fullExecution && fullExecution.finished;
}, },
executionWaiting(): boolean { executionWaiting(): boolean {
const fullExecution = this.$store.getters.getWorkflowExecution; const fullExecution = this.workflowsStore.getWorkflowExecution as IExecutionsSummary;
return !!fullExecution && !!fullExecution.waitTill; return !!fullExecution && !!fullExecution.waitTill;
}, },
workflowExecution(): IExecutionResponse | null { workflowExecution(): IExecutionResponse | null {
return this.$store.getters.getWorkflowExecution; return this.workflowsStore.getWorkflowExecution;
}, },
workflowName(): string { workflowName(): string {
return this.$store.getters.workflowName; return this.workflowsStore.workflowName;
}, },
}, },
methods: { methods: {

View file

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<div :class="{'main-header': true, expanded: !sidebarMenuCollapsed}"> <div :class="{'main-header': true, expanded: !this.uiStore.sidebarMenuCollapsed}">
<div v-show="!hideMenuBar" class="top-menu"> <div v-show="!hideMenuBar" class="top-menu">
<ExecutionDetails v-if="isExecutionPage" /> <ExecutionDetails v-if="isExecutionPage" />
<WorkflowDetails v-else /> <WorkflowDetails v-else />
@ -12,7 +12,6 @@
<script lang="ts"> <script lang="ts">
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { mapGetters } from 'vuex';
import { pushConnection } from '@/components/mixins/pushConnection'; import { pushConnection } from '@/components/mixins/pushConnection';
import WorkflowDetails from '@/components/MainHeader/WorkflowDetails.vue'; import WorkflowDetails from '@/components/MainHeader/WorkflowDetails.vue';
import ExecutionDetails from '@/components/MainHeader/ExecutionDetails/ExecutionDetails.vue'; import ExecutionDetails from '@/components/MainHeader/ExecutionDetails/ExecutionDetails.vue';
@ -21,6 +20,9 @@ import { MAIN_HEADER_TABS, PLACEHOLDER_EMPTY_WORKFLOW_ID, STICKY_NODE_TYPE, VIEW
import { IExecutionsSummary, INodeUi, ITabBarItem } from '@/Interface'; import { IExecutionsSummary, INodeUi, ITabBarItem } from '@/Interface';
import { workflowHelpers } from '../mixins/workflowHelpers'; import { workflowHelpers } from '../mixins/workflowHelpers';
import { Route } from 'vue-router'; import { Route } from 'vue-router';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useNDVStore } from '@/stores/ndv';
export default mixins( export default mixins(
pushConnection, pushConnection,
@ -36,13 +38,14 @@ export default mixins(
return { return {
activeHeaderTab: MAIN_HEADER_TABS.WORKFLOW, activeHeaderTab: MAIN_HEADER_TABS.WORKFLOW,
workflowToReturnTo: '', workflowToReturnTo: '',
dirtyState: this.$store.getters.getStateIsDirty, dirtyState: false,
}; };
}, },
computed: { computed: {
...mapGetters('ui', [ ...mapStores(
'sidebarMenuCollapsed', useNDVStore,
]), useUIStore,
),
tabBarItems(): ITabBarItem[] { tabBarItems(): ITabBarItem[] {
return [ return [
{ value: MAIN_HEADER_TABS.WORKFLOW, label: this.$locale.baseText('generic.workflow') }, { value: MAIN_HEADER_TABS.WORKFLOW, label: this.$locale.baseText('generic.workflow') },
@ -53,25 +56,26 @@ export default mixins(
return this.$route.name === VIEWS.EXECUTION; return this.$route.name === VIEWS.EXECUTION;
}, },
activeNode (): INodeUi | null { activeNode (): INodeUi | null {
return this.$store.getters['ndv/activeNode']; return this.ndvStore.activeNode;
}, },
hideMenuBar(): boolean { hideMenuBar(): boolean {
return Boolean(this.activeNode && this.activeNode.type !== STICKY_NODE_TYPE); return Boolean(this.activeNode && this.activeNode.type !== STICKY_NODE_TYPE);
}, },
workflowName (): string { workflowName (): string {
return this.$store.getters.workflowName; return this.workflowsStore.workflowName;
}, },
currentWorkflow (): string { currentWorkflow (): string {
return this.$route.params.name || this.$store.getters.workflowId; return this.$route.params.name || this.workflowsStore.workflowId;
}, },
onWorkflowPage(): boolean { onWorkflowPage(): boolean {
return this.$route.meta && (this.$route.meta.nodeView || this.$route.meta.keepWorkflowAlive === true); return this.$route.meta && (this.$route.meta.nodeView || this.$route.meta.keepWorkflowAlive === true);
}, },
activeExecution(): IExecutionsSummary { activeExecution(): IExecutionsSummary {
return this.$store.getters['workflows/getActiveWorkflowExecution']; return this.workflowsStore.activeWorkflowExecution as IExecutionsSummary;
}, },
}, },
mounted() { mounted() {
this.dirtyState = this.uiStore.stateIsDirty;
this.syncTabsWithRoute(this.$route); this.syncTabsWithRoute(this.$route);
// Initialize the push connection // Initialize the push connection
this.pushConnect(); this.pushConnect();
@ -109,13 +113,13 @@ export default mixins(
} else { } else {
if (this.$route.name !== VIEWS.NEW_WORKFLOW) { if (this.$route.name !== VIEWS.NEW_WORKFLOW) {
this.$router.push({ name: VIEWS.NEW_WORKFLOW }); this.$router.push({ name: VIEWS.NEW_WORKFLOW });
this.$store.commit('setStateDirty', this.dirtyState); this.uiStore.stateIsDirty = this.dirtyState;
} }
} }
this.activeHeaderTab = MAIN_HEADER_TABS.WORKFLOW; this.activeHeaderTab = MAIN_HEADER_TABS.WORKFLOW;
break; break;
case MAIN_HEADER_TABS.EXECUTIONS: case MAIN_HEADER_TABS.EXECUTIONS:
this.dirtyState = this.$store.getters.getStateIsDirty; this.dirtyState = this.uiStore.stateIsDirty;
this.workflowToReturnTo = this.currentWorkflow; this.workflowToReturnTo = this.currentWorkflow;
const routeWorkflowId = this.currentWorkflow === PLACEHOLDER_EMPTY_WORKFLOW_ID ? 'new' : this.currentWorkflow; const routeWorkflowId = this.currentWorkflow === PLACEHOLDER_EMPTY_WORKFLOW_ID ? 'new' : this.currentWorkflow;
if (this.activeExecution) { if (this.activeExecution) {

View file

@ -12,6 +12,8 @@
import Vue, { PropType } from 'vue'; import Vue, { PropType } from 'vue';
import { ITabBarItem } from '@/Interface'; import { ITabBarItem } from '@/Interface';
import { MAIN_HEADER_TABS } from '@/constants'; import { MAIN_HEADER_TABS } from '@/constants';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
export default Vue.extend({ export default Vue.extend({
name: 'tab-bar', name: 'tab-bar',
@ -31,8 +33,11 @@ export default Vue.extend({
}, },
}, },
computed: { computed: {
...mapStores(
useUIStore,
),
mainSidebarCollapsed(): boolean { mainSidebarCollapsed(): boolean {
return this.$store.getters['ui/sidebarMenuCollapsed']; return this.uiStore.sidebarMenuCollapsed;
}, },
}, },
methods: { methods: {

View file

@ -23,7 +23,7 @@
</template> </template>
</BreakpointsObserver> </BreakpointsObserver>
<span v-if="areTagsEnabled" class="tags"> <span v-if="settingsStore.areTagsEnabled" class="tags">
<div <div
v-if="isTagsEditEnabled"> v-if="isTagsEditEnabled">
<TagsDropdown <TagsDropdown
@ -105,6 +105,11 @@ import { IWorkflowDataUpdate, IWorkflowToShare } from "@/Interface";
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
import { titleChange } from "../mixins/titleChange"; import { titleChange } from "../mixins/titleChange";
import type { MessageBoxInputData } from 'element-ui/types/message-box'; import type { MessageBoxInputData } from 'element-ui/types/message-box';
import { mapStores } from "pinia";
import { useUIStore } from "@/stores/ui";
import { useSettingsStore } from "@/stores/settings";
import { useWorkflowsStore } from "@/stores/workflows";
import { useRootStore } from "@/stores/n8nRootStore";
const hasChanged = (prev: string[], curr: string[]) => { const hasChanged = (prev: string[], curr: string[]) => {
if (prev.length !== curr.length) { if (prev.length !== curr.length) {
@ -138,24 +143,32 @@ export default mixins(workflowHelpers, titleChange).extend({
}; };
}, },
computed: { computed: {
...mapGetters({ ...mapStores(
isWorkflowActive: "isActive", useRootStore,
workflowName: "workflowName", useSettingsStore,
isDirty: "getStateIsDirty", useUIStore,
currentWorkflowTagIds: "workflowTags", useWorkflowsStore,
}), ),
...mapGetters('settings', ['areTagsEnabled']), isWorkflowActive(): boolean {
return this.workflowsStore.isWorkflowActive;
},
workflowName(): string {
return this.workflowsStore.workflowName;
},
isDirty(): boolean {
return this.uiStore.stateIsDirty;
},
currentWorkflowTagIds(): string[] {
return this.workflowsStore.workflowTags;
},
isNewWorkflow(): boolean { isNewWorkflow(): boolean {
return !this.currentWorkflowId || (this.currentWorkflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID || this.currentWorkflowId === 'new'); return !this.currentWorkflowId || (this.currentWorkflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID || this.currentWorkflowId === 'new');
}, },
isWorkflowSaving(): boolean { isWorkflowSaving(): boolean {
return this.$store.getters.isActionActive("workflowSaving"); return this.uiStore.isActionActive('workflowSaving');
}, },
currentWorkflowId(): string { currentWorkflowId(): string {
return this.$store.getters.workflowId; return this.workflowsStore.workflowId;
},
workflowName (): string {
return this.$store.getters.workflowName;
}, },
onWorkflowPage(): boolean { onWorkflowPage(): boolean {
return this.$route.meta && (this.$route.meta.nodeView || this.$route.meta.keepWorkflowAlive === true); return this.$route.meta && (this.$route.meta.nodeView || this.$route.meta.keepWorkflowAlive === true);
@ -209,7 +222,7 @@ export default mixins(workflowHelpers, titleChange).extend({
currentId = this.$route.params.name; currentId = this.$route.params.name;
} }
const saved = await this.saveCurrentWorkflow({ id: currentId, name: this.workflowName, tags: this.currentWorkflowTagIds }); const saved = await this.saveCurrentWorkflow({ id: currentId, name: this.workflowName, tags: this.currentWorkflowTagIds });
if (saved) this.$store.dispatch('settings/fetchPromptsData'); if (saved) await this.settingsStore.fetchPromptsData();
}, },
onTagsEditEnable() { onTagsEditEnable() {
this.$data.appliedTagIds = this.currentWorkflowTagIds; this.$data.appliedTagIds = this.currentWorkflowTagIds;
@ -314,12 +327,12 @@ export default mixins(workflowHelpers, titleChange).extend({
async onWorkflowMenuSelect(action: string): Promise<void> { async onWorkflowMenuSelect(action: string): Promise<void> {
switch (action) { switch (action) {
case WORKFLOW_MENU_ACTIONS.DUPLICATE: { case WORKFLOW_MENU_ACTIONS.DUPLICATE: {
await this.$store.dispatch('ui/openModalWithData', { this.uiStore.openModalWithData({
name: DUPLICATE_MODAL_KEY, name: DUPLICATE_MODAL_KEY,
data: { data: {
id: this.$store.getters.workflowId, id: this.workflowsStore.workflowId,
name: this.$store.getters.workflowName, name: this.workflowsStore.workflowName,
tags: this.$store.getters.workflowTags, tags: this.workflowsStore.workflowTags,
}, },
}); });
break; break;
@ -334,7 +347,7 @@ export default mixins(workflowHelpers, titleChange).extend({
const exportData: IWorkflowToShare = { const exportData: IWorkflowToShare = {
...data, ...data,
meta: { meta: {
instanceId: this.$store.getters.instanceId, instanceId: this.rootStore.instanceId,
}, },
tags: (tags || []).map(tagId => { tags: (tags || []).map(tagId => {
const {usageCount, ...tag} = this.$store.getters["tags/getTagById"](tagId); const {usageCount, ...tag} = this.$store.getters["tags/getTagById"](tagId);
@ -347,7 +360,7 @@ export default mixins(workflowHelpers, titleChange).extend({
type: 'application/json;charset=utf-8', type: 'application/json;charset=utf-8',
}); });
let workflowName = this.$store.getters.workflowName || 'unsaved_workflow'; let workflowName = this.workflowName || 'unsaved_workflow';
workflowName = workflowName.replace(/[^a-z0-9]/gi, '_'); workflowName = workflowName.replace(/[^a-z0-9]/gi, '_');
this.$telemetry.track('User exported workflow', { workflow_id: workflowData.id }); this.$telemetry.track('User exported workflow', { workflow_id: workflowData.id });
@ -376,7 +389,7 @@ export default mixins(workflowHelpers, titleChange).extend({
break; break;
} }
case WORKFLOW_MENU_ACTIONS.SETTINGS: { case WORKFLOW_MENU_ACTIONS.SETTINGS: {
this.$store.dispatch('ui/openModal', WORKFLOW_SETTINGS_MODAL_KEY); this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
break; break;
} }
case WORKFLOW_MENU_ACTIONS.DELETE: { case WORKFLOW_MENU_ACTIONS.DELETE: {
@ -404,7 +417,7 @@ export default mixins(workflowHelpers, titleChange).extend({
); );
return; return;
} }
this.$store.commit('setStateDirty', false); this.uiStore.stateIsDirty = false;
// Reset tab title since workflow is deleted. // Reset tab title since workflow is deleted.
this.$titleReset(); this.$titleReset();
this.$showMessage({ this.$showMessage({

View file

@ -31,7 +31,7 @@
<!-- This dropdown is only enabled when sidebar is collapsed --> <!-- This dropdown is only enabled when sidebar is collapsed -->
<el-dropdown :disabled="!isCollapsed" placement="right-end" trigger="click" @command="onUserActionToggle"> <el-dropdown :disabled="!isCollapsed" placement="right-end" trigger="click" @command="onUserActionToggle">
<div :class="{[$style.avatar]: true, ['clickable']: isCollapsed }"> <div :class="{[$style.avatar]: true, ['clickable']: isCollapsed }">
<n8n-avatar :firstName="currentUser.firstName" :lastName="currentUser.lastName" size="small" /> <n8n-avatar :firstName="usersStore.currentUser.firstName" :lastName="usersStore.currentUser.lastName" size="small" />
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<el-dropdown-item command="settings">{{ $locale.baseText('settings') }}</el-dropdown-item> <el-dropdown-item command="settings">{{ $locale.baseText('settings') }}</el-dropdown-item>
<el-dropdown-item command="logout">{{ $locale.baseText('auth.signout') }}</el-dropdown-item> <el-dropdown-item command="logout">{{ $locale.baseText('auth.signout') }}</el-dropdown-item>
@ -40,7 +40,7 @@
</el-dropdown> </el-dropdown>
</div> </div>
<div :class="{ ['ml-2xs']: true, [$style.userName]: true, [$style.expanded]: fullyExpanded }"> <div :class="{ ['ml-2xs']: true, [$style.userName]: true, [$style.expanded]: fullyExpanded }">
<n8n-text size="small" :bold="true" color="text-dark">{{currentUser.fullName}}</n8n-text> <n8n-text size="small" :bold="true" color="text-dark">{{usersStore.currentUser.fullName}}</n8n-text>
</div> </div>
<div :class="{ [$style.userActions]: true, [$style.expanded]: fullyExpanded }"> <div :class="{ [$style.userActions]: true, [$style.expanded]: fullyExpanded }">
<n8n-action-dropdown :items="userMenuItems" placement="top-start" @select="onUserActionToggle" /> <n8n-action-dropdown :items="userMenuItems" placement="top-start" @select="onUserActionToggle" />
@ -78,12 +78,17 @@ import {
VERSIONS_MODAL_KEY, VERSIONS_MODAL_KEY,
EXECUTIONS_MODAL_KEY, EXECUTIONS_MODAL_KEY,
VIEWS, VIEWS,
WORKFLOW_OPEN_MODAL_KEY,
PLACEHOLDER_EMPTY_WORKFLOW_ID, PLACEHOLDER_EMPTY_WORKFLOW_ID,
} from '@/constants'; } from '@/constants';
import { userHelpers } from './mixins/userHelpers'; import { userHelpers } from './mixins/userHelpers';
import { debounceHelper } from './mixins/debounce'; import { debounceHelper } from './mixins/debounce';
import Vue from 'vue'; import Vue from 'vue';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useSettingsStore } from '@/stores/settings';
import { useUsersStore } from '@/stores/users';
import { useWorkflowsStore } from '@/stores/workflows';
import { useRootStore } from '@/stores/n8nRootStore';
export default mixins( export default mixins(
genericHelpers, genericHelpers,
@ -105,36 +110,34 @@ export default mixins(
data () { data () {
return { return {
// @ts-ignore // @ts-ignore
basePath: this.$store.getters.getBaseUrl, basePath: '',
fullyExpanded: false, fullyExpanded: false,
}; };
}, },
computed: { computed: {
...mapGetters('ui', { ...mapStores(
isCollapsed: 'sidebarMenuCollapsed', useRootStore,
isNodeView: 'isNodeView', useSettingsStore,
}), useUIStore,
useUsersStore,
useWorkflowsStore,
),
...mapGetters('versions', [ ...mapGetters('versions', [
'hasVersionUpdates', 'hasVersionUpdates',
'nextVersions', 'nextVersions',
]), ]),
...mapGetters('users', [ isCollapsed(): boolean {
'canUserAccessSidebarUserInfo', return this.uiStore.sidebarMenuCollapsed;
'currentUser', },
]),
...mapGetters('settings', [
'isTemplatesEnabled',
'isUserManagementEnabled',
]),
canUserAccessSettings(): boolean { canUserAccessSettings(): boolean {
const accessibleRoute = this.findFirstAccessibleSettingsRoute(); const accessibleRoute = this.findFirstAccessibleSettingsRoute();
return accessibleRoute !== null; return accessibleRoute !== null;
}, },
showUserArea(): boolean { showUserArea(): boolean {
return this.isUserManagementEnabled && this.canUserAccessSidebarUserInfo && this.currentUser; return this.settingsStore.isUserManagementEnabled && this.usersStore.canUserAccessSidebarUserInfo && this.usersStore.currentUser !== null;
}, },
workflowExecution (): IExecutionResponse | null { workflowExecution (): IExecutionResponse | null {
return this.$store.getters.getWorkflowExecution; return this.workflowsStore.getWorkflowExecution;
}, },
userMenuItems (): object[] { userMenuItems (): object[] {
return [ return [
@ -150,7 +153,7 @@ export default mixins(
}, },
mainMenuItems (): IMenuItem[] { mainMenuItems (): IMenuItem[] {
const items: IMenuItem[] = []; const items: IMenuItem[] = [];
const injectedItems = this.$store.getters.sidebarMenuItems as IMenuItem[]; const injectedItems = this.uiStore.sidebarMenuItems;
if (injectedItems && injectedItems.length > 0) { if (injectedItems && injectedItems.length > 0) {
for(const item of injectedItems) { for(const item of injectedItems) {
@ -182,7 +185,7 @@ export default mixins(
icon: 'box-open', icon: 'box-open',
label: this.$locale.baseText('mainSidebar.templates'), label: this.$locale.baseText('mainSidebar.templates'),
position: 'top', position: 'top',
available: this.isTemplatesEnabled, available: this.settingsStore.isTemplatesEnabled,
activateOnRouteNames: [ VIEWS.TEMPLATES ], activateOnRouteNames: [ VIEWS.TEMPLATES ],
}, },
{ {
@ -204,7 +207,7 @@ export default mixins(
icon: 'cog', icon: 'cog',
label: this.$locale.baseText('settings'), label: this.$locale.baseText('settings'),
position: 'bottom', position: 'bottom',
available: this.canUserAccessSettings && this.currentUser, available: this.canUserAccessSettings && this.usersStore.currentUser !== null,
activateOnRouteNames: [ VIEWS.USERS_SETTINGS, VIEWS.API_SETTINGS, VIEWS.PERSONAL_SETTINGS ], activateOnRouteNames: [ VIEWS.USERS_SETTINGS, VIEWS.API_SETTINGS, VIEWS.PERSONAL_SETTINGS ],
}, },
{ {
@ -266,13 +269,14 @@ export default mixins(
}, },
}, },
async mounted() { async mounted() {
this.basePath = this.rootStore.baseUrl;
if (this.$refs.user) { if (this.$refs.user) {
this.$externalHooks().run('mainSidebar.mounted', { userRef: this.$refs.user }); this.$externalHooks().run('mainSidebar.mounted', { userRef: this.$refs.user });
} }
if (window.innerWidth < 900 || this.isNodeView) { if (window.innerWidth < 900 || this.uiStore.isNodeView) {
this.$store.commit('ui/collapseSidebarMenu'); this.uiStore.sidebarMenuCollapsed = true;
} else { } else {
this.$store.commit('ui/expandSidebarMenu'); this.uiStore.sidebarMenuCollapsed = false;
} }
await Vue.nextTick(); await Vue.nextTick();
this.fullyExpanded = !this.isCollapsed; this.fullyExpanded = !this.isCollapsed;
@ -285,7 +289,7 @@ export default mixins(
}, },
methods: { methods: {
trackHelpItemClick (itemType: string) { trackHelpItemClick (itemType: string) {
this.$telemetry.track('User clicked help resource', { type: itemType, workflow_id: this.$store.getters.workflowId }); this.$telemetry.track('User clicked help resource', { type: itemType, workflow_id: this.workflowsStore.workflowId });
}, },
async onUserActionToggle(action: string) { async onUserActionToggle(action: string) {
switch (action) { switch (action) {
@ -301,8 +305,7 @@ export default mixins(
}, },
async onLogout() { async onLogout() {
try { try {
await this.$store.dispatch('users/logout'); await this.usersStore.logout();
const route = this.$router.resolve({ name: VIEWS.SIGNIN }); const route = this.$router.resolve({ name: VIEWS.SIGNIN });
window.open(route.href, '_self'); window.open(route.href, '_self');
} catch (e) { } catch (e) {
@ -310,7 +313,7 @@ export default mixins(
} }
}, },
toggleCollapse () { toggleCollapse () {
this.$store.commit('ui/toggleSidebarMenuCollapse'); this.uiStore.toggleSidebarMenuCollapse();
// When expanding, delay showing some element to ensure smooth animation // When expanding, delay showing some element to ensure smooth animation
if (!this.isCollapsed) { if (!this.isCollapsed) {
setTimeout(() => { setTimeout(() => {
@ -321,7 +324,7 @@ export default mixins(
} }
}, },
openUpdatesPanel() { openUpdatesPanel() {
this.$store.dispatch('ui/openModal', VERSIONS_MODAL_KEY); this.uiStore.openModal(VERSIONS_MODAL_KEY);
}, },
async handleSelect (key: string) { async handleSelect (key: string) {
switch (key) { switch (key) {
@ -344,7 +347,7 @@ export default mixins(
break; break;
} }
case 'executions': { case 'executions': {
this.$store.dispatch('ui/openModal', EXECUTIONS_MODAL_KEY); this.uiStore.openModal(EXECUTIONS_MODAL_KEY);
break; break;
} }
case 'settings': { case 'settings': {
@ -359,7 +362,7 @@ export default mixins(
} }
case 'about': { case 'about': {
this.trackHelpItemClick('about'); this.trackHelpItemClick('about');
this.$store.dispatch('ui/openModal', ABOUT_MODAL_KEY); this.uiStore.openModal(ABOUT_MODAL_KEY);
break; break;
} }
case 'quickstart': case 'quickstart':
@ -373,7 +376,7 @@ export default mixins(
} }
}, },
async createNewWorkflow (): Promise<void> { async createNewWorkflow (): Promise<void> {
const result = this.$store.getters.getStateIsDirty; const result = this.uiStore.stateIsDirty;
if(result) { if(result) {
const confirmModal = await this.confirmModal( const confirmModal = await this.confirmModal(
this.$locale.baseText('generic.unsavedWork.confirmMessage.message'), this.$locale.baseText('generic.unsavedWork.confirmMessage.message'),
@ -385,7 +388,7 @@ export default mixins(
); );
if (confirmModal === MODAL_CONFIRMED) { if (confirmModal === MODAL_CONFIRMED) {
const saved = await this.saveCurrentWorkflow({}, false); const saved = await this.saveCurrentWorkflow({}, false);
if (saved) this.$store.dispatch('settings/fetchPromptsData'); if (saved) await this.settingsStore.fetchPromptsData();
if (this.$router.currentRoute.name === VIEWS.NEW_WORKFLOW) { if (this.$router.currentRoute.name === VIEWS.NEW_WORKFLOW) {
this.$root.$emit('newWorkflow'); this.$root.$emit('newWorkflow');
} else { } else {
@ -396,11 +399,11 @@ export default mixins(
type: 'success', type: 'success',
}); });
} else if (confirmModal === MODAL_CANCEL) { } else if (confirmModal === MODAL_CANCEL) {
this.$store.commit('setStateDirty', false); this.uiStore.stateIsDirty = false;
if (this.$router.currentRoute.name === VIEWS.NEW_WORKFLOW) { if (this.$router.currentRoute.name === VIEWS.NEW_WORKFLOW) {
this.$root.$emit('newWorkflow'); this.$root.$emit('newWorkflow');
} else { } else {
this.$store.commit('setWorkflowId', PLACEHOLDER_EMPTY_WORKFLOW_ID); this.workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID);
this.$router.push({ name: VIEWS.NEW_WORKFLOW }); this.$router.push({ name: VIEWS.NEW_WORKFLOW });
} }
this.$showMessage({ this.$showMessage({
@ -412,7 +415,7 @@ export default mixins(
} }
} else { } else {
if (this.$router.currentRoute.name !== VIEWS.NEW_WORKFLOW) { if (this.$router.currentRoute.name !== VIEWS.NEW_WORKFLOW) {
this.$store.commit('setWorkflowId', PLACEHOLDER_EMPTY_WORKFLOW_ID); this.workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID);
this.$router.push({ name: VIEWS.NEW_WORKFLOW }); this.$router.push({ name: VIEWS.NEW_WORKFLOW });
} }
this.$showMessage({ this.$showMessage({
@ -447,7 +450,7 @@ export default mixins(
}, },
checkWidthAndAdjustSidebar (width: number) { checkWidthAndAdjustSidebar (width: number) {
if (width < 900) { if (width < 900) {
this.$store.commit('ui/collapseSidebarMenu'); this.uiStore.sidebarMenuCollapsed = true;
Vue.nextTick(() => { Vue.nextTick(() => {
this.fullyExpanded = !this.isCollapsed; this.fullyExpanded = !this.isCollapsed;
}); });

View file

@ -1,6 +1,6 @@
<template> <template>
<el-dialog <el-dialog
:visible="visible" :visible="uiStore.isModalOpen(this.$props.name)"
:before-close="closeDialog" :before-close="closeDialog"
:class="{'dialog-wrapper': true, [$style.center]: center, scrollable: scrollable}" :class="{'dialog-wrapper': true, [$style.center]: center, scrollable: scrollable}"
:width="width" :width="width"
@ -38,6 +38,8 @@
<script lang="ts"> <script lang="ts">
import Vue from "vue"; import Vue from "vue";
import { useUIStore } from '@/stores/ui';
import { mapStores } from "pinia";
export default Vue.extend({ export default Vue.extend({
name: "Modal", name: "Modal",
@ -118,7 +120,7 @@ export default Vue.extend({
}); });
this.$props.eventBus.$on('closeAll', () => { this.$props.eventBus.$on('closeAll', () => {
this.closeAllDialogs(); this.uiStore.closeAllModals();
}); });
} }
@ -130,51 +132,8 @@ export default Vue.extend({
beforeDestroy() { beforeDestroy() {
window.removeEventListener('keydown', this.onWindowKeydown); window.removeEventListener('keydown', this.onWindowKeydown);
}, },
methods: {
onWindowKeydown(event: KeyboardEvent) {
if (!this.isActive) {
return;
}
if (event && event.keyCode === 13) {
this.handleEnter();
}
},
handleEnter() {
if (this.isActive) {
this.$emit('enter');
}
},
closeAllDialogs() {
this.$store.commit('ui/closeAllModals');
},
async closeDialog() {
if (this.beforeClose) {
const shouldClose = await this.beforeClose();
if (shouldClose === false) { // must be strictly false to stop modal from closing
return;
}
}
this.$store.commit('ui/closeModal', this.$props.name);
},
getCustomClass() {
let classes = this.$props.customClass || '';
if (this.$props.classic) {
classes = `${classes} classic`;
}
return classes;
},
},
computed: { computed: {
isActive(): boolean { ...mapStores(useUIStore),
return this.$store.getters['ui/isModalActive'](this.$props.name);
},
visible(): boolean {
return this.$store.getters['ui/isModalOpen'](this.$props.name);
},
styles() { styles() {
const styles: {[prop: string]: string} = {}; const styles: {[prop: string]: string} = {};
if (this.height) { if (this.height) {
@ -195,6 +154,40 @@ export default Vue.extend({
return styles; return styles;
}, },
}, },
methods: {
onWindowKeydown(event: KeyboardEvent) {
if (!this.uiStore.isModalActive(this.$props.name)) {
return;
}
if (event && event.keyCode === 13) {
this.handleEnter();
}
},
handleEnter() {
if (this.uiStore.isModalActive(this.$props.name)) {
this.$emit('enter');
}
},
async closeDialog() {
if (this.beforeClose) {
const shouldClose = await this.beforeClose();
if (shouldClose === false) { // must be strictly false to stop modal from closing
return;
}
}
this.uiStore.closeModal(this.$props.name);
},
getCustomClass() {
let classes = this.$props.customClass || '';
if (this.$props.classic) {
classes = `${classes} classic`;
}
return classes;
},
},
}); });
</script> </script>

View file

@ -1,7 +1,7 @@
<template> <template>
<el-drawer <el-drawer
:direction="direction" :direction="direction"
:visible="visible" :visible="uiStore.isModalOpen(this.$props.name)"
:size="width" :size="width"
:before-close="close" :before-close="close"
:modal="modal" :modal="modal"
@ -19,6 +19,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { useUIStore } from "@/stores/ui";
import { mapStores } from "pinia";
import Vue from "vue"; import Vue from "vue";
export default Vue.extend({ export default Vue.extend({
@ -65,9 +67,12 @@ export default Vue.extend({
beforeDestroy() { beforeDestroy() {
window.removeEventListener('keydown', this.onWindowKeydown); window.removeEventListener('keydown', this.onWindowKeydown);
}, },
computed: {
...mapStores(useUIStore),
},
methods: { methods: {
onWindowKeydown(event: KeyboardEvent) { onWindowKeydown(event: KeyboardEvent) {
if (!this.isActive) { if (!this.uiStore.isModalActive(this.$props.name)) {
return; return;
} }
@ -76,7 +81,7 @@ export default Vue.extend({
} }
}, },
handleEnter() { handleEnter() {
if (this.isActive) { if (this.uiStore.isModalActive(this.$props.name)) {
this.$emit('enter'); this.$emit('enter');
} }
}, },
@ -87,16 +92,7 @@ export default Vue.extend({
return; return;
} }
} }
this.uiStore.closeModal(this.$props.name);
this.$store.commit('ui/closeModal', this.$props.name);
},
},
computed: {
isActive(): boolean {
return this.$store.getters['ui/isModalActive'](this.$props.name);
},
visible(): boolean {
return this.$store.getters['ui/isModalOpen'](this.$props.name);
}, },
}, },
}); });

View file

@ -1,40 +1,36 @@
<template> <template>
<div <div
v-if="isOpen(name) || keepAlive" v-if="uiStore.isModalOpen(name) || keepAlive"
> >
<slot <slot
:modalName="name" :modalName="name"
:active="isActive(name)" :active="uiStore.isModalActive(name)"
:open="isOpen(name)" :open="uiStore.isModalOpen(name)"
:activeId="getActiveId(name)" :activeId="uiStore.getModalActiveId(name)"
:mode="getMode(name)" :mode="uiStore.getModalMode(name)"
:data="getData(name)" :data="uiStore.getModalData(name)"
></slot> ></slot>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from "vue"; import Vue from "vue";
import { useUIStore } from '@/stores/ui';
import { mapStores } from "pinia";
export default Vue.extend({ export default Vue.extend({
name: "ModalRoot", name: "ModalRoot",
props: ["name", "keepAlive"], props: {
methods: { name: {
isActive(name: string) { type: String,
return this.$store.getters['ui/isModalActive'](name); required: true,
}, },
isOpen(name: string) { keepAlive: {
return this.$store.getters['ui/isModalOpen'](name); type: Boolean,
},
getData(name: string) {
return this.$store.getters['ui/getModalData'](name);
},
getMode(name: string) {
return this.$store.getters['ui/getModalMode'](name);
},
getActiveId(name: string) {
return this.$store.getters['ui/getModalActiveId'](name);
}, },
}, },
computed: {
...mapStores(useUIStore),
},
}); });
</script> </script>

View file

@ -49,6 +49,9 @@ import {
} from '@/constants'; } from '@/constants';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { debounceHelper } from './mixins/debounce'; import { debounceHelper } from './mixins/debounce';
import { mapStores } from 'pinia';
import { useNDVStore } from '@/stores/ndv';
import { nodePanelType } from '@/Interface';
const SIDE_MARGIN = 24; const SIDE_MARGIN = 24;
@ -116,32 +119,35 @@ export default mixins(debounceHelper).extend({
window.removeEventListener('resize', this.setTotalWidth); window.removeEventListener('resize', this.setTotalWidth);
}, },
computed: { computed: {
...mapStores(
useNDVStore,
),
mainPanelDimensions(): { mainPanelDimensions(): {
relativeWidth: number, relativeWidth: number,
relativeLeft: number, relativeLeft: number,
relativeRight: number relativeRight: number
} { } {
return this.$store.getters['ndv/mainPanelDimensions'](this.currentNodePaneType); return this.ndvStore.getMainPanelDimensions(this.currentNodePaneType as nodePanelType);
}, },
supportedResizeDirections() { supportedResizeDirections(): string[] {
const supportedDirections = ['right']; const supportedDirections = ['right'];
if(this.isDraggable) supportedDirections.push('left'); if(this.isDraggable) supportedDirections.push('left');
return supportedDirections; return supportedDirections;
}, },
currentNodePaneType() { currentNodePaneType(): string {
if(!this.hasInputSlot) return 'inputless'; if(!this.hasInputSlot) return 'inputless';
if(!this.isDraggable) return 'dragless'; if(!this.isDraggable) return 'dragless';
if(this.nodeType === null) return 'unknown'; if(this.nodeType === null) return 'unknown';
return get(this, 'nodeType.parameterPane') || 'regular'; return get(this, 'nodeType.parameterPane') || 'regular';
}, },
hasInputSlot() { hasInputSlot(): boolean {
return this.$slots.input !== undefined; return this.$slots.input !== undefined;
}, },
inputPanelMargin(): number { inputPanelMargin(): number {
return this.pxToRelativeWidth(SIDE_PANELS_MARGIN); return this.pxToRelativeWidth(SIDE_PANELS_MARGIN);
}, },
minWindowWidth() { minWindowWidth(): number {
return 2 * (SIDE_MARGIN + SIDE_PANELS_MARGIN) + MIN_PANEL_WIDTH; return 2 * (SIDE_MARGIN + SIDE_PANELS_MARGIN) + MIN_PANEL_WIDTH;
}, },
minimumLeftPosition(): number { minimumLeftPosition(): number {
@ -196,7 +202,7 @@ export default mixins(debounceHelper).extend({
const currentRelativeLeftDelta = this.calculatedPositions.outputPanelRelativeLeft - panelMinLeft; const currentRelativeLeftDelta = this.calculatedPositions.outputPanelRelativeLeft - panelMinLeft;
return currentRelativeLeftDelta > 0 ? currentRelativeLeftDelta : 0; return currentRelativeLeftDelta > 0 ? currentRelativeLeftDelta : 0;
}, },
hasDoubleWidth() { hasDoubleWidth(): boolean {
return get(this, 'nodeType.parameterPane') === 'wide'; return get(this, 'nodeType.parameterPane') === 'wide';
}, },
fixedPanelWidth(): number { fixedPanelWidth(): number {
@ -244,7 +250,7 @@ export default mixins(debounceHelper).extend({
setMainPanelWidth(relativeWidth?: number) { setMainPanelWidth(relativeWidth?: number) {
const mainPanelRelativeWidth = relativeWidth || this.pxToRelativeWidth(initialMainPanelWidth[this.currentNodePaneType]); const mainPanelRelativeWidth = relativeWidth || this.pxToRelativeWidth(initialMainPanelWidth[this.currentNodePaneType]);
this.$store.commit('ndv/setMainPanelDimensions', { this.ndvStore.setMainPanelDimensions({
panelType: this.currentNodePaneType, panelType: this.currentNodePaneType,
dimensions: { dimensions: {
relativeWidth: mainPanelRelativeWidth, relativeWidth: mainPanelRelativeWidth,
@ -260,7 +266,7 @@ export default mixins(debounceHelper).extend({
const isInputless = this.currentNodePaneType === 'inputless'; const isInputless = this.currentNodePaneType === 'inputless';
if(isMinLeft) { if(isMinLeft) {
this.$store.commit('ndv/setMainPanelDimensions', { this.ndvStore.setMainPanelDimensions({
panelType: this.currentNodePaneType, panelType: this.currentNodePaneType,
dimensions: { dimensions: {
relativeLeft: this.minimumLeftPosition, relativeLeft: this.minimumLeftPosition,
@ -271,18 +277,18 @@ export default mixins(debounceHelper).extend({
} }
if(isMaxRight) { if(isMaxRight) {
this.$store.commit('ndv/setMainPanelDimensions', { this.ndvStore.setMainPanelDimensions({
panelType: this.currentNodePaneType, panelType: this.currentNodePaneType as nodePanelType,
dimensions: { dimensions: {
relativeLeft: 1 - this.mainPanelDimensions.relativeWidth - this.maximumRightPosition, relativeLeft: 1 - this.mainPanelDimensions.relativeWidth - this.maximumRightPosition,
relativeRight: this.maximumRightPosition, relativeRight: this.maximumRightPosition as number,
}, },
}); });
return; return;
} }
this.$store.commit('ndv/setMainPanelDimensions', { this.ndvStore.setMainPanelDimensions({
panelType: this.currentNodePaneType, panelType: this.currentNodePaneType as nodePanelType,
dimensions: { dimensions: {
relativeLeft: isInputless ? this.minimumLeftPosition : mainPanelRelativeLeft, relativeLeft: isInputless ? this.minimumLeftPosition : mainPanelRelativeLeft,
relativeRight: mainPanelRelativeRight, relativeRight: mainPanelRelativeRight,

View file

@ -111,8 +111,13 @@ import mixins from 'vue-typed-mixins';
import { get } from 'lodash'; import { get } from 'lodash';
import { getStyleTokenValue, getTriggerNodeServiceName } from './helpers'; import { getStyleTokenValue, getTriggerNodeServiceName } from './helpers';
import { INodeUi, XYPosition } from '@/Interface'; import { IExecutionsSummary, INodeUi, XYPosition } from '@/Interface';
import { debounceHelper } from './mixins/debounce'; import { debounceHelper } from './mixins/debounce';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins( export default mixins(
externalHooks, externalHooks,
@ -128,6 +133,12 @@ export default mixins(
NodeIcon, NodeIcon,
}, },
computed: { computed: {
...mapStores(
useNodeTypesStore,
useNDVStore,
useUIStore,
useWorkflowsStore,
),
isDuplicatable(): boolean { isDuplicatable(): boolean {
if(!this.nodeType) return true; if(!this.nodeType) return true;
return this.nodeType.maxNodes === undefined || this.sameTypeNodes.length < this.nodeType.maxNodes; return this.nodeType.maxNodes === undefined || this.sameTypeNodes.length < this.nodeType.maxNodes;
@ -136,7 +147,7 @@ export default mixins(
return this.nodeType?.group.includes('schedule') === true; return this.nodeType?.group.includes('schedule') === true;
}, },
nodeRunData(): ITaskData[] { nodeRunData(): ITaskData[] {
return this.$store.getters.getWorkflowResultDataByNodeName(this.data.name); return this.workflowsStore.getWorkflowResultDataByNodeName(this.data?.name || '') || [];
}, },
hasIssues (): boolean { hasIssues (): boolean {
if (this.hasPinData) return false; if (this.hasPinData) return false;
@ -154,7 +165,7 @@ export default mixins(
return workflowResultDataNode.length; return workflowResultDataNode.length;
}, },
canvasOffsetPosition() { canvasOffsetPosition() {
return this.$store.getters.getNodeViewOffsetPosition; return this.uiStore.nodeViewOffsetPosition;
}, },
getTriggerNodeTooltip (): string | undefined { getTriggerNodeTooltip (): string | undefined {
if (this.nodeType !== null && this.nodeType.hasOwnProperty('eventTriggerDescription')) { if (this.nodeType !== null && this.nodeType.hasOwnProperty('eventTriggerDescription')) {
@ -179,11 +190,11 @@ export default mixins(
return !!(this.nodeType && this.nodeType.polling); return !!(this.nodeType && this.nodeType.polling);
}, },
isExecuting (): boolean { isExecuting (): boolean {
return this.$store.getters.executingNode === this.data.name; return this.workflowsStore.executingNode === this.data.name;
}, },
isSingleActiveTriggerNode (): boolean { isSingleActiveTriggerNode (): boolean {
const nodes = this.$store.getters.workflowTriggerNodes.filter((node: INodeUi) => { const nodes = this.workflowsStore.workflowTriggerNodes.filter((node: INodeUi) => {
const nodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion) as INodeTypeDescription | null; const nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
return nodeType && nodeType.eventTriggerDescription !== '' && !node.disabled; return nodeType && nodeType.eventTriggerDescription !== '' && !node.disabled;
}); });
@ -193,7 +204,7 @@ export default mixins(
return this.data.type === MANUAL_TRIGGER_NODE_TYPE; return this.data.type === MANUAL_TRIGGER_NODE_TYPE;
}, },
isTriggerNode (): boolean { isTriggerNode (): boolean {
return this.$store.getters['nodeTypes/isTriggerNode'](this.data.type); return this.nodeTypesStore.isTriggerNode(this.data?.type || '');
}, },
isTriggerNodeTooltipEmpty () : boolean { isTriggerNodeTooltipEmpty () : boolean {
return this.nodeType !== null ? this.nodeType.eventTriggerDescription === '' : false; return this.nodeType !== null ? this.nodeType.eventTriggerDescription === '' : false;
@ -202,13 +213,13 @@ export default mixins(
return this.node && this.node.disabled; return this.node && this.node.disabled;
}, },
nodeType (): INodeTypeDescription | null { nodeType (): INodeTypeDescription | null {
return this.data && this.$store.getters['nodeTypes/getNodeType'](this.data.type, this.data.typeVersion); return this.data && this.nodeTypesStore.getNodeType(this.data.type, this.data.typeVersion);
}, },
node (): INodeUi | undefined { // same as this.data but reactive.. node (): INodeUi | undefined { // same as this.data but reactive..
return this.$store.getters.nodesByName[this.name] as INodeUi | undefined; return this.workflowsStore.nodesByName[this.name] as INodeUi | undefined;
}, },
sameTypeNodes (): INodeUi[] { sameTypeNodes (): INodeUi[] {
return this.$store.getters.allNodes.filter((node: INodeUi) => node.type === this.data.type); return this.workflowsStore.allNodes.filter((node: INodeUi) => node.type === this.data.type);
}, },
nodeClass (): object { nodeClass (): object {
return { return {
@ -261,7 +272,7 @@ export default mixins(
return this.data.name; return this.data.name;
}, },
waiting (): string | undefined { waiting (): string | undefined {
const workflowExecution = this.$store.getters.getWorkflowExecution; const workflowExecution = this.workflowsStore.getWorkflowExecution as IExecutionsSummary;
if (workflowExecution && workflowExecution.waitTill) { if (workflowExecution && workflowExecution.waitTill) {
const lastNodeExecuted = get(workflowExecution, 'data.resultData.lastNodeExecuted'); const lastNodeExecuted = get(workflowExecution, 'data.resultData.lastNodeExecuted');
@ -285,7 +296,7 @@ export default mixins(
return; return;
}, },
workflowRunning (): boolean { workflowRunning (): boolean {
return this.$store.getters.isActionActive('workflowRunning'); return this.uiStore.isActionActive('workflowRunning');
}, },
nodeStyle (): object { nodeStyle (): object {
let borderColor = getStyleTokenValue('--color-foreground-xdark'); let borderColor = getStyleTokenValue('--color-foreground-xdark');
@ -312,7 +323,7 @@ export default mixins(
return returnStyles; return returnStyles;
}, },
isSelected (): boolean { isSelected (): boolean {
return this.$store.getters.getSelectedNodes.find((node: INodeUi) => node.name === this.data.name); return this.uiStore.getSelectedNodes.find((node: INodeUi) => node.name === this.data.name) !== undefined;
}, },
shiftOutputCount (): boolean { shiftOutputCount (): boolean {
return !!(this.nodeType && this.nodeType.outputs.length > 2); return !!(this.nodeType && this.nodeType.outputs.length > 2);
@ -408,14 +419,14 @@ export default mixins(
}, },
disableNode () { disableNode () {
this.disableNodes([this.data]); this.disableNodes([this.data]);
this.$telemetry.track('User clicked node hover button', { node_type: this.data.type, button_name: 'disable', workflow_id: this.$store.getters.workflowId }); this.$telemetry.track('User clicked node hover button', { node_type: this.data.type, button_name: 'disable', workflow_id: this.workflowsStore.workflowId });
}, },
executeNode () { executeNode () {
this.$emit('runWorkflow', this.data.name, 'Node.executeNode'); this.$emit('runWorkflow', this.data.name, 'Node.executeNode');
this.$telemetry.track('User clicked node hover button', { node_type: this.data.type, button_name: 'execute', workflow_id: this.$store.getters.workflowId }); this.$telemetry.track('User clicked node hover button', { node_type: this.data.type, button_name: 'execute', workflow_id: this.workflowsStore.workflowId });
}, },
deleteNode () { deleteNode () {
this.$telemetry.track('User clicked node hover button', { node_type: this.data.type, button_name: 'delete', workflow_id: this.$store.getters.workflowId }); this.$telemetry.track('User clicked node hover button', { node_type: this.data.type, button_name: 'delete', workflow_id: this.workflowsStore.workflowId });
Vue.nextTick(() => { Vue.nextTick(() => {
// Wait a tick else vue causes problems because the data is gone // Wait a tick else vue causes problems because the data is gone
@ -423,7 +434,7 @@ export default mixins(
}); });
}, },
duplicateNode () { duplicateNode () {
this.$telemetry.track('User clicked node hover button', { node_type: this.data.type, button_name: 'duplicate', workflow_id: this.$store.getters.workflowId }); this.$telemetry.track('User clicked node hover button', { node_type: this.data.type, button_name: 'duplicate', workflow_id: this.workflowsStore.workflowId });
Vue.nextTick(() => { Vue.nextTick(() => {
// Wait a tick else vue causes problems because the data is gone // Wait a tick else vue causes problems because the data is gone
this.$emit('duplicateNode', this.data.name); this.$emit('duplicateNode', this.data.name);
@ -444,7 +455,7 @@ export default mixins(
}, },
setNodeActive () { setNodeActive () {
this.$store.commit('ndv/setActiveNodeName', this.data.name); this.ndvStore.activeNodeName = this.data ? this.data.name : '';
this.pinDataDiscoveryTooltipVisible = false; this.pinDataDiscoveryTooltipVisible = false;
}, },
touchStart () { touchStart () {

View file

@ -20,6 +20,8 @@
import Vue from "vue"; import Vue from "vue";
import * as CanvasHelpers from "@/views/canvasHelpers"; import * as CanvasHelpers from "@/views/canvasHelpers";
import {DEFAULT_STICKY_HEIGHT, DEFAULT_STICKY_WIDTH, STICKY_NODE_TYPE} from "@/constants"; import {DEFAULT_STICKY_HEIGHT, DEFAULT_STICKY_WIDTH, STICKY_NODE_TYPE} from "@/constants";
import { mapStores } from "pinia";
import { useUIStore } from "@/stores/ui";
export default Vue.extend({ export default Vue.extend({
name: 'node-creation', name: 'node-creation',
@ -41,6 +43,9 @@ export default Vue.extend({
showStickyButton: false, showStickyButton: false,
}; };
}, },
computed: {
...mapStores(useUIStore),
},
methods: { methods: {
onCreateMenuHoverIn(mouseinEvent: MouseEvent) { onCreateMenuHoverIn(mouseinEvent: MouseEvent) {
const buttonsWrapper = mouseinEvent.target as Element; const buttonsWrapper = mouseinEvent.target as Element;
@ -73,7 +78,7 @@ export default Vue.extend({
(document.activeElement as HTMLElement).blur(); (document.activeElement as HTMLElement).blur();
} }
const offset: [number, number] = [...(this.$store.getters.getNodeViewOffsetPosition as [number, number])]; const offset: [number, number] = [...(this.uiStore.nodeViewOffsetPosition)];
const position = CanvasHelpers.getMidCanvasPosition(this.nodeViewScale, offset); const position = CanvasHelpers.getMidCanvasPosition(this.nodeViewScale, offset);
position[0] -= DEFAULT_STICKY_WIDTH / 2; position[0] -= DEFAULT_STICKY_WIDTH / 2;

View file

@ -92,6 +92,10 @@ import { matchesNodeType, matchesSelectType } from './helpers';
import { BaseTextKey } from '@/plugins/i18n'; import { BaseTextKey } from '@/plugins/i18n';
import { intersection } from '@/utils'; import { intersection } from '@/utils';
import { sublimeSearch } from './sortUtils'; import { sublimeSearch } from './sortUtils';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useRootStore } from '@/stores/n8nRootStore';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins(externalHooks, globalLinkActions).extend({ export default mixins(externalHooks, globalLinkActions).extend({
name: 'CategorizedItems', name: 'CategorizedItems',
@ -146,6 +150,11 @@ export default mixins(externalHooks, globalLinkActions).extend({
this.unregisterCustomAction('showAllNodeCreatorNodes'); this.unregisterCustomAction('showAllNodeCreatorNodes');
}, },
computed: { computed: {
...mapStores(
useNodeTypesStore,
useRootStore,
useWorkflowsStore,
),
activeSubcategory(): INodeCreateElement | null { activeSubcategory(): INodeCreateElement | null {
return this.activeSubcategoryHistory[this.activeSubcategoryHistory.length - 1] || null; return this.activeSubcategoryHistory[this.activeSubcategoryHistory.length - 1] || null;
}, },
@ -156,10 +165,10 @@ export default mixins(externalHooks, globalLinkActions).extend({
return this.$store.getters['nodeCreator/selectedType']; return this.$store.getters['nodeCreator/selectedType'];
}, },
categoriesWithNodes(): ICategoriesWithNodes { categoriesWithNodes(): ICategoriesWithNodes {
return this.$store.getters['nodeTypes/categoriesWithNodes']; return this.nodeTypesStore.categoriesWithNodes;
}, },
categorizedItems(): INodeCreateElement[] { categorizedItems(): INodeCreateElement[] {
return this.$store.getters['nodeTypes/categorizedItems']; return this.nodeTypesStore.categorizedItems;
}, },
activeSubcategoryTitle(): string { activeSubcategoryTitle(): string {
if(!this.activeSubcategory || !this.activeSubcategory.properties) return ''; if(!this.activeSubcategory || !this.activeSubcategory.properties) return '';
@ -178,7 +187,7 @@ export default mixins(externalHooks, globalLinkActions).extend({
return this.nodeFilter.toLowerCase().trim(); return this.nodeFilter.toLowerCase().trim();
}, },
defaultLocale (): string { defaultLocale (): string {
return this.$store.getters.defaultLocale; return this.rootStore.defaultLocale;
}, },
filteredNodeTypes(): INodeCreateElement[] { filteredNodeTypes(): INodeCreateElement[] {
const filter = this.searchFilter; const filter = this.searchFilter;
@ -337,7 +346,7 @@ export default mixins(externalHooks, globalLinkActions).extend({
newValue, newValue,
selectedType: this.selectedType, selectedType: this.selectedType,
filteredNodes: this.filteredNodeTypes, filteredNodes: this.filteredNodeTypes,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
}); });
}, },
}, },
@ -444,7 +453,7 @@ export default mixins(externalHooks, globalLinkActions).extend({
); );
} else { } else {
this.activeCategory = [...this.activeCategory, category]; this.activeCategory = [...this.activeCategory, category];
this.$telemetry.trackNodesPanel('nodeCreateList.onCategoryExpanded', { category_name: category, workflow_id: this.$store.getters.workflowId }); this.$telemetry.trackNodesPanel('nodeCreateList.onCategoryExpanded', { category_name: category, workflow_id: this.workflowsStore.workflowId });
} }
this.activeIndex = this.categorized.findIndex( this.activeIndex = this.categorized.findIndex(
@ -456,7 +465,7 @@ export default mixins(externalHooks, globalLinkActions).extend({
this.$store.commit('nodeCreator/setShowTabs', false); this.$store.commit('nodeCreator/setShowTabs', false);
this.activeSubcategoryIndex = 0; this.activeSubcategoryIndex = 0;
this.activeSubcategoryHistory.push(selected); this.activeSubcategoryHistory.push(selected);
this.$telemetry.trackNodesPanel('nodeCreateList.onSubcategorySelected', { selected, workflow_id: this.$store.getters.workflowId }); this.$telemetry.trackNodesPanel('nodeCreateList.onSubcategorySelected', { selected, workflow_id: this.workflowsStore.workflowId });
}, },
onSubcategoryClose() { onSubcategoryClose() {

View file

@ -18,6 +18,8 @@ import camelcase from 'lodash.camelcase';
import { CategoryName } from '@/plugins/i18n'; import { CategoryName } from '@/plugins/i18n';
import { INodeCreateElement, ICategoriesWithNodes } from '@/Interface'; import { INodeCreateElement, ICategoriesWithNodes } from '@/Interface';
import { NODE_TYPE_COUNT_MAPPER } from '@/constants'; import { NODE_TYPE_COUNT_MAPPER } from '@/constants';
import { mapStores } from 'pinia';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default Vue.extend({ export default Vue.extend({
@ -27,20 +29,23 @@ export default Vue.extend({
}, },
}, },
computed: { computed: {
...mapStores(
useNodeTypesStore,
),
selectedType(): "Regular" | "Trigger" | "All" { selectedType(): "Regular" | "Trigger" | "All" {
return this.$store.getters['nodeCreator/selectedType']; return this.$store.getters['nodeCreator/selectedType'];
}, },
categoriesWithNodes(): ICategoriesWithNodes { categoriesWithNodes(): ICategoriesWithNodes {
return this.$store.getters['nodeTypes/categoriesWithNodes']; return this.nodeTypesStore.categoriesWithNodes;
}, },
categorizedItems(): INodeCreateElement[] { categorizedItems(): INodeCreateElement[] {
return this.$store.getters['nodeTypes/categorizedItems']; return this.nodeTypesStore.categorizedItems;
}, },
categoryName() { categoryName() {
return camelcase(this.item.category); return camelcase(this.item.category);
}, },
nodesCount(): number { nodesCount(): number {
const currentCategory = this.categoriesWithNodes[this.item.category]; const currentCategory= (this.categoriesWithNodes as ICategoriesWithNodes)[this.item.category];
const subcategories = Object.keys(currentCategory); const subcategories = Object.keys(currentCategory);
// We need to sum subcategories count for the curent nodeType view // We need to sum subcategories count for the curent nodeType view

View file

@ -33,6 +33,8 @@ import { ALL_NODE_FILTER, TRIGGER_NODE_FILTER, OTHER_TRIGGER_NODES_SUBCATEGORY,
import CategorizedItems from './CategorizedItems.vue'; import CategorizedItems from './CategorizedItems.vue';
import TypeSelector from './TypeSelector.vue'; import TypeSelector from './TypeSelector.vue';
import { INodeCreateElement } from '@/Interface'; import { INodeCreateElement } from '@/Interface';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
export default mixins(externalHooks).extend({ export default mixins(externalHooks).extend({
name: 'NodeCreateList', name: 'NodeCreateList',
@ -55,6 +57,9 @@ export default mixins(externalHooks).extend({
}; };
}, },
computed: { computed: {
...mapStores(
useWorkflowsStore,
),
selectedType(): string { selectedType(): string {
return this.$store.getters['nodeCreator/selectedType']; return this.$store.getters['nodeCreator/selectedType'];
}, },
@ -68,7 +73,7 @@ export default mixins(externalHooks).extend({
this.$telemetry.trackNodesPanel('nodeCreateList.selectedTypeChanged', { this.$telemetry.trackNodesPanel('nodeCreateList.selectedTypeChanged', {
old_filter: oldValue, old_filter: oldValue,
new_filter: newValue, new_filter: newValue,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
}); });
}, },
}, },
@ -80,7 +85,7 @@ export default mixins(externalHooks).extend({
destroyed() { destroyed() {
this.$store.commit('nodeCreator/setSelectedType', ALL_NODE_FILTER); this.$store.commit('nodeCreator/setSelectedType', ALL_NODE_FILTER);
this.$externalHooks().run('nodeCreateList.destroyed'); this.$externalHooks().run('nodeCreateList.destroyed');
this.$telemetry.trackNodesPanel('nodeCreateList.destroyed', { workflow_id: this.$store.getters.workflowId }); this.$telemetry.trackNodesPanel('nodeCreateList.destroyed', { workflow_id: this.workflowsStore.workflowId });
}, },
}); });
</script> </script>

View file

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<aside :class="{'node-creator-scrim': true, expanded: !sidebarMenuCollapsed, active: showScrim}" /> <aside :class="{'node-creator-scrim': true, expanded: !uiStore.sidebarMenuCollapsed, active: showScrim}" />
<slide-transition> <slide-transition>
<div <div
@ -29,6 +29,9 @@ import { INodeTypeDescription } from 'n8n-workflow';
import SlideTransition from '../../transitions/SlideTransition.vue'; import SlideTransition from '../../transitions/SlideTransition.vue';
import MainPanel from './MainPanel.vue'; import MainPanel from './MainPanel.vue';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default Vue.extend({ export default Vue.extend({
name: 'NodeCreator', name: 'NodeCreator',
@ -42,14 +45,15 @@ export default Vue.extend({
}, },
}, },
computed: { computed: {
...mapStores(
useNodeTypesStore,
useUIStore,
),
showScrim(): boolean { showScrim(): boolean {
return this.$store.getters['nodeCreator/showScrim']; return this.$store.getters['nodeCreator/showScrim'];
}, },
sidebarMenuCollapsed(): boolean {
return this.$store.getters['ui/sidebarMenuCollapsed'];
},
visibleNodeTypes(): INodeTypeDescription[] { visibleNodeTypes(): INodeTypeDescription[] {
return this.$store.getters['nodeTypes/visibleNodeTypes']; return this.nodeTypesStore.visibleNodeTypes;
}, },
searchItems(): INodeCreateElement[] { searchItems(): INodeCreateElement[] {
const sorted = [...this.visibleNodeTypes]; const sorted = [...this.visibleNodeTypes];

View file

@ -63,6 +63,7 @@ import {
ICredentialsResponse, ICredentialsResponse,
INodeUi, INodeUi,
INodeUpdatePropertiesInformation, INodeUpdatePropertiesInformation,
IUser,
} from '@/Interface'; } from '@/Interface';
import { import {
ICredentialType, ICredentialType,
@ -81,6 +82,11 @@ import { mapGetters } from "vuex";
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import {getCredentialPermissions} from "@/permissions"; import {getCredentialPermissions} from "@/permissions";
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useUsersStore } from '@/stores/users';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins( export default mixins(
genericHelpers, genericHelpers,
@ -103,11 +109,19 @@ export default mixins(
}; };
}, },
computed: { computed: {
...mapGetters('users', ['currentUser']), ...mapStores(
useNodeTypesStore,
useUIStore,
useUsersStore,
useWorkflowsStore,
),
...mapGetters('credentials', { ...mapGetters('credentials', {
allCredentialsByType: 'allCredentialsByType', allCredentialsByType: 'allCredentialsByType',
getCredentialTypeByName: 'getCredentialTypeByName', getCredentialTypeByName: 'getCredentialTypeByName',
}), }),
currentUser (): IUser {
return this.usersStore.currentUser || {} as IUser;
},
credentialTypesNode (): string[] { credentialTypesNode (): string[] {
return this.credentialTypesNodeDescription return this.credentialTypesNodeDescription
.map((credentialTypeDescription) => credentialTypeDescription.name); .map((credentialTypeDescription) => credentialTypeDescription.name);
@ -125,7 +139,7 @@ export default mixins(
if (credType) return [credType]; if (credType) return [credType];
const activeNodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion) as INodeTypeDescription | null; const activeNodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
if (activeNodeType && activeNodeType.credentials) { if (activeNodeType && activeNodeType.credentials) {
return activeNodeType.credentials; return activeNodeType.credentials;
} }
@ -220,8 +234,8 @@ export default mixins(
onCredentialSelected (credentialType: string, credentialId: string | null | undefined) { onCredentialSelected (credentialType: string, credentialId: string | null | undefined) {
if (credentialId === this.NEW_CREDENTIALS_TEXT) { if (credentialId === this.NEW_CREDENTIALS_TEXT) {
this.listenForNewCredentials(credentialType); this.listenForNewCredentials(credentialType);
this.$store.dispatch('ui/openNewCredential', { type: credentialType }); this.uiStore.openNewCredential(credentialType);
this.$telemetry.track('User opened Credential modal', { credential_type: credentialType, source: 'node', new_credential: true, workflow_id: this.$store.getters.workflowId }); this.$telemetry.track('User opened Credential modal', { credential_type: credentialType, source: 'node', new_credential: true, workflow_id: this.workflowsStore.workflowId });
return; return;
} }
@ -231,7 +245,7 @@ export default mixins(
credential_type: credentialType, credential_type: credentialType,
node_type: this.node.type, node_type: this.node.type,
...(this.hasProxyAuth(this.node) ? { is_service_specific: true } : {}), ...(this.hasProxyAuth(this.node) ? { is_service_specific: true } : {}),
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
credential_id: credentialId, credential_id: credentialId,
}, },
); );
@ -244,7 +258,7 @@ export default mixins(
// if credentials has been string or neither id matched nor name matched uniquely // if credentials has been string or neither id matched nor name matched uniquely
if (oldCredentials.id === null || (oldCredentials.id && !this.$store.getters['credentials/getCredentialByIdAndType'](oldCredentials.id, credentialType))) { if (oldCredentials.id === null || (oldCredentials.id && !this.$store.getters['credentials/getCredentialByIdAndType'](oldCredentials.id, credentialType))) {
// update all nodes in the workflow with the same old/invalid credentials // update all nodes in the workflow with the same old/invalid credentials
this.$store.commit('replaceInvalidWorkflowCredentials', { this.workflowsStore.replaceInvalidWorkflowCredentials({
credentials: selected, credentials: selected,
invalid: oldCredentials, invalid: oldCredentials,
type: credentialType, type: credentialType,
@ -316,9 +330,9 @@ export default mixins(
editCredential(credentialType: string): void { editCredential(credentialType: string): void {
const { id } = this.node.credentials[credentialType]; const { id } = this.node.credentials[credentialType];
this.$store.dispatch('ui/openExistingCredential', { id }); this.uiStore.openExistingCredential(id);
this.$telemetry.track('User opened Credential modal', { credential_type: credentialType, source: 'node', new_credential: false, workflow_id: this.$store.getters.workflowId }); this.$telemetry.track('User opened Credential modal', { credential_type: credentialType, source: 'node', new_credential: false, workflow_id: this.workflowsStore.workflowId });
this.listenForNewCredentials(credentialType); this.listenForNewCredentials(credentialType);
}, },

View file

@ -141,6 +141,11 @@ import {
import { workflowActivate } from './mixins/workflowActivate'; import { workflowActivate } from './mixins/workflowActivate';
import { pinData } from "@/components/mixins/pinData"; import { pinData } from "@/components/mixins/pinData";
import { dataPinningEventBus } from '@/event-bus/data-pinning-event-bus'; import { dataPinningEventBus } from '@/event-bus/data-pinning-event-bus';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useUIStore } from '@/stores/ui';
export default mixins( export default mixins(
externalHooks, externalHooks,
@ -182,8 +187,7 @@ export default mixins(
}; };
}, },
mounted() { mounted() {
this.$store.commit('ndv/setNDVSessionId'); this.ndvStore.setNDVSessionId;
dataPinningEventBus.$on('data-pinning-discovery', ({ isTooltipVisible }: { isTooltipVisible: boolean }) => { dataPinningEventBus.$on('data-pinning-discovery', ({ isTooltipVisible }: { isTooltipVisible: boolean }) => {
this.pinDataDiscoveryTooltipVisible = isTooltipVisible; this.pinDataDiscoveryTooltipVisible = isTooltipVisible;
}); });
@ -192,12 +196,17 @@ export default mixins(
dataPinningEventBus.$off('data-pinning-discovery'); dataPinningEventBus.$off('data-pinning-discovery');
}, },
computed: { computed: {
...mapGetters(['executionWaitingForWebhook']), ...mapStores(
useNodeTypesStore,
useNDVStore,
useUIStore,
useWorkflowsStore,
),
sessionId(): string { sessionId(): string {
return this.$store.getters['ndv/ndvSessionId']; return this.ndvStore.sessionId;
}, },
workflowRunning(): boolean { workflowRunning(): boolean {
return this.$store.getters.isActionActive('workflowRunning'); return this.uiStore.isActionActive('workflowRunning');
}, },
showTriggerWaitingWarning(): boolean { showTriggerWaitingWarning(): boolean {
return ( return (
@ -205,25 +214,25 @@ export default mixins(
!!this.activeNodeType && !!this.activeNodeType &&
!this.activeNodeType.group.includes('trigger') && !this.activeNodeType.group.includes('trigger') &&
this.workflowRunning && this.workflowRunning &&
this.executionWaitingForWebhook this.workflowsStore.executionWaitingForWebhook
); );
}, },
activeNode(): INodeUi | null { activeNode(): INodeUi | null {
return this.$store.getters['ndv/activeNode']; return this.ndvStore.activeNode;
}, },
inputNodeName(): string | undefined { inputNodeName(): string | undefined {
return this.selectedInput || this.parentNode; return this.selectedInput || this.parentNode;
}, },
inputNode(): INodeUi | null { inputNode(): INodeUi | null {
if (this.inputNodeName) { if (this.inputNodeName) {
return this.$store.getters.getNodeByName(this.inputNodeName); return this.workflowsStore.getNodeByName(this.inputNodeName);
} }
return null; return null;
}, },
activeNodeType(): INodeTypeDescription | null { activeNodeType(): INodeTypeDescription | null {
if (this.activeNode) { if (this.activeNode) {
return this.$store.getters['nodeTypes/getNodeType'](this.activeNode.type, this.activeNode.typeVersion); return this.nodeTypesStore.getNodeType(this.activeNode.type, this.activeNode.typeVersion);
} }
return null; return null;
}, },
@ -257,11 +266,11 @@ export default mixins(
}, },
isActiveStickyNode(): boolean { isActiveStickyNode(): boolean {
return ( return (
!!this.$store.getters['ndv/activeNode'] && this.$store.getters['ndv/activeNode'].type === STICKY_NODE_TYPE !!this.ndvStore.activeNode && this.ndvStore.activeNode .type === STICKY_NODE_TYPE
); );
}, },
workflowExecution(): IExecutionResponse | null { workflowExecution(): IExecutionResponse | null {
return this.$store.getters.getWorkflowExecution; return this.workflowsStore.getWorkflowExecution;
}, },
workflowRunData(): IRunData | null { workflowRunData(): IRunData | null {
if (this.workflowExecution === null) { if (this.workflowExecution === null) {
@ -340,13 +349,13 @@ export default mixins(
return `${BASE_NODE_SURVEY_URL}${this.activeNodeType.name}`; return `${BASE_NODE_SURVEY_URL}${this.activeNodeType.name}`;
}, },
outputPanelEditMode(): { enabled: boolean; value: string; } { outputPanelEditMode(): { enabled: boolean; value: string; } {
return this.$store.getters['ndv/outputPanelEditMode']; return this.ndvStore.outputPanelEditMode;
}, },
isWorkflowRunning(): boolean { isWorkflowRunning(): boolean {
return this.$store.getters.isActionActive('workflowRunning'); return this.uiStore.isActionActive('workflowRunning');
}, },
isExecutionWaitingForWebhook(): boolean { isExecutionWaitingForWebhook(): boolean {
return this.$store.getters.executionWaitingForWebhook; return this.workflowsStore.executionWaitingForWebhook;
}, },
blockUi(): boolean { blockUi(): boolean {
return this.isWorkflowRunning || this.isExecutionWaitingForWebhook; return this.isWorkflowRunning || this.isExecutionWaitingForWebhook;
@ -364,7 +373,7 @@ export default mixins(
this.avgInputRowHeight = 0; this.avgInputRowHeight = 0;
setTimeout(() => { setTimeout(() => {
this.$store.commit('ndv/setNDVSessionId'); this.ndvStore.setNDVSessionId;
}, 0); }, 0);
this.$externalHooks().run('dataDisplay.nodeTypeChanged', { this.$externalHooks().run('dataDisplay.nodeTypeChanged', {
nodeSubtitle: this.getNodeSubtitle(node, this.activeNodeType, this.getCurrentWorkflow()), nodeSubtitle: this.getNodeSubtitle(node, this.activeNodeType, this.getCurrentWorkflow()),
@ -374,24 +383,24 @@ export default mixins(
setTimeout(() => { setTimeout(() => {
if (this.activeNode) { if (this.activeNode) {
const outogingConnections = this.$store.getters.outgoingConnectionsByNodeName( const outgoingConnections = this.workflowsStore.outgoingConnectionsByNodeName(
this.activeNode.name, this.activeNode.name,
) as INodeConnections; ) as INodeConnections;
this.$telemetry.track('User opened node modal', { this.$telemetry.track('User opened node modal', {
node_type: this.activeNodeType ? this.activeNodeType.name : '', node_type: this.activeNodeType ? this.activeNodeType.name : '',
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId, session_id: this.sessionId,
parameters_pane_position: this.mainPanelPosition, parameters_pane_position: this.mainPanelPosition,
input_first_connector_runs: this.maxInputRun, input_first_connector_runs: this.maxInputRun,
output_first_connector_runs: this.maxOutputRun, output_first_connector_runs: this.maxOutputRun,
selected_view_inputs: this.isTriggerNode selected_view_inputs: this.isTriggerNode
? 'trigger' ? 'trigger'
: this.$store.getters['ndv/inputPanelDisplayMode'], : this.ndvStore.inputPanelDisplayMode,
selected_view_outputs: this.$store.getters['ndv/outputPanelDisplayMode'], selected_view_outputs: this.ndvStore.outputPanelDisplayMode,
input_connectors: this.parentNodes.length, input_connectors: this.parentNodes.length,
output_connectors: output_connectors:
outogingConnections && outogingConnections.main && outogingConnections.main.length, outgoingConnections && outgoingConnections.main && outgoingConnections.main.length,
input_displayed_run_index: this.inputRun, input_displayed_run_index: this.inputRun,
output_displayed_run_index: this.outputRun, output_displayed_run_index: this.outputRun,
data_pinning_tooltip_presented: this.pinDataDiscoveryTooltipVisible, data_pinning_tooltip_presented: this.pinDataDiscoveryTooltipVisible,
@ -413,12 +422,12 @@ export default mixins(
}, },
inputNodeName(nodeName: string | undefined) { inputNodeName(nodeName: string | undefined) {
setTimeout(() => { setTimeout(() => {
this.$store.commit('ndv/setInputNodeName', nodeName); this.ndvStore.setInputNodeName(nodeName);
}, 0); }, 0);
}, },
inputRun() { inputRun() {
setTimeout(() => { setTimeout(() => {
this.$store.commit('ndv/setInputRunIndex', this.inputRun); this.ndvStore.setInputRunIndex(this.inputRun);
}, 0); }, 0);
}, },
}, },
@ -428,7 +437,7 @@ export default mixins(
return; return;
} }
if (e === null) { if (e === null) {
this.$store.commit('ndv/setHoveringItem', null); this.ndvStore.setHoveringItem(null);
return; return;
} }
@ -438,11 +447,11 @@ export default mixins(
outputIndex: e.outputIndex, outputIndex: e.outputIndex,
itemIndex: e.itemIndex, itemIndex: e.itemIndex,
}; };
this.$store.commit('ndv/setHoveringItem', item); this.ndvStore.setHoveringItem(item);
}, },
onOutputItemHover(e: {itemIndex: number, outputIndex: number} | null) { onOutputItemHover(e: {itemIndex: number, outputIndex: number} | null) {
if (e === null || !this.activeNode) { if (e === null || !this.activeNode) {
this.$store.commit('ndv/setHoveringItem', null); this.ndvStore.setHoveringItem(null);
return; return;
} }
@ -452,7 +461,7 @@ export default mixins(
outputIndex: e.outputIndex, outputIndex: e.outputIndex,
itemIndex: e.itemIndex, itemIndex: e.itemIndex,
}; };
this.$store.commit('ndv/setHoveringItem', item); this.ndvStore.setHoveringItem(item);
}, },
onInputTableMounted(e: { avgRowHeight: number }) { onInputTableMounted(e: { avgRowHeight: number }) {
this.avgInputRowHeight = e.avgRowHeight; this.avgInputRowHeight = e.avgRowHeight;
@ -461,7 +470,7 @@ export default mixins(
this.avgOutputRowHeight = e.avgRowHeight; this.avgOutputRowHeight = e.avgRowHeight;
}, },
onWorkflowActivate() { onWorkflowActivate() {
this.$store.commit('ndv/setActiveNodeName', null); this.ndvStore.activeNodeName = null;
setTimeout(() => { setTimeout(() => {
this.activateCurrentWorkflow('ndv'); this.activateCurrentWorkflow('ndv');
}, 1000); }, 1000);
@ -471,7 +480,7 @@ export default mixins(
if (this.activeNode) { if (this.activeNode) {
this.$telemetry.track('User clicked ndv link', { this.$telemetry.track('User clicked ndv link', {
node_type: this.activeNode.type, node_type: this.activeNode.type,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId, session_id: this.sessionId,
pane: 'main', pane: 'main',
type: 'i-wish-this-node-would', type: 'i-wish-this-node-would',
@ -493,7 +502,7 @@ export default mixins(
end_position: e.position, end_position: e.position,
node_type: this.activeNodeType ? this.activeNodeType.name : '', node_type: this.activeNodeType ? this.activeNodeType.name : '',
session_id: this.sessionId, session_id: this.sessionId,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
}); });
this.mainPanelPosition = e.position; this.mainPanelPosition = e.position;
}, },
@ -567,21 +576,23 @@ export default mixins(
return; return;
} }
this.$store.commit('pinData', { node: this.activeNode, data: jsonParse(value) }); if (this.activeNode) {
this.workflowsStore.pinData({ node: this.activeNode, data: jsonParse(value) });
}
} }
this.$store.commit('ndv/setOutputPanelEditModeEnabled', false); this.ndvStore.setOutputPanelEditModeEnabled(false);
} }
this.$externalHooks().run('dataDisplay.nodeEditingFinished'); this.$externalHooks().run('dataDisplay.nodeEditingFinished');
this.$telemetry.track('User closed node modal', { this.$telemetry.track('User closed node modal', {
node_type: this.activeNodeType ? this.activeNodeType.name : '', node_type: this.activeNodeType ? this.activeNodeType.name : '',
session_id: this.sessionId, session_id: this.sessionId,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
}); });
this.triggerWaitingWarningEnabled = false; this.triggerWaitingWarningEnabled = false;
this.$store.commit('ndv/setActiveNodeName', null); this.ndvStore.activeNodeName = null;
this.$store.commit('ndv/resetNDVSessionId'); this.ndvStore.resetNDVSessionId();
}, },
onRunOutputIndexChange(run: number) { onRunOutputIndexChange(run: number) {
this.runOutputIndex = run; this.runOutputIndex = run;
@ -610,7 +621,7 @@ export default mixins(
this.$telemetry.track('User changed ndv input dropdown', { this.$telemetry.track('User changed ndv input dropdown', {
node_type: this.activeNode ? this.activeNode.type : '', node_type: this.activeNode ? this.activeNode.type : '',
session_id: this.sessionId, session_id: this.sessionId,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
selection_value: index, selection_value: index,
input_node_type: this.inputNode ? this.inputNode.type : '', input_node_type: this.inputNode ? this.inputNode.type : '',
}); });

View file

@ -23,6 +23,10 @@ import mixins from 'vue-typed-mixins';
import { workflowRun } from './mixins/workflowRun'; import { workflowRun } from './mixins/workflowRun';
import { pinData } from './mixins/pinData'; import { pinData } from './mixins/pinData';
import { dataPinningEventBus } from '@/event-bus/data-pinning-event-bus'; import { dataPinningEventBus } from '@/event-bus/data-pinning-event-bus';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins( export default mixins(
workflowRun, workflowRun,
@ -54,25 +58,30 @@ export default mixins(
}, },
}, },
computed: { computed: {
node (): INodeUi { ...mapStores(
return this.$store.getters.getNodeByName(this.nodeName); useNodeTypesStore,
useNDVStore,
useWorkflowsStore,
),
node (): INodeUi | null {
return this.workflowsStore.getNodeByName(this.nodeName);
}, },
nodeType (): INodeTypeDescription | null { nodeType (): INodeTypeDescription | null {
if (this.node) { if (this.node) {
return this.$store.getters['nodeTypes/getNodeType'](this.node.type, this.node.typeVersion); return this.nodeTypesStore.getNodeType(this.node.type, this.node.typeVersion);
} }
return null; return null;
}, },
nodeRunning (): boolean { nodeRunning (): boolean {
const triggeredNode = this.$store.getters.executedNode; const triggeredNode = this.workflowsStore.executedNode;
const executingNode = this.$store.getters.executingNode; const executingNode = this.workflowsStore.executingNode;
return this.workflowRunning && (executingNode === this.node.name || triggeredNode === this.node.name); return this.workflowRunning && (executingNode === this.node.name || triggeredNode === this.node.name);
}, },
workflowRunning (): boolean { workflowRunning (): boolean {
return this.$store.getters.isActionActive('workflowRunning'); return this.uiStore.isActionActive('workflowRunning');
}, },
isTriggerNode (): boolean { isTriggerNode (): boolean {
return this.$store.getters['nodeTypes/isTriggerNode'](this.node.type); return this.nodeTypesStore.isTriggerNode(this.node.type);
}, },
isManualTriggerNode (): boolean { isManualTriggerNode (): boolean {
return Boolean(this.nodeType && this.nodeType.name === MANUAL_TRIGGER_NODE_TYPE); return Boolean(this.nodeType && this.nodeType.name === MANUAL_TRIGGER_NODE_TYPE);
@ -87,8 +96,8 @@ export default mixins(
return Boolean(this.nodeType && this.nodeType.name === WEBHOOK_NODE_TYPE); return Boolean(this.nodeType && this.nodeType.name === WEBHOOK_NODE_TYPE);
}, },
isListeningForEvents(): boolean { isListeningForEvents(): boolean {
const waitingOnWebhook = this.$store.getters.executionWaitingForWebhook as boolean; const waitingOnWebhook = this.workflowsStore.executionWaitingForWebhook;
const executedNode = this.$store.getters.executedNode as string | undefined; const executedNode = this.workflowsStore.executedNode;
return ( return (
this.node && this.node &&
@ -114,7 +123,8 @@ export default mixins(
} }
if (this.isTriggerNode && this.hasIssues) { if (this.isTriggerNode && this.hasIssues) {
if (this.$store.getters['ndv/activeNode'] && this.$store.getters['ndv/activeNode'].name !== this.nodeName) { const activeNode = this.ndvStore.activeNode;
if (activeNode && activeNode.name !== this.nodeName) {
return this.$locale.baseText('ndv.execute.fixPrevious'); return this.$locale.baseText('ndv.execute.fixPrevious');
} }
@ -154,7 +164,7 @@ export default mixins(
methods: { methods: {
async stopWaitingForWebhook () { async stopWaitingForWebhook () {
try { try {
await this.restApi().removeTestWebhook(this.$store.getters.workflowId); await this.restApi().removeTestWebhook(this.workflowsStore.workflowId);
} catch (error) { } catch (error) {
this.$showError( this.$showError(
error, error,
@ -182,14 +192,14 @@ export default mixins(
if (shouldUnpinAndExecute) { if (shouldUnpinAndExecute) {
dataPinningEventBus.$emit('data-unpinning', { source: 'unpin-and-execute-modal' }); dataPinningEventBus.$emit('data-unpinning', { source: 'unpin-and-execute-modal' });
this.$store.commit('unpinData', { node: this.node }); this.workflowsStore.unpinData({ node: this.node });
} }
} }
if (!this.hasPinData || shouldUnpinAndExecute) { if (!this.hasPinData || shouldUnpinAndExecute) {
const telemetryPayload = { const telemetryPayload = {
node_type: this.nodeType ? this.nodeType.name : null, node_type: this.nodeType ? this.nodeType.name : null,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
source: this.telemetrySource, source: this.telemetrySource,
}; };
this.$telemetry.track('User clicked execute node button', telemetryPayload); this.$telemetry.track('User clicked execute node button', telemetryPayload);

View file

@ -15,7 +15,9 @@
<script lang="ts"> <script lang="ts">
import { IVersionNode } from '@/Interface'; import { IVersionNode } from '@/Interface';
import { useRootStore } from '@/stores/n8nRootStore';
import { INodeTypeDescription } from 'n8n-workflow'; import { INodeTypeDescription } from 'n8n-workflow';
import { mapStores } from 'pinia';
import Vue from 'vue'; import Vue from 'vue';
interface NodeIconSource { interface NodeIconSource {
@ -47,6 +49,9 @@ export default Vue.extend({
}, },
}, },
computed: { computed: {
...mapStores(
useRootStore,
),
type (): string { type (): string {
const nodeType = this.nodeType as INodeTypeDescription | IVersionNode | null; const nodeType = this.nodeType as INodeTypeDescription | IVersionNode | null;
let iconType = 'unknown'; let iconType = 'unknown';
@ -68,7 +73,7 @@ export default Vue.extend({
}, },
iconSource () : NodeIconSource { iconSource () : NodeIconSource {
const nodeType = this.nodeType as INodeTypeDescription | IVersionNode | null; const nodeType = this.nodeType as INodeTypeDescription | IVersionNode | null;
const restUrl = this.$store.getters.getRestUrl; const restUrl = this.rootStore.getRestUrl;
const iconSource = {} as NodeIconSource; const iconSource = {} as NodeIconSource;
if (nodeType) { if (nodeType) {

View file

@ -152,6 +152,11 @@ import { nodeHelpers } from '@/components/mixins/nodeHelpers';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import NodeExecuteButton from './NodeExecuteButton.vue'; import NodeExecuteButton from './NodeExecuteButton.vue';
import { isCommunityPackageName } from './helpers'; import { isCommunityPackageName } from './helpers';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins(externalHooks, nodeHelpers).extend({ export default mixins(externalHooks, nodeHelpers).extend({
name: 'NodeSettings', name: 'NodeSettings',
@ -165,8 +170,14 @@ export default mixins(externalHooks, nodeHelpers).extend({
NodeExecuteButton, NodeExecuteButton,
}, },
computed: { computed: {
isCurlImportModalOpen() { ...mapStores(
return this.$store.getters['ui/isModalOpen'](IMPORT_CURL_MODAL_KEY); useNodeTypesStore,
useNDVStore,
useUIStore,
useWorkflowsStore,
),
isCurlImportModalOpen(): boolean {
return this.uiStore.isModalOpen(IMPORT_CURL_MODAL_KEY);
}, },
nodeTypeName(): string { nodeTypeName(): string {
if (this.nodeType) { if (this.nodeType) {
@ -201,8 +212,8 @@ export default mixins(externalHooks, nodeHelpers).extend({
'background-color': this.node.color, 'background-color': this.node.color,
}; };
}, },
node(): INodeUi { node(): INodeUi | null {
return this.$store.getters['ndv/activeNode']; return this.ndvStore.activeNode;
}, },
parametersSetting(): INodeProperties[] { parametersSetting(): INodeProperties[] {
return this.parameters.filter((item) => { return this.parameters.filter((item) => {
@ -222,13 +233,13 @@ export default mixins(externalHooks, nodeHelpers).extend({
return this.nodeType.properties; return this.nodeType.properties;
}, },
outputPanelEditMode(): { enabled: boolean; value: string } { outputPanelEditMode(): { enabled: boolean; value: string } {
return this.$store.getters['ndv/outputPanelEditMode']; return this.ndvStore.outputPanelEditMode;
}, },
isCommunityNode(): boolean { isCommunityNode(): boolean {
return isCommunityPackageName(this.node.type); return isCommunityPackageName(this.node.type);
}, },
isTriggerNode(): boolean { isTriggerNode(): boolean {
return this.$store.getters['nodeTypes/isTriggerNode'](this.node.type); return this.nodeTypesStore.isTriggerNode(this.node.type);
}, },
}, },
props: { props: {
@ -366,7 +377,7 @@ export default mixins(externalHooks, nodeHelpers).extend({
}, },
isCurlImportModalOpen(newValue, oldValue) { isCurlImportModalOpen(newValue, oldValue) {
if (newValue === false) { if (newValue === false) {
let parameters = this.$store.getters['ui/getHttpNodeParameters']; let parameters = this.uiStore.getHttpNodeParameters || '';
if (!parameters) return; if (!parameters) return;
@ -382,7 +393,7 @@ export default mixins(externalHooks, nodeHelpers).extend({
value: parameters, value: parameters,
}); });
this.$store.dispatch('ui/setHttpNodeParameters', { parameters: '' }); this.uiStore.setHttpNodeParameters({ name: IMPORT_CURL_MODAL_KEY, parameters: '' });
} catch (_) {} } catch (_) {}
} }
}, },
@ -452,12 +463,14 @@ export default mixins(externalHooks, nodeHelpers).extend({
}, },
credentialSelected(updateInformation: INodeUpdatePropertiesInformation) { credentialSelected(updateInformation: INodeUpdatePropertiesInformation) {
// Update the values on the node // Update the values on the node
this.$store.commit('updateNodeProperties', updateInformation); this.workflowsStore.updateNodeProperties(updateInformation);
const node = this.$store.getters.getNodeByName(updateInformation.name); const node = this.workflowsStore.getNodeByName(updateInformation.name);
// Update the issues if (node) {
this.updateNodeCredentialIssues(node); // Update the issues
this.updateNodeCredentialIssues(node);
}
this.$externalHooks().run('nodeSettings.credentialSelected', { updateInformation }); this.$externalHooks().run('nodeSettings.credentialSelected', { updateInformation });
}, },
@ -481,7 +494,12 @@ export default mixins(externalHooks, nodeHelpers).extend({
// Save the node name before we commit the change because // Save the node name before we commit the change because
// we need the old name to rename the node properly // we need the old name to rename the node properly
const nodeNameBefore = parameterData.node || this.node.name; const nodeNameBefore = parameterData.node || this.node.name;
const node = this.$store.getters.getNodeByName(nodeNameBefore); const node = this.workflowsStore.getNodeByName(nodeNameBefore);
if (node === null) {
return;
}
if (parameterData.name === 'name') { if (parameterData.name === 'name') {
// Name of node changed so we have to set also the new node name as active // Name of node changed so we have to set also the new node name as active
@ -494,10 +512,7 @@ export default mixins(externalHooks, nodeHelpers).extend({
this.$emit('valueChanged', sendData); this.$emit('valueChanged', sendData);
} else if (parameterData.name === 'parameters') { } else if (parameterData.name === 'parameters') {
const nodeType = this.$store.getters['nodeTypes/getNodeType']( const nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
node.type,
node.typeVersion,
) as INodeTypeDescription | null;
if (!nodeType) { if (!nodeType) {
return; return;
} }
@ -573,23 +588,21 @@ export default mixins(externalHooks, nodeHelpers).extend({
} }
} }
// Update the data in vuex if (nodeParameters) {
const updateInformation = { const updateInformation: IUpdateInformation = {
name: node.name, name: node.name,
value: nodeParameters, value: nodeParameters,
}; };
this.$store.commit('setNodeParameters', updateInformation); this.workflowsStore.setNodeParameters(updateInformation);
this.updateNodeParameterIssues(node, nodeType); this.updateNodeParameterIssues(node, nodeType);
this.updateNodeCredentialIssues(node); this.updateNodeCredentialIssues(node);
}
} else if (parameterData.name.startsWith('parameters.')) { } else if (parameterData.name.startsWith('parameters.')) {
// A node parameter changed // A node parameter changed
const nodeType = this.$store.getters['nodeTypes/getNodeType']( const nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
node.type,
node.typeVersion,
) as INodeTypeDescription | null;
if (!nodeType) { if (!nodeType) {
return; return;
} }
@ -657,7 +670,7 @@ export default mixins(externalHooks, nodeHelpers).extend({
value: nodeParameters, value: nodeParameters,
}; };
this.$store.commit('setNodeParameters', updateInformation); this.workflowsStore.setNodeParameters(updateInformation);
this.$externalHooks().run('nodeSettings.valueChanged', { this.$externalHooks().run('nodeSettings.valueChanged', {
parameterPath, parameterPath,
@ -680,7 +693,8 @@ export default mixins(externalHooks, nodeHelpers).extend({
key: parameterData.name, key: parameterData.name,
value: newValue, value: newValue,
}; };
this.$store.commit('setNodeValue', updateInformation);
this.workflowsStore.setNodeValue(updateInformation);
} }
}, },
/** /**

View file

@ -6,7 +6,10 @@
import { externalHooks } from '@/components/mixins/externalHooks'; import { externalHooks } from '@/components/mixins/externalHooks';
import { BUILTIN_NODES_DOCS_URL, COMMUNITY_NODES_INSTALLATION_DOCS_URL, NPM_PACKAGE_DOCS_BASE_URL } from '@/constants'; import { BUILTIN_NODES_DOCS_URL, COMMUNITY_NODES_INSTALLATION_DOCS_URL, NPM_PACKAGE_DOCS_BASE_URL } from '@/constants';
import { INodeUi, ITab } from '@/Interface'; import { INodeUi, ITab } from '@/Interface';
import { useNDVStore } from '@/stores/ndv';
import { useWorkflowsStore } from '@/stores/workflows';
import { INodeTypeDescription } from 'n8n-workflow'; import { INodeTypeDescription } from 'n8n-workflow';
import { mapStores } from 'pinia';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { isCommunityPackageName } from './helpers'; import { isCommunityPackageName } from './helpers';
@ -26,8 +29,12 @@ export default mixins(
}, },
}, },
computed: { computed: {
activeNode(): INodeUi { ...mapStores(
return this.$store.getters['ndv/activeNode']; useNDVStore,
useWorkflowsStore,
),
activeNode(): INodeUi | null {
return this.ndvStore.activeNode;
}, },
documentationUrl (): string { documentationUrl (): string {
const nodeType = this.nodeType as INodeTypeDescription | null; const nodeType = this.nodeType as INodeTypeDescription | null;
@ -113,7 +120,7 @@ export default mixins(
this.$externalHooks().run('dataDisplay.onDocumentationUrlClick', { nodeType: this.nodeType as INodeTypeDescription, documentationUrl: this.documentationUrl }); this.$externalHooks().run('dataDisplay.onDocumentationUrlClick', { nodeType: this.nodeType as INodeTypeDescription, documentationUrl: this.documentationUrl });
this.$telemetry.track('User clicked ndv link', { this.$telemetry.track('User clicked ndv link', {
node_type: this.activeNode.type, node_type: this.activeNode.type,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId, session_id: this.sessionId,
pane: 'main', pane: 'main',
type: 'docs', type: 'docs',
@ -121,7 +128,7 @@ export default mixins(
} }
if(tab === 'settings' && this.nodeType) { if(tab === 'settings' && this.nodeType) {
this.$telemetry.track('User viewed node settings', { node_type: (this.nodeType as INodeTypeDescription).name, workflow_id: this.$store.getters.workflowId }); this.$telemetry.track('User viewed node settings', { node_type: (this.nodeType as INodeTypeDescription).name, workflow_id: this.workflowsStore.workflowId });
} }
if (tab === 'settings' || tab === 'params') { if (tab === 'settings' || tab === 'params') {

View file

@ -55,6 +55,8 @@ import Modal from './Modal.vue';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { showMessage } from './mixins/showMessage'; import { showMessage } from './mixins/showMessage';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
export default mixins( export default mixins(
showMessage, showMessage,
@ -75,6 +77,7 @@ export default mixins(
}; };
}, },
computed: { computed: {
...mapStores(useUIStore),
isEmailValid(): boolean { isEmailValid(): boolean {
return VALID_EMAIL_REGEX.test(String(this.email).toLowerCase()); return VALID_EMAIL_REGEX.test(String(this.email).toLowerCase());
}, },
@ -90,7 +93,7 @@ export default mixins(
this.okToClose = false; this.okToClose = false;
try { try {
await this.$store.dispatch('ui/applyForOnboardingCall', { email: this.email }); await this.uiStore.applyForOnboardingCall(this.email);
this.$showMessage({ this.$showMessage({
type: 'success', type: 'success',
title: this.$locale.baseText('onboardingCallSignupSucess.title'), title: this.$locale.baseText('onboardingCallSignupSucess.title'),

View file

@ -86,6 +86,11 @@ import RunData, { EnterEditModeArgs } from './RunData.vue';
import RunInfo from './RunInfo.vue'; import RunInfo from './RunInfo.vue';
import { pinData } from "@/components/mixins/pinData"; import { pinData } from "@/components/mixins/pinData";
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
type RunDataRef = Vue & { enterEditMode: (args: EnterEditModeArgs) => void }; type RunDataRef = Vue & { enterEditMode: (args: EnterEditModeArgs) => void };
@ -116,17 +121,23 @@ export default mixins(
}, },
}, },
computed: { computed: {
node(): INodeUi { ...mapStores(
return this.$store.getters['ndv/activeNode']; useNodeTypesStore,
useNDVStore,
useUIStore,
useWorkflowsStore,
),
node(): INodeUi | null {
return this.ndvStore.activeNode;
}, },
nodeType (): INodeTypeDescription | null { nodeType (): INodeTypeDescription | null {
if (this.node) { if (this.node) {
return this.$store.getters['nodeTypes/getNodeType'](this.node.type, this.node.typeVersion); return this.nodeTypesStore.getNodeType(this.node.type, this.node.typeVersion);
} }
return null; return null;
}, },
isTriggerNode (): boolean { isTriggerNode (): boolean {
return this.$store.getters['nodeTypes/isTriggerNode'](this.node.type); return this.nodeTypesStore.isTriggerNode(this.node.type);
}, },
isPollingTypeNode (): boolean { isPollingTypeNode (): boolean {
return !!(this.nodeType && this.nodeType.polling); return !!(this.nodeType && this.nodeType.polling);
@ -135,14 +146,14 @@ export default mixins(
return !!(this.nodeType && this.nodeType.group.includes('schedule')); return !!(this.nodeType && this.nodeType.group.includes('schedule'));
}, },
isNodeRunning(): boolean { isNodeRunning(): boolean {
const executingNode = this.$store.getters.executingNode; const executingNode = this.workflowsStore.executingNode;
return this.node && executingNode === this.node.name; return this.node && executingNode === this.node.name;
}, },
workflowRunning (): boolean { workflowRunning (): boolean {
return this.$store.getters.isActionActive('workflowRunning'); return this.uiStore.isActionActive('workflowRunning');
}, },
workflowExecution(): IExecutionResponse | null { workflowExecution(): IExecutionResponse | null {
return this.$store.getters.getWorkflowExecution; return this.workflowsStore.getWorkflowExecution;
}, },
workflowRunData(): IRunData | null { workflowRunData(): IRunData | null {
if (this.workflowExecution === null) { if (this.workflowExecution === null) {
@ -155,7 +166,7 @@ export default mixins(
return executionData.resultData.runData; return executionData.resultData.runData;
}, },
hasNodeRun(): boolean { hasNodeRun(): boolean {
if (this.$store.getters.subworkflowExecutionError) return true; if (this.workflowsStore.subWorkflowExecutionError) return true;
return Boolean( return Boolean(
this.node && this.workflowRunData && this.workflowRunData.hasOwnProperty(this.node.name), this.node && this.workflowRunData && this.workflowRunData.hasOwnProperty(this.node.name),
@ -199,7 +210,7 @@ export default mixins(
if (!this.node) { if (!this.node) {
return false; return false;
} }
const updatedAt = this.$store.getters.getParametersLastUpdated(this.node.name); const updatedAt = this.workflowsStore.getParametersLastUpdate(this.node.name);
if (!updatedAt || !this.runTaskData) { if (!updatedAt || !this.runTaskData) {
return false; return false;
} }
@ -207,7 +218,7 @@ export default mixins(
return updatedAt > runAt; return updatedAt > runAt;
}, },
outputPanelEditMode(): { enabled: boolean; value: string; } { outputPanelEditMode(): { enabled: boolean; value: string; } {
return this.$store.getters['ndv/outputPanelEditMode']; return this.ndvStore.outputPanelEditMode;
}, },
canPinData(): boolean { canPinData(): boolean {
return this.isPinDataNodeType && !this.isReadOnly; return this.isPinDataNodeType && !this.isReadOnly;
@ -221,7 +232,7 @@ export default mixins(
}); });
this.$telemetry.track('User clicked ndv link', { this.$telemetry.track('User clicked ndv link', {
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId, session_id: this.sessionId,
node_type: this.node.type, node_type: this.node.type,
pane: 'output', pane: 'output',
@ -239,7 +250,7 @@ export default mixins(
this.$emit('openSettings'); this.$emit('openSettings');
this.$telemetry.track('User clicked ndv link', { this.$telemetry.track('User clicked ndv link', {
node_type: this.node.type, node_type: this.node.type,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId, session_id: this.sessionId,
pane: 'output', pane: 'output',
type: 'settings', type: 'settings',

View file

@ -339,6 +339,10 @@ import { mapGetters } from 'vuex';
import { CODE_NODE_TYPE } from '@/constants'; import { CODE_NODE_TYPE } from '@/constants';
import { PropType } from 'vue'; import { PropType } from 'vue';
import { debounceHelper } from './mixins/debounce'; import { debounceHelper } from './mixins/debounce';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins( export default mixins(
externalHooks, externalHooks,
@ -472,6 +476,11 @@ export default mixins(
}, },
}, },
computed: { computed: {
...mapStores(
useNodeTypesStore,
useNDVStore,
useWorkflowsStore,
),
...mapGetters('credentials', ['allCredentialTypes']), ...mapGetters('credentials', ['allCredentialTypes']),
expressionDisplayValue(): string { expressionDisplayValue(): string {
if (this.activeDrop || this.forceShowExpression) { if (this.activeDrop || this.forceShowExpression) {
@ -499,7 +508,7 @@ export default mixins(
} }
// Get the resolved parameter values of the current node // Get the resolved parameter values of the current node
const currentNodeParameters = this.$store.getters['ndv/activeNode'].parameters; const currentNodeParameters = this.ndvStore.activeNode?.parameters;
try { try {
const resolvedNodeParameters = this.resolveParameter(currentNodeParameters); const resolvedNodeParameters = this.resolveParameter(currentNodeParameters);
@ -514,7 +523,7 @@ export default mixins(
} }
}, },
node (): INodeUi | null { node (): INodeUi | null {
return this.$store.getters['ndv/activeNode']; return this.ndvStore.activeNode;
}, },
displayTitle (): string { displayTitle (): string {
const interpolation = { interpolate: { shortPath: this.shortPath } }; const interpolation = { interpolate: { shortPath: this.shortPath } };
@ -742,12 +751,14 @@ export default mixins(
}, },
credentialSelected (updateInformation: INodeUpdatePropertiesInformation) { credentialSelected (updateInformation: INodeUpdatePropertiesInformation) {
// Update the values on the node // Update the values on the node
this.$store.commit('updateNodeProperties', updateInformation); this.workflowsStore.updateNodeProperties(updateInformation);
const node = this.$store.getters.getNodeByName(updateInformation.name); const node = this.workflowsStore.getNodeByName(updateInformation.name);
// Update the issues if (node) {
this.updateNodeCredentialIssues(node); // Update the issues
this.updateNodeCredentialIssues(node);
}
this.$externalHooks().run('nodeSettings.credentialSelected', { updateInformation }); this.$externalHooks().run('nodeSettings.credentialSelected', { updateInformation });
}, },
@ -784,12 +795,12 @@ export default mixins(
// Get the resolved parameter values of the current node // Get the resolved parameter values of the current node
try { try {
const currentNodeParameters = (this.$store.getters['ndv/activeNode'] as INodeUi).parameters; const currentNodeParameters = (this.ndvStore.activeNode as INodeUi).parameters;
const resolvedNodeParameters = this.resolveParameter(currentNodeParameters) as INodeParameters; const resolvedNodeParameters = this.resolveParameter(currentNodeParameters) as INodeParameters;
const loadOptionsMethod = this.getArgument('loadOptionsMethod') as string | undefined; const loadOptionsMethod = this.getArgument('loadOptionsMethod') as string | undefined;
const loadOptions = this.getArgument('loadOptions') as ILoadOptions | undefined; const loadOptions = this.getArgument('loadOptions') as ILoadOptions | undefined;
const options = await this.$store.dispatch('nodeTypes/getNodeParameterOptions', const options = await this.nodeTypesStore.getNodeParameterOptions(
{ {
nodeTypeAndVersion: { nodeTypeAndVersion: {
name: this.node.type, name: this.node.type,
@ -827,8 +838,8 @@ export default mixins(
parameter_name: this.parameter.displayName, parameter_name: this.parameter.displayName,
parameter_field_type: this.parameter.type, parameter_field_type: this.parameter.type,
new_expression: !this.isValueExpression, new_expression: !this.isValueExpression,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
session_id: this.$store.getters['ndv/ndvSessionId'], session_id: this.ndvStore.sessionId,
source: this.eventSource || 'ndv', source: this.eventSource || 'ndv',
}); });
} }
@ -956,11 +967,11 @@ export default mixins(
if (this.parameter.name === 'operation' || this.parameter.name === 'mode') { if (this.parameter.name === 'operation' || this.parameter.name === 'mode') {
this.$telemetry.track('User set node operation or mode', { this.$telemetry.track('User set node operation or mode', {
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
node_type: this.node && this.node.type, node_type: this.node && this.node.type,
resource: this.node && this.node.parameters.resource, resource: this.node && this.node.parameters.resource,
is_custom: value === CUSTOM_API_CALL_KEY, is_custom: value === CUSTOM_API_CALL_KEY,
session_id: this.$store.getters['ndv/ndvSessionId'], session_id: this.ndvStore.sessionId,
parameter: this.parameter.name, parameter: this.parameter.name,
}); });
} }

View file

@ -54,6 +54,8 @@ import Vue, { PropType } from 'vue';
import ParameterInputWrapper from './ParameterInputWrapper.vue'; import ParameterInputWrapper from './ParameterInputWrapper.vue';
import { isValueExpression } from './helpers'; import { isValueExpression } from './helpers';
import { INodeParameterResourceLocator, INodeProperties } from 'n8n-workflow'; import { INodeParameterResourceLocator, INodeProperties } from 'n8n-workflow';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
export default Vue.extend({ export default Vue.extend({
name: 'parameter-input-expanded', name: 'parameter-input-expanded',
@ -85,6 +87,9 @@ export default Vue.extend({
}; };
}, },
computed: { computed: {
...mapStores(
useWorkflowsStore,
),
showRequiredErrors(): boolean { showRequiredErrors(): boolean {
if (!this.$props.parameter.required) { if (!this.$props.parameter.required) {
return false; return false;
@ -136,7 +141,7 @@ export default Vue.extend({
this.$telemetry.track('User clicked credential modal docs link', { this.$telemetry.track('User clicked credential modal docs link', {
docs_link: this.documentationUrl, docs_link: this.documentationUrl,
source: 'field', source: 'field',
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
}); });
}, },
}, },

View file

@ -79,6 +79,8 @@ import { hasOnlyListMode } from '@/components/ResourceLocator/helpers';
import { INodeParameters, INodeProperties, INodePropertyMode } from 'n8n-workflow'; import { INodeParameters, INodeProperties, INodePropertyMode } from 'n8n-workflow';
import { isResourceLocatorValue } from '@/typeGuards'; import { isResourceLocatorValue } from '@/typeGuards';
import { BaseTextKey } from "@/plugins/i18n"; import { BaseTextKey } from "@/plugins/i18n";
import { mapStores } from 'pinia';
import { useNDVStore } from '@/stores/ndv';
export default mixins( export default mixins(
showMessage, showMessage,
@ -135,8 +137,11 @@ export default mixins(
]; ];
}, },
computed: { computed: {
...mapStores(
useNDVStore,
),
node (): INodeUi | null { node (): INodeUi | null {
return this.$store.getters['ndv/activeNode']; return this.ndvStore.activeNode;
}, },
hint (): string | null { hint (): string | null {
return this.$locale.nodeText().hint(this.parameter, this.path); return this.$locale.nodeText().hint(this.parameter, this.path);
@ -154,10 +159,10 @@ export default mixins(
return this.isResourceLocator ? !hasOnlyListMode(this.parameter): true; return this.isResourceLocator ? !hasOnlyListMode(this.parameter): true;
}, },
isInputDataEmpty (): boolean { isInputDataEmpty (): boolean {
return this.$store.getters['ndv/getNDVDataIsEmpty']('input'); return this.ndvStore.isDNVDataEmpty('input');
}, },
displayMode(): IRunDataDisplayMode { displayMode(): IRunDataDisplayMode {
return this.$store.getters['ndv/inputPanelDisplayMode']; return this.ndvStore.inputPanelDisplayMode;
}, },
showMappingTooltip (): boolean { showMappingTooltip (): boolean {
return this.focused && this.isInputTypeString && !this.isInputDataEmpty && window.localStorage.getItem(LOCAL_STORAGE_MAPPING_FLAG) !== 'true'; return this.focused && this.isInputTypeString && !this.isInputDataEmpty && window.localStorage.getItem(LOCAL_STORAGE_MAPPING_FLAG) !== 'true';
@ -167,13 +172,13 @@ export default mixins(
onFocus() { onFocus() {
this.focused = true; this.focused = true;
if (!this.parameter.noDataExpression) { if (!this.parameter.noDataExpression) {
this.$store.commit('ndv/setMappableNDVInputFocus', this.parameter.displayName); this.ndvStore.setMappableNDVInputFocus(this.parameter.displayName);
} }
}, },
onBlur() { onBlur() {
this.focused = false; this.focused = false;
if (!this.parameter.noDataExpression) { if (!this.parameter.noDataExpression) {
this.$store.commit('ndv/setMappableNDVInputFocus', ''); this.ndvStore.setMappableNDVInputFocus('');
} }
}, },
onMenuExpanded(expanded: boolean) { onMenuExpanded(expanded: boolean) {
@ -250,7 +255,7 @@ export default mixins(
window.localStorage.setItem(LOCAL_STORAGE_MAPPING_FLAG, 'true'); window.localStorage.setItem(LOCAL_STORAGE_MAPPING_FLAG, 'true');
} }
this.$store.commit('ndv/setMappingTelemetry', { this.ndvStore.setMappingTelemetry({
dest_node_type: this.node.type, dest_node_type: this.node.type,
dest_parameter: this.path, dest_parameter: this.path,
dest_parameter_mode: typeof prevValue === 'string' && prevValue.startsWith('=')? 'expression': 'fixed', dest_parameter_mode: typeof prevValue === 'string' && prevValue.startsWith('=')? 'expression': 'fixed',

View file

@ -119,6 +119,9 @@ import { get, set } from 'lodash';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import {Component} from "vue"; import {Component} from "vue";
import { mapState, mapStores } from 'pinia';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins( export default mixins(
workflowHelpers, workflowHelpers,
@ -141,6 +144,10 @@ export default mixins(
'isReadOnly', 'isReadOnly',
], ],
computed: { computed: {
...mapStores(
useNodeTypesStore,
useNDVStore,
),
nodeTypeVersion(): number | null { nodeTypeVersion(): number | null {
if (this.node) { if (this.node) {
return this.node.typeVersion; return this.node.typeVersion;
@ -159,8 +166,8 @@ export default mixins(
filteredParameterNames (): string[] { filteredParameterNames (): string[] {
return this.filteredParameters.map(parameter => parameter.name); return this.filteredParameters.map(parameter => parameter.name);
}, },
node (): INodeUi { node (): INodeUi | null {
return this.$store.getters['ndv/activeNode']; return this.ndvStore.activeNode;
}, },
indexToShowSlotAt (): number { indexToShowSlotAt (): number {
let index = 0; let index = 0;
@ -179,7 +186,7 @@ export default mixins(
methods: { methods: {
getCredentialsDependencies() { getCredentialsDependencies() {
const dependencies = new Set(); const dependencies = new Set();
const nodeType = this.$store.getters['nodeTypes/getNodeType'](this.node.type, this.node.typeVersion) as INodeTypeDescription | undefined; const nodeType = this.nodeTypesStore.getNodeType(this.node?.type || '', this.node?.typeVersion);
// Get names of all fields that credentials rendering depends on (using displayOptions > show) // Get names of all fields that credentials rendering depends on (using displayOptions > show)
if(nodeType && nodeType.credentials) { if(nodeType && nodeType.credentials) {
@ -323,7 +330,7 @@ export default mixins(
if (!newValue.includes(parameter)) { if (!newValue.includes(parameter)) {
const parameterData = { const parameterData = {
name: `${this.path}.${parameter}`, name: `${this.path}.${parameter}`,
node: this.$store.getters['ndv/activeNode'].name, node: this.ndvStore.activeNode?.name || '',
value: undefined, value: undefined,
}; };
this.$emit('valueChanged', parameterData); this.$emit('valueChanged', parameterData);

View file

@ -37,6 +37,8 @@ import { INodeProperties, INodePropertyMode, IRunData, isResourceLocatorValue, N
import { INodeUi, IUiState, IUpdateInformation, TargetItem } from '@/Interface'; import { INodeUi, IUiState, IUpdateInformation, TargetItem } from '@/Interface';
import { workflowHelpers } from './mixins/workflowHelpers'; import { workflowHelpers } from './mixins/workflowHelpers';
import { isValueExpression } from './helpers'; import { isValueExpression } from './helpers';
import { mapStores } from 'pinia';
import { useNDVStore } from '@/stores/ndv';
export default mixins( export default mixins(
showMessage, showMessage,
@ -97,11 +99,14 @@ export default mixins(
}, },
}, },
computed: { computed: {
...mapStores(
useNDVStore,
),
isValueExpression () { isValueExpression () {
return isValueExpression(this.parameter, this.value); return isValueExpression(this.parameter, this.value);
}, },
activeNode(): INodeUi | null { activeNode(): INodeUi | null {
return this.$store.getters['ndv/activeNode']; return this.ndvStore.activeNode;
}, },
selectedRLMode(): INodePropertyMode | undefined { selectedRLMode(): INodePropertyMode | undefined {
if (typeof this.value !== 'object' ||this.parameter.type !== 'resourceLocator' || !isResourceLocatorValue(this.value)) { if (typeof this.value !== 'object' ||this.parameter.type !== 'resourceLocator' || !isResourceLocatorValue(this.value)) {
@ -126,17 +131,17 @@ export default mixins(
return this.hint; return this.hint;
}, },
targetItem(): TargetItem | null { targetItem(): TargetItem | null {
return this.$store.getters['ndv/hoveringItem']; return this.ndvStore.hoveringItem;
}, },
expressionValueComputed (): string | null { expressionValueComputed (): string | null {
const inputNodeName: string | undefined = this.$store.getters['ndv/ndvInputNodeName']; const inputNodeName: string | undefined = this.ndvStore.ndvInputNodeName;
const value = isResourceLocatorValue(this.value)? this.value.value: this.value; const value = isResourceLocatorValue(this.value)? this.value.value: this.value;
if (this.activeNode === null || !this.isValueExpression || typeof value !== 'string') { if (this.activeNode === null || !this.isValueExpression || typeof value !== 'string') {
return null; return null;
} }
const inputRunIndex: number | undefined = this.$store.getters['ndv/ndvInputRunIndex']; const inputRunIndex: number | undefined = this.ndvStore.ndvInputRunIndex;
const inputBranchIndex: number | undefined = this.$store.getters['ndv/ndvInputBranchIndex']; const inputBranchIndex: number | undefined = this.ndvStore.ndvInputBranchIndex;
let computedValue: NodeParameterValue; let computedValue: NodeParameterValue;
try { try {
@ -157,7 +162,7 @@ export default mixins(
}, },
expressionOutput(): string | null { expressionOutput(): string | null {
if (this.isValueExpression && this.expressionValueComputed) { if (this.isValueExpression && this.expressionValueComputed) {
const inputData = this.$store.getters['ndv/ndvInputData']; const inputData = this.ndvStore.ndvInputData;
if (!inputData || (inputData && inputData.length <= 1)) { if (!inputData || (inputData && inputData.length <= 1)) {
return this.expressionValueComputed; return this.expressionValueComputed;
} }

View file

@ -17,7 +17,7 @@
> >
<template v-slot:content> <template v-slot:content>
<div v-if="submitted" :class="$style.submittedContainer"> <div v-if="submitted" :class="$style.submittedContainer">
<img :class="$style.demoImage" :src="baseUrl + 'suggestednodes.png'" /> <img :class="$style.demoImage" :src="rootStore.baseUrl + 'suggestednodes.png'" />
<n8n-text>{{ $locale.baseText('personalizationModal.lookOutForThingsMarked') }}</n8n-text> <n8n-text>{{ $locale.baseText('personalizationModal.lookOutForThingsMarked') }}</n8n-text>
</div> </div>
<div :class="$style.container" v-else> <div :class="$style.container" v-else>
@ -50,7 +50,6 @@ import mixins from 'vue-typed-mixins';
const SURVEY_VERSION = 'v3'; const SURVEY_VERSION = 'v3';
import { import {
CODING_SKILL_KEY,
COMPANY_SIZE_100_499, COMPANY_SIZE_100_499,
COMPANY_SIZE_1000_OR_MORE, COMPANY_SIZE_1000_OR_MORE,
COMPANY_SIZE_20_OR_LESS, COMPANY_SIZE_20_OR_LESS,
@ -116,11 +115,15 @@ import {
import { workflowHelpers } from '@/components/mixins/workflowHelpers'; import { workflowHelpers } from '@/components/mixins/workflowHelpers';
import { showMessage } from '@/components/mixins/showMessage'; import { showMessage } from '@/components/mixins/showMessage';
import Modal from './Modal.vue'; import Modal from './Modal.vue';
import { IFormInputs, IPersonalizationLatestVersion } from '@/Interface'; import { IFormInputs, IPersonalizationLatestVersion, IPersonalizationSurveyAnswersV3, IUser } from '@/Interface';
import Vue from 'vue'; import Vue from 'vue';
import { mapGetters } from 'vuex';
import { getAccountAge } from '@/modules/userHelpers'; import { getAccountAge } from '@/modules/userHelpers';
import { GenericValue } from 'n8n-workflow'; import { GenericValue } from 'n8n-workflow';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useSettingsStore } from '@/stores/settings';
import { useRootStore } from '@/stores/n8nRootStore';
import { useUsersStore } from '@/stores/users';
export default mixins(showMessage, workflowHelpers).extend({ export default mixins(showMessage, workflowHelpers).extend({
components: { Modal }, components: { Modal },
@ -138,15 +141,12 @@ export default mixins(showMessage, workflowHelpers).extend({
}; };
}, },
computed: { computed: {
...mapGetters({ ...mapStores(
baseUrl: 'getBaseUrl', useRootStore,
}), useSettingsStore,
...mapGetters('users', [ useUIStore,
'currentUser', useUsersStore,
]), ),
...mapGetters('settings', [
'isOnboardingCallPromptFeatureEnabled',
]),
survey() { survey() {
const survey: IFormInputs = [ const survey: IFormInputs = [
{ {
@ -470,12 +470,12 @@ export default mixins(showMessage, workflowHelpers).extend({
...values, ...values,
version: SURVEY_VERSION, version: SURVEY_VERSION,
personalization_survey_submitted_at: new Date().toISOString(), personalization_survey_submitted_at: new Date().toISOString(),
personalization_survey_n8n_version: this.$store.getters.versionCli, personalization_survey_n8n_version: this.rootStore.versionCli,
}; };
this.$externalHooks().run('personalizationModal.onSubmit', survey); this.$externalHooks().run('personalizationModal.onSubmit', survey);
await this.$store.dispatch('users/submitPersonalizationSurvey', survey); await this.usersStore.submitPersonalizationSurvey(survey as IPersonalizationSurveyAnswersV3);
if (Object.keys(values).length === 0) { if (Object.keys(values).length === 0) {
this.closeDialog(); this.closeDialog();
@ -490,8 +490,8 @@ export default mixins(showMessage, workflowHelpers).extend({
this.$data.isSaving = false; this.$data.isSaving = false;
}, },
async fetchOnboardingPrompt() { async fetchOnboardingPrompt() {
if (this.isOnboardingCallPromptFeatureEnabled && getAccountAge(this.currentUser) <= ONBOARDING_PROMPT_TIMEBOX) { if (this.settingsStore.onboardingCallPromptEnabled && getAccountAge(this.usersStore.currentUser || {} as IUser) <= ONBOARDING_PROMPT_TIMEBOX) {
const onboardingResponse = await this.$store.dispatch('ui/getNextOnboardingPrompt'); const onboardingResponse = await this.uiStore.getNextOnboardingPrompt();
const promptTimeout = onboardingResponse.toast_sequence_number === 1 ? FIRST_ONBOARDING_PROMPT_TIMEOUT : 1000; const promptTimeout = onboardingResponse.toast_sequence_number === 1 ? FIRST_ONBOARDING_PROMPT_TIMEOUT : 1000;
if (onboardingResponse.title && onboardingResponse.description) { if (onboardingResponse.title && onboardingResponse.description) {
@ -509,7 +509,7 @@ export default mixins(showMessage, workflowHelpers).extend({
title: onboardingResponse.title, title: onboardingResponse.title,
description: onboardingResponse.description, description: onboardingResponse.description,
}); });
this.$store.commit('ui/openModal', ONBOARDING_CALL_SIGNUP_MODAL_KEY, {root: true}); this.uiStore.openModal(ONBOARDING_CALL_SIGNUP_MODAL_KEY);
}, },
}); });
}, promptTimeout); }, promptTimeout);

View file

@ -1,6 +1,6 @@
<template> <template>
<span> <span>
<div class="push-connection-lost primary-color" v-if="!pushConnectionActive"> <div class="push-connection-lost primary-color" v-if="!rootStore.pushConnectionActive">
<n8n-tooltip placement="bottom-end" > <n8n-tooltip placement="bottom-end" >
<div slot="content" v-html="$locale.baseText('pushConnectionTracker.cannotConnectToServer')"></div> <div slot="content" v-html="$locale.baseText('pushConnectionTracker.cannotConnectToServer')"></div>
<span> <span>
@ -13,13 +13,16 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { useRootStore } from "@/stores/n8nRootStore";
import { mapStores } from "pinia";
import Vue from "vue"; import Vue from "vue";
import { mapGetters } from "vuex";
export default Vue.extend({ export default Vue.extend({
name: "PushConnectionTracker", name: "PushConnectionTracker",
computed: { computed: {
...mapGetters(["pushConnectionActive"]), ...mapStores(
useRootStore,
),
}, },
}); });
</script> </script>

View file

@ -172,6 +172,12 @@ import { workflowHelpers } from '../mixins/workflowHelpers';
import { nodeHelpers } from '../mixins/nodeHelpers'; import { nodeHelpers } from '../mixins/nodeHelpers';
import { getAppNameFromNodeName } from '../helpers'; import { getAppNameFromNodeName } from '../helpers';
import { isResourceLocatorValue } from '@/typeGuards'; import { isResourceLocatorValue } from '@/typeGuards';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { useRootStore } from '@/stores/n8nRootStore';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
interface IResourceLocatorQuery { interface IResourceLocatorQuery {
results: INodeListSearchItems[]; results: INodeListSearchItems[];
@ -248,7 +254,6 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
}, },
data() { data() {
return { return {
mainPanelMutationSubscription: () => {},
showResourceDropdown: false, showResourceDropdown: false,
searchFilter: '', searchFilter: '',
cachedResponses: {} as { [key: string]: IResourceLocatorQuery }, cachedResponses: {} as { [key: string]: IResourceLocatorQuery },
@ -257,13 +262,20 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
}; };
}, },
computed: { computed: {
...mapStores(
useNodeTypesStore,
useNDVStore,
useRootStore,
useUIStore,
useWorkflowsStore,
),
appName(): string { appName(): string {
if (!this.node) { if (!this.node) {
return ''; return '';
} }
const nodeType = this.$store.getters['nodeTypes/getNodeType'](this.node.type); const nodeType = this.nodeTypesStore.getNodeType(this.node.type);
return getAppNameFromNodeName(nodeType.displayName); return getAppNameFromNodeName(nodeType?.displayName || '');
}, },
selectedMode(): string { selectedMode(): string {
if (typeof this.value !== 'object') { // legacy mode if (typeof this.value !== 'object') { // legacy mode
@ -280,7 +292,7 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
return this.selectedMode === 'list'; return this.selectedMode === 'list';
}, },
hasCredential(): boolean { hasCredential(): boolean {
const node = this.$store.getters['ndv/activeNode'] as INodeUi | null; const node = this.ndvStore.activeNode;
if (!node) { if (!node) {
return false; return false;
} }
@ -425,24 +437,21 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
mounted() { mounted() {
this.$on('refreshList', this.refreshList); this.$on('refreshList', this.refreshList);
window.addEventListener('resize', this.setWidth); window.addEventListener('resize', this.setWidth);
this.mainPanelMutationSubscription = this.$store.subscribe(this.setWidthOnMainPanelResize); useNDVStore().$subscribe((mutation, state) => {
// Update the width when main panel dimension change
this.setWidth();
});
setTimeout(() => { setTimeout(() => {
this.setWidth(); this.setWidth();
}, 0); }, 0);
}, },
beforeDestroy() { beforeDestroy() {
// Unsubscribe
this.mainPanelMutationSubscription();
window.removeEventListener('resize', this.setWidth); window.removeEventListener('resize', this.setWidth);
}, },
methods: { methods: {
setWidth() { setWidth() {
this.width = (this.$refs.container as HTMLElement).offsetWidth; this.width = (this.$refs.container as HTMLElement).offsetWidth;
}, },
setWidthOnMainPanelResize(mutation: { type: string }) {
// Update the width when main panel dimension change
if(mutation.type === 'ndv/setMainPanelDimensions') this.setWidth();
},
getLinkAlt(entity: string) { getLinkAlt(entity: string) {
if (this.selectedMode === 'list' && entity) { if (this.selectedMode === 'list' && entity) {
return this.$locale.baseText('resourceLocator.openSpecificResource', { interpolate: { entity, appName: this.appName } }); return this.$locale.baseText('resourceLocator.openSpecificResource', { interpolate: { entity, appName: this.appName } });
@ -480,7 +489,7 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
return parameter.typeOptions[argumentName]; return parameter.typeOptions[argumentName];
}, },
openCredential(): void { openCredential(): void {
const node = this.$store.getters['ndv/activeNode'] as INodeUi | null; const node = this.ndvStore.activeNode;
if (!node || !node.credentials) { if (!node || !node.credentials) {
return; return;
} }
@ -489,7 +498,7 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
return; return;
} }
const id = node.credentials[credentialKey].id; const id = node.credentials[credentialKey].id;
this.$store.dispatch('ui/openExistingCredential', { id }); this.uiStore.openExistingCredential(id);
}, },
findModeByName(name: string): INodePropertyMode | null { findModeByName(name: string): INodePropertyMode | null {
if (this.parameter.modes) { if (this.parameter.modes) {
@ -533,8 +542,8 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
}, },
trackEvent(event: string, params?: {[key: string]: string}): void { trackEvent(event: string, params?: {[key: string]: string}): void {
this.$telemetry.track(event, { this.$telemetry.track(event, {
instance_id: this.$store.getters.instanceId, instance_id: this.rootStore.instanceId,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
node_type: this.node && this.node.type, node_type: this.node && this.node.type,
resource: this.node && this.node.parameters && this.node.parameters.resource, resource: this.node && this.node.parameters && this.node.parameters.resource,
operation: this.node && this.node.parameters && this.node.parameters.operation, operation: this.node && this.node.parameters && this.node.parameters.operation,
@ -618,10 +627,7 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
...(paginationToken ? { paginationToken } : {}), ...(paginationToken ? { paginationToken } : {}),
}; };
const response: INodeListSearchResult = await this.$store.dispatch( const response = await this.nodeTypesStore.getResourceLocatorResults(requestParams);
'nodeTypes/getResourceLocatorResults',
requestParams,
);
this.setResponse(paramsKey, { this.setResponse(paramsKey, {
results: (cachedResponse ? cachedResponse.results : []).concat(response.results), results: (cachedResponse ? cachedResponse.results : []).concat(response.results),

View file

@ -145,7 +145,7 @@
:value="editMode.value" :value="editMode.value"
:options="{ scrollBeyondLastLine: false }" :options="{ scrollBeyondLastLine: false }"
type="json" type="json"
@input="$store.commit('ndv/setOutputPanelEditModeValue', $event)" @input="ndvStore.setOutputPanelEditModeValue($event)"
/> />
</div> </div>
<div :class="$style['edit-mode-footer']"> <div :class="$style['edit-mode-footer']">
@ -344,6 +344,7 @@ import {
IBinaryDisplayData, IBinaryDisplayData,
IExecutionResponse, IExecutionResponse,
INodeUi, INodeUi,
INodeUpdatePropertiesInformation,
IRunDataDisplayMode, IRunDataDisplayMode,
ITab, ITab,
} from '@/Interface'; } from '@/Interface';
@ -373,6 +374,10 @@ import { clearJsonKey, executionDataToJson, stringSizeInBytes } from './helpers'
import RunDataTable from './RunDataTable.vue'; import RunDataTable from './RunDataTable.vue';
import RunDataJson from '@/components/RunDataJson.vue'; import RunDataJson from '@/components/RunDataJson.vue';
import { isEmpty } from '@/utils'; import { isEmpty } from '@/utils';
import { useWorkflowsStore } from "@/stores/workflows";
import { mapStores } from "pinia";
import { useNDVStore } from "@/stores/ndv";
import { useNodeTypesStore } from "@/stores/nodeTypes";
export type EnterEditModeArgs = { export type EnterEditModeArgs = {
origin: 'editIconButton' | 'insertTestDataLink', origin: 'editIconButton' | 'insertTestDataLink',
@ -475,8 +480,8 @@ export default mixins(
this.showPinDataDiscoveryTooltip(this.jsonData); this.showPinDataDiscoveryTooltip(this.jsonData);
} }
} }
this.$store.commit('ndv/setNDVBranchIndex', { this.ndvStore.setNDVBranchIndex({
pane: this.paneType, pane: this.paneType as "input" | "output",
branchIndex: this.currentOutputIndex, branchIndex: this.currentOutputIndex,
}); });
}, },
@ -486,8 +491,13 @@ export default mixins(
this.eventBus.$off('data-unpinning', this.onDataUnpinning); this.eventBus.$off('data-unpinning', this.onDataUnpinning);
}, },
computed: { computed: {
activeNode(): INodeUi { ...mapStores(
return this.$store.getters['ndv/activeNode']; useNodeTypesStore,
useNDVStore,
useWorkflowsStore,
),
activeNode(): INodeUi | null {
return this.ndvStore.activeNode;
}, },
dataPinningDocsUrl(): string { dataPinningDocsUrl(): string {
return DATA_PINNING_DOCS_URL; return DATA_PINNING_DOCS_URL;
@ -496,19 +506,19 @@ export default mixins(
return DATA_EDITING_DOCS_URL; return DATA_EDITING_DOCS_URL;
}, },
displayMode(): IRunDataDisplayMode { displayMode(): IRunDataDisplayMode {
return this.$store.getters['ndv/getPanelDisplayMode'](this.paneType); return this.ndvStore.getPanelDisplayMode(this.paneType as "input" | "output");
}, },
node(): INodeUi | null { node(): INodeUi | null {
return (this.nodeUi as INodeUi | null) || null; return (this.nodeUi as INodeUi | null) || null;
}, },
nodeType (): INodeTypeDescription | null { nodeType (): INodeTypeDescription | null {
if (this.node) { if (this.node) {
return this.$store.getters['nodeTypes/getNodeType'](this.node.type, this.node.typeVersion); return this.nodeTypesStore.getNodeType(this.node.type, this.node.typeVersion);
} }
return null; return null;
}, },
isTriggerNode (): boolean { isTriggerNode (): boolean {
return this.$store.getters['nodeTypes/isTriggerNode'](this.node.type); return this.nodeTypesStore.isTriggerNode(this.node.type);
}, },
canPinData (): boolean { canPinData (): boolean {
return !this.isPaneTypeInput && return !this.isPaneTypeInput &&
@ -532,7 +542,7 @@ export default mixins(
return Boolean(!this.isExecuting && this.node && (this.workflowRunData && this.workflowRunData.hasOwnProperty(this.node.name) || this.hasPinData)); return Boolean(!this.isExecuting && this.node && (this.workflowRunData && this.workflowRunData.hasOwnProperty(this.node.name) || this.hasPinData));
}, },
subworkflowExecutionError(): Error | null { subworkflowExecutionError(): Error | null {
return this.$store.getters.subworkflowExecutionError; return this.workflowsStore.subWorkflowExecutionError;
}, },
hasSubworkflowExecutionError(): boolean { hasSubworkflowExecutionError(): boolean {
return Boolean(this.subworkflowExecutionError); return Boolean(this.subworkflowExecutionError);
@ -541,7 +551,7 @@ export default mixins(
return Boolean(this.node && this.workflowRunData && this.workflowRunData[this.node.name] && this.workflowRunData[this.node.name][this.runIndex] && this.workflowRunData[this.node.name][this.runIndex].error); return Boolean(this.node && this.workflowRunData && this.workflowRunData[this.node.name] && this.workflowRunData[this.node.name][this.runIndex] && this.workflowRunData[this.node.name][this.runIndex].error);
}, },
workflowExecution (): IExecutionResponse | null { workflowExecution (): IExecutionResponse | null {
return this.$store.getters.getWorkflowExecution; return this.workflowsStore.getWorkflowExecution;
}, },
workflowRunData (): IRunData | null { workflowRunData (): IRunData | null {
if (this.workflowExecution === null) { if (this.workflowExecution === null) {
@ -680,7 +690,7 @@ export default mixins(
editMode(): { enabled: boolean; value: string; } { editMode(): { enabled: boolean; value: string; } {
return this.isPaneTypeInput return this.isPaneTypeInput
? { enabled: false, value: '' } ? { enabled: false, value: '' }
: this.$store.getters['ndv/outputPanelEditMode']; : this.ndvStore.outputPanelEditMode;
}, },
isPaneTypeInput(): boolean { isPaneTypeInput(): boolean {
return this.paneType === 'input'; return this.paneType === 'input';
@ -700,9 +710,9 @@ export default mixins(
}, },
onClickDataPinningDocsLink() { onClickDataPinningDocsLink() {
this.$telemetry.track('User clicked ndv link', { this.$telemetry.track('User clicked ndv link', {
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId, session_id: this.sessionId,
node_type: this.activeNode.type, node_type: this.activeNode?.type,
pane: 'output', pane: 'output',
type: 'data-pinning-docs', type: 'data-pinning-docs',
}); });
@ -742,11 +752,11 @@ export default mixins(
? inputData ? inputData
: TEST_PIN_DATA; : TEST_PIN_DATA;
this.$store.commit('ndv/setOutputPanelEditModeEnabled', true); this.ndvStore.setOutputPanelEditModeEnabled(true);
this.$store.commit('ndv/setOutputPanelEditModeValue', JSON.stringify(data, null, 2)); this.ndvStore.setOutputPanelEditModeValue(JSON.stringify(data, null, 2));
this.$telemetry.track('User opened ndv edit state', { this.$telemetry.track('User opened ndv edit state', {
node_type: this.activeNode.type, node_type: this.activeNode?.type,
click_type: origin === 'editIconButton' ? 'button' : 'link', click_type: origin === 'editIconButton' ? 'button' : 'link',
session_id: this.sessionId, session_id: this.sessionId,
run_index: this.runIndex, run_index: this.runIndex,
@ -756,8 +766,8 @@ export default mixins(
}); });
}, },
onClickCancelEdit() { onClickCancelEdit() {
this.$store.commit('ndv/setOutputPanelEditModeEnabled', false); this.ndvStore.setOutputPanelEditModeEnabled(false);
this.$store.commit('ndv/setOutputPanelEditModeValue', ''); this.ndvStore.setOutputPanelEditModeValue('');
this.onExitEditMode({ type: 'cancel' }); this.onExitEditMode({ type: 'cancel' });
}, },
onClickSaveEdit() { onClickSaveEdit() {
@ -775,8 +785,8 @@ export default mixins(
return; return;
} }
this.$store.commit('ndv/setOutputPanelEditModeEnabled', false); this.ndvStore.setOutputPanelEditModeEnabled(false);
this.$store.commit('pinData', { node: this.node, data: clearJsonKey(value) }); this.workflowsStore.pinData({ node: this.node, data: clearJsonKey(value) as INodeExecutionData[] });
this.onDataPinningSuccess({ source: 'save-edit' }); this.onDataPinningSuccess({ source: 'save-edit' });
@ -784,7 +794,7 @@ export default mixins(
}, },
onExitEditMode({ type }: { type: 'save' | 'cancel' }) { onExitEditMode({ type }: { type: 'save' | 'cancel' }) {
this.$telemetry.track('User closed ndv edit state', { this.$telemetry.track('User closed ndv edit state', {
node_type: this.activeNode.type, node_type: this.activeNode?.type,
session_id: this.sessionId, session_id: this.sessionId,
run_index: this.runIndex, run_index: this.runIndex,
view: this.displayMode, view: this.displayMode,
@ -795,7 +805,7 @@ export default mixins(
{ source }: { source: 'banner-link' | 'pin-icon-click' | 'unpin-and-execute-modal' }, { source }: { source: 'banner-link' | 'pin-icon-click' | 'unpin-and-execute-modal' },
) { ) {
this.$telemetry.track('User unpinned ndv data', { this.$telemetry.track('User unpinned ndv data', {
node_type: this.activeNode.type, node_type: this.activeNode?.type,
session_id: this.sessionId, session_id: this.sessionId,
run_index: this.runIndex, run_index: this.runIndex,
source, source,
@ -849,11 +859,11 @@ export default mixins(
if (this.hasPinData) { if (this.hasPinData) {
this.onDataUnpinning({ source }); this.onDataUnpinning({ source });
this.$store.commit('unpinData', { node: this.node }); this.workflowsStore.unpinData({ node: this.node });
return; return;
} }
const data = executionDataToJson(this.rawInputData); const data = executionDataToJson(this.rawInputData) as INodeExecutionData[];
if (!this.isValidPinDataSize(data)) { if (!this.isValidPinDataSize(data)) {
this.onDataPinningError({ errorType: 'data-too-large', source: 'pin-icon-click' }); this.onDataPinningError({ errorType: 'data-too-large', source: 'pin-icon-click' });
@ -862,7 +872,7 @@ export default mixins(
this.onDataPinningSuccess({ source: 'pin-icon-click' }); this.onDataPinningSuccess({ source: 'pin-icon-click' });
this.$store.commit('pinData', { node: this.node, data }); this.workflowsStore.pinData({ node: this.node, data });
if (this.maxRunIndex > 0) { if (this.maxRunIndex > 0) {
this.$showToast({ this.$showToast({
@ -898,7 +908,7 @@ export default mixins(
this.showData = true; this.showData = true;
this.$telemetry.track('User clicked ndv button', { this.$telemetry.track('User clicked ndv button', {
node_type: this.activeNode.type, node_type: this.activeNode.type,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId, session_id: this.sessionId,
pane: this.paneType, pane: this.paneType,
type: 'showTooMuchData', type: 'showTooMuchData',
@ -912,8 +922,8 @@ export default mixins(
}, },
onCurrentPageChange() { onCurrentPageChange() {
this.$telemetry.track('User changed ndv page', { this.$telemetry.track('User changed ndv page', {
node_type: this.activeNode.type, node_type: this.activeNode?.type,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId, session_id: this.sessionId,
pane: this.paneType, pane: this.paneType,
page_selected: this.currentPage, page_selected: this.currentPage,
@ -929,8 +939,8 @@ export default mixins(
} }
this.$telemetry.track('User changed ndv page size', { this.$telemetry.track('User changed ndv page size', {
node_type: this.activeNode.type, node_type: this.activeNode?.type,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId, session_id: this.sessionId,
pane: this.paneType, pane: this.paneType,
page_selected: this.currentPage, page_selected: this.currentPage,
@ -940,7 +950,7 @@ export default mixins(
}, },
onDisplayModeChange(displayMode: IRunDataDisplayMode) { onDisplayModeChange(displayMode: IRunDataDisplayMode) {
const previous = this.displayMode; const previous = this.displayMode;
this.$store.commit('ndv/setPanelDisplayMode', {pane: this.paneType, mode: displayMode}); this.ndvStore.setPanelDisplayMode({pane: this.paneType as "input" | "output", mode: displayMode});
const dataContainer = this.$refs.dataContainer; const dataContainer = this.$refs.dataContainer;
if (dataContainer) { if (dataContainer) {
@ -958,7 +968,7 @@ export default mixins(
previous_view: previous, previous_view: previous,
new_view: displayMode, new_view: displayMode,
node_type: this.activeNode.type, node_type: this.activeNode.type,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId, session_id: this.sessionId,
pane: this.paneType, pane: this.paneType,
}); });
@ -1012,10 +1022,10 @@ export default mixins(
this.refreshDataSize(); this.refreshDataSize();
this.closeBinaryDataDisplay(); this.closeBinaryDataDisplay();
if (this.binaryData.length > 0) { if (this.binaryData.length > 0) {
this.$store.commit('ndv/setPanelDisplayMode', {pane: this.paneType, mode: 'binary'}); this.ndvStore.setPanelDisplayMode({pane: this.paneType as "input" | "output", mode: 'binary'});
} }
else if (this.displayMode === 'binary') { else if (this.displayMode === 'binary') {
this.$store.commit('ndv/setPanelDisplayMode', {pane: this.paneType, mode: 'table'}); this.ndvStore.setPanelDisplayMode({pane: this.paneType as "input" | "output", mode: 'table'});
} }
}, },
closeBinaryDataDisplay () { closeBinaryDataDisplay () {
@ -1023,7 +1033,7 @@ export default mixins(
this.binaryDataDisplayData = null; this.binaryDataDisplayData = null;
}, },
clearExecutionData () { clearExecutionData () {
this.$store.commit('setWorkflowExecutionData', null); this.workflowsStore.setWorkflowExecutionData(null);
this.updateNodesExecutionIssues(); this.updateNodesExecutionIssues();
}, },
isDownloadable (index: number, key: string): boolean { isDownloadable (index: number, key: string): boolean {
@ -1093,15 +1103,15 @@ export default mixins(
name: this.node.name, name: this.node.name,
properties: { properties: {
disabled: !this.node.disabled, disabled: !this.node.disabled,
}, } as IDataObject,
}; } as INodeUpdatePropertiesInformation;
this.$store.commit('updateNodeProperties', updateInformation); this.workflowsStore.updateNodeProperties(updateInformation);
} }
}, },
goToErroredNode() { goToErroredNode() {
if (this.node) { if (this.node) {
this.$store.commit('ndv/setActiveNodeName', this.node.name); this.ndvStore.activeNodeName = this.node.name;
} }
}, },
}, },
@ -1112,7 +1122,7 @@ export default mixins(
inputData:{ inputData:{
handler(data: INodeExecutionData[]) { handler(data: INodeExecutionData[]) {
if(this.paneType && data){ if(this.paneType && data){
this.$store.commit('ndv/setNDVPanelDataIsEmpty', { panel: this.paneType, isEmpty: data.every(item => isEmpty(item.json)) }); this.ndvStore.setNDVPanelDataIsEmpty({ panel: this.paneType as "input" | "output", isEmpty: data.every(item => isEmpty(item.json)) });
} }
}, },
immediate: true, immediate: true,
@ -1135,8 +1145,8 @@ export default mixins(
} }
}, },
currentOutputIndex(branchIndex: number) { currentOutputIndex(branchIndex: number) {
this.$store.commit('ndv/setNDVBranchIndex', { this.ndvStore.setNDVBranchIndex({
pane: this.paneType, pane: this.paneType as "input" | "output",
branchIndex, branchIndex,
}); });
}, },

View file

@ -66,6 +66,8 @@ import { convertPath, executionDataToJson, isString, isStringNumber } from "@/co
import { INodeUi } from "@/Interface"; import { INodeUi } from "@/Interface";
import { shorten } from './helpers'; import { shorten } from './helpers';
import { externalHooks } from "@/components/mixins/externalHooks"; import { externalHooks } from "@/components/mixins/externalHooks";
import { mapStores } from "pinia";
import { useNDVStore } from "@/stores/ndv";
const runDataJsonActions = () => import('@/components/RunDataJsonActions.vue'); const runDataJsonActions = () => import('@/components/RunDataJsonActions.vue');
@ -137,6 +139,9 @@ export default mixins(externalHooks).extend({
} }
}, },
computed: { computed: {
...mapStores(
useNDVStore,
),
jsonData(): IDataObject[] { jsonData(): IDataObject[] {
return executionDataToJson(this.inputData as INodeExecutionData[]); return executionDataToJson(this.inputData as INodeExecutionData[]);
}, },
@ -165,13 +170,13 @@ export default mixins(externalHooks).extend({
this.draggingPath = el.dataset.path; this.draggingPath = el.dataset.path;
} }
this.$store.commit('ndv/resetMappingTelemetry'); this.ndvStore.resetMappingTelemetry();
}, },
onDragEnd(el: HTMLElement) { onDragEnd(el: HTMLElement) {
this.draggingPath = null; this.draggingPath = null;
setTimeout(() => { setTimeout(() => {
const mappingTelemetry = this.$store.getters['ndv/mappingTelemetry']; const mappingTelemetry = this.ndvStore.mappingTelemetry;
const telemetryPayload = { const telemetryPayload = {
src_node_type: this.node.type, src_node_type: this.node.type,
src_field_name: el.dataset.name || '', src_field_name: el.dataset.name || '',

View file

@ -35,6 +35,9 @@ import { pinData } from "@/components/mixins/pinData";
import { nodeHelpers } from "@/components/mixins/nodeHelpers"; import { nodeHelpers } from "@/components/mixins/nodeHelpers";
import { genericHelpers } from "@/components/mixins/genericHelpers"; import { genericHelpers } from "@/components/mixins/genericHelpers";
import { clearJsonKey, convertPath, executionDataToJson } from "@/components/helpers"; import { clearJsonKey, convertPath, executionDataToJson } from "@/components/helpers";
import { mapStores } from "pinia";
import { useWorkflowsStore } from "@/stores/workflows";
import { useNDVStore } from "@/stores/ndv";
type JsonPathData = { type JsonPathData = {
path: string; path: string;
@ -83,8 +86,12 @@ export default mixins(
}, },
}, },
computed: { computed: {
activeNode(): INodeUi { ...mapStores(
return this.$store.getters['ndv/activeNode']; useNDVStore,
useWorkflowsStore,
),
activeNode(): INodeUi | null {
return this.ndvStore.activeNode;
}, },
normalisedJsonPath(): string { normalisedJsonPath(): string {
const isNotSelected = this.selectedJsonPath === nonExistingJsonPath; const isNotSelected = this.selectedJsonPath === nonExistingJsonPath;
@ -189,7 +196,7 @@ export default mixins(
run_index: this.runIndex, run_index: this.runIndex,
view: 'json', view: 'json',
copy_type: copyType, copy_type: copyType,
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
pane: this.paneType, pane: this.paneType,
in_execution_log: this.isReadOnly, in_execution_log: this.isReadOnly,
}); });

View file

@ -152,14 +152,18 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { INodeUi, IRootState, ITableData, NDVState } from '@/Interface'; /* eslint-disable prefer-spread */
import { INodeUi, ITableData, NDVState } from '@/Interface';
import { getPairedItemId } from '@/pairedItemUtils'; import { getPairedItemId } from '@/pairedItemUtils';
import Vue, { PropType } from 'vue'; import Vue, { PropType } from 'vue';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { GenericValue, IDataObject, INodeExecutionData } from 'n8n-workflow'; import { GenericValue, IDataObject, INodeExecutionData } from 'n8n-workflow';
import Draggable from '@/components/Draggable.vue'; import Draggable from './Draggable.vue';
import { shorten } from '@/components/helpers'; import { shorten } from './helpers';
import { externalHooks } from '@/components/mixins/externalHooks'; import { externalHooks } from './mixins/externalHooks';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
const MAX_COLUMNS_LIMIT = 40; const MAX_COLUMNS_LIMIT = 40;
@ -217,11 +221,15 @@ export default mixins(externalHooks).extend({
} }
}, },
computed: { computed: {
...mapStores(
useNDVStore,
useWorkflowsStore,
),
hoveringItem(): NDVState['hoveringItem'] { hoveringItem(): NDVState['hoveringItem'] {
return this.$store.getters['ndv/hoveringItem']; return this.ndvStore.hoveringItem;
}, },
pairedItemMappings(): IRootState['workflowExecutionPairedItemMappings'] { pairedItemMappings(): {[itemId: string]: Set<string>} {
return this.$store.getters['workflowExecutionPairedItemMappings']; return this.workflowsStore.workflowExecutionPairedItemMappings;
}, },
tableData(): ITableData { tableData(): ITableData {
return this.convertToTable(this.inputData); return this.convertToTable(this.inputData);
@ -360,8 +368,7 @@ export default mixins(externalHooks).extend({
}, },
onDragStart() { onDragStart() {
this.draggedColumn = true; this.draggedColumn = true;
this.ndvStore.resetMappingTelemetry();
this.$store.commit('ndv/resetMappingTelemetry');
}, },
onCellDragStart(el: HTMLElement) { onCellDragStart(el: HTMLElement) {
if (el && el.dataset.value) { if (el && el.dataset.value) {
@ -384,7 +391,7 @@ export default mixins(externalHooks).extend({
}, },
onDragEnd(column: string, src: string, depth = '0') { onDragEnd(column: string, src: string, depth = '0') {
setTimeout(() => { setTimeout(() => {
const mappingTelemetry = this.$store.getters['ndv/mappingTelemetry']; const mappingTelemetry = this.ndvStore.mappingTelemetry;
const telemetryPayload = { const telemetryPayload = {
src_node_type: this.node.type, src_node_type: this.node.type,
src_field_name: column, src_field_name: column,

View file

@ -12,7 +12,7 @@
<template #menuSuffix> <template #menuSuffix>
<div :class="$style.versionContainer"> <div :class="$style.versionContainer">
<n8n-link @click="onVersionClick" size="small"> <n8n-link @click="onVersionClick" size="small">
{{ $locale.baseText('settings.version') }} {{ versionCli }} {{ $locale.baseText('settings.version') }} {{ rootStore.versionCli }}
</n8n-link> </n8n-link>
</div> </div>
</template> </template>
@ -29,6 +29,10 @@ import { pushConnection } from "@/components/mixins/pushConnection";
import { IFakeDoor } from '@/Interface'; import { IFakeDoor } from '@/Interface';
import { IMenuItem } from 'n8n-design-system'; import { IMenuItem } from 'n8n-design-system';
import { BaseTextKey } from '@/plugins/i18n'; import { BaseTextKey } from '@/plugins/i18n';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useSettingsStore } from '@/stores/settings';
import { useRootStore } from '@/stores/n8nRootStore';
export default mixins( export default mixins(
userHelpers, userHelpers,
@ -36,9 +40,13 @@ export default mixins(
).extend({ ).extend({
name: 'SettingsSidebar', name: 'SettingsSidebar',
computed: { computed: {
...mapGetters('settings', ['versionCli']), ...mapStores(
useRootStore,
useSettingsStore,
useUIStore,
),
settingsFakeDoorFeatures(): IFakeDoor[] { settingsFakeDoorFeatures(): IFakeDoor[] {
return this.$store.getters['ui/getFakeDoorByLocation']('settings'); return this.uiStore.getFakeDoorByLocation('settings');
}, },
sidebarMenuItems(): IMenuItem[] { sidebarMenuItems(): IMenuItem[] {
@ -113,13 +121,13 @@ export default mixins(
return this.canUserAccessRouteByName(VIEWS.API_SETTINGS); return this.canUserAccessRouteByName(VIEWS.API_SETTINGS);
}, },
onVersionClick() { onVersionClick() {
this.$store.dispatch('ui/openModal', ABOUT_MODAL_KEY); this.uiStore.openModal(ABOUT_MODAL_KEY);
}, },
onReturn() { onReturn() {
this.$router.push({name: VIEWS.HOMEPAGE}); this.$router.push({name: VIEWS.HOMEPAGE});
}, },
openUpdatesPanel() { openUpdatesPanel() {
this.$store.dispatch('ui/openModal', VERSIONS_MODAL_KEY); this.uiStore.openModal(VERSIONS_MODAL_KEY);
}, },
async handleSelect (key: string) { async handleSelect (key: string) {
switch (key) { switch (key) {

View file

@ -50,12 +50,18 @@ import { nodeBase } from '@/components/mixins/nodeBase';
import { nodeHelpers } from '@/components/mixins/nodeHelpers'; import { nodeHelpers } from '@/components/mixins/nodeHelpers';
import { workflowHelpers } from '@/components/mixins/workflowHelpers'; import { workflowHelpers } from '@/components/mixins/workflowHelpers';
import { getStyleTokenValue, isNumber, isString } from './helpers'; import { getStyleTokenValue, isNumber, isString } from './helpers';
import { INodeUi, XYPosition } from '@/Interface'; import { INodeUi, INodeUpdatePropertiesInformation, IUpdateInformation, XYPosition } from '@/Interface';
import { import {
IDataObject,
INodeTypeDescription, INodeTypeDescription,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { QUICKSTART_NOTE_NAME } from '@/constants'; import { QUICKSTART_NOTE_NAME } from '@/constants';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).extend({ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).extend({
name: 'Sticky', name: 'Sticky',
@ -68,6 +74,12 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
}, },
}, },
computed: { computed: {
...mapStores(
useNodeTypesStore,
useNDVStore,
useUIStore,
useWorkflowsStore,
),
defaultText (): string { defaultText (): string {
if (!this.nodeType) { if (!this.nodeType) {
return ''; return '';
@ -78,13 +90,13 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
return content && isString(content.default) ? content.default : ''; return content && isString(content.default) ? content.default : '';
}, },
isSelected (): boolean { isSelected (): boolean {
return this.$store.getters.getSelectedNodes.find((node: INodeUi) => node.name === this.data.name); return this.uiStore.getSelectedNodes.find((node: INodeUi) => node.name === this.data.name) !== undefined;
}, },
nodeType (): INodeTypeDescription | null { nodeType (): INodeTypeDescription | null {
return this.data && this.$store.getters['nodeTypes/getNodeType'](this.data.type, this.data.typeVersion); return this.data && this.nodeTypesStore.getNodeType(this.data.type, this.data.typeVersion);
}, },
node (): INodeUi | undefined { // same as this.data but reactive.. node (): INodeUi | null { // same as this.data but reactive..
return this.$store.getters.nodesByName[this.name] as INodeUi | undefined; return this.workflowsStore.getNodeByName(this.name);
}, },
position (): XYPosition { position (): XYPosition {
if (this.node) { if (this.node) {
@ -124,7 +136,7 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
return !(this.hideActions || this.isReadOnly || this.workflowRunning || this.isResizing); return !(this.hideActions || this.isReadOnly || this.workflowRunning || this.isResizing);
}, },
workflowRunning (): boolean { workflowRunning (): boolean {
return this.$store.getters.isActionActive('workflowRunning'); return this.uiStore.isActionActive('workflowRunning');
}, },
}, },
data () { data () {
@ -142,10 +154,10 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
}, },
onEdit(edit: boolean) { onEdit(edit: boolean) {
if (edit && !this.isActive && this.node) { if (edit && !this.isActive && this.node) {
this.$store.commit('ndv/setActiveNodeName', this.node.name); this.ndvStore.activeNodeName = this.node.name;
} }
else if (this.isActive && !edit) { else if (this.isActive && !edit) {
this.$store.commit('ndv/setActiveNodeName', null); this.ndvStore.activeNodeName = null;
} }
}, },
onMarkdownClick ( link:HTMLAnchorElement, event: Event ) { onMarkdownClick ( link:HTMLAnchorElement, event: Event ) {
@ -191,12 +203,13 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
width: isNumber(params.width) ? params.width : this.node.parameters.width, width: isNumber(params.width) ? params.width : this.node.parameters.width,
}; };
const updateInformation = { const updateInformation: IUpdateInformation = {
key: this.node.id,
name: this.node.name, name: this.node.name,
value: nodeParameters, value: nodeParameters,
}; };
this.$store.commit('setNodeParameters', updateInformation); this.workflowsStore.setNodeParameters(updateInformation);
} }
}, },
setPosition(position: XYPosition) { setPosition(position: XYPosition) {
@ -204,14 +217,14 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
return; return;
} }
const updateInformation = { const updateInformation: INodeUpdatePropertiesInformation = {
name: this.node.name, name: this.node.name,
properties: { properties: {
position, position: { position },
}, },
}; };
this.$store.commit('updateNodeProperties', updateInformation); this.workflowsStore.updateNodeProperties(updateInformation);
}, },
touchStart () { touchStart () {
if (this.isTouchDevice === true && this.isMacOs === false && this.isTouchActive === false) { if (this.isTouchDevice === true && this.isMacOs === false && this.isTouchActive === false) {

View file

@ -60,6 +60,8 @@ import { ITag } from "@/Interface";
import { MAX_TAG_NAME_LENGTH, TAGS_MANAGER_MODAL_KEY } from "@/constants"; import { MAX_TAG_NAME_LENGTH, TAGS_MANAGER_MODAL_KEY } from "@/constants";
import { showMessage } from "@/components/mixins/showMessage"; import { showMessage } from "@/components/mixins/showMessage";
import { mapStores } from "pinia";
import { useUIStore } from "@/stores/ui";
const MANAGE_KEY = "__manage"; const MANAGE_KEY = "__manage";
const CREATE_KEY = "__create"; const CREATE_KEY = "__create";
@ -113,6 +115,7 @@ export default mixins(showMessage).extend({
this.$store.dispatch("tags/fetchAll"); this.$store.dispatch("tags/fetchAll");
}, },
computed: { computed: {
...mapStores(useUIStore),
...mapGetters("tags", ["allTags", "isLoading", "hasTags"]), ...mapGetters("tags", ["allTags", "isLoading", "hasTags"]),
options(): ITag[] { options(): ITag[] {
return this.allTags return this.allTags
@ -156,7 +159,7 @@ export default mixins(showMessage).extend({
); );
if (ops === MANAGE_KEY) { if (ops === MANAGE_KEY) {
this.$data.filter = ""; this.$data.filter = "";
this.$store.dispatch("ui/openModal", TAGS_MANAGER_MODAL_KEY); this.uiStore.openModal(TAGS_MANAGER_MODAL_KEY);
} else if (ops === CREATE_KEY) { } else if (ops === CREATE_KEY) {
this.onCreate(); this.onCreate();
} else { } else {

View file

@ -32,6 +32,8 @@ import { ITag, ITagRow } from "@/Interface";
import TagsTableHeader from "@/components/TagsManager/TagsView/TagsTableHeader.vue"; import TagsTableHeader from "@/components/TagsManager/TagsView/TagsTableHeader.vue";
import TagsTable from "@/components/TagsManager/TagsView/TagsTable.vue"; import TagsTable from "@/components/TagsManager/TagsView/TagsTable.vue";
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { mapStores } from "pinia";
import { useUsersStore } from "@/stores/users";
const matches = (name: string, filter: string) => name.toLowerCase().trim().includes(filter.toLowerCase().trim()); const matches = (name: string, filter: string) => name.toLowerCase().trim().includes(filter.toLowerCase().trim());
@ -51,7 +53,7 @@ export default Vue.extend({
}; };
}, },
computed: { computed: {
...mapGetters('users', ['canUserDeleteTags']), ...mapStores(useUsersStore),
isCreateEnabled(): boolean { isCreateEnabled(): boolean {
return (this.$props.tags || []).length === 0 || this.$data.createEnabled; return (this.$props.tags || []).length === 0 || this.$data.createEnabled;
}, },
@ -69,7 +71,7 @@ export default Vue.extend({
disable: disabled && tag.id !== this.deleteId && tag.id !== this.$data.updateId, disable: disabled && tag.id !== this.deleteId && tag.id !== this.$data.updateId,
update: disabled && tag.id === this.$data.updateId, update: disabled && tag.id === this.$data.updateId,
delete: disabled && tag.id === this.$data.deleteId, delete: disabled && tag.id === this.$data.deleteId,
canDelete: this.canUserDeleteTags, canDelete: this.usersStore.canUserDeleteTags,
})); }));
return this.isCreateEnabled return this.isCreateEnabled

View file

@ -3,10 +3,12 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import { useRootStore } from '@/stores/n8nRootStore';
import { useSettingsStore } from '@/stores/settings';
import { useUsersStore } from '@/stores/users';
import { ITelemetrySettings } from 'n8n-workflow';
import { mapStores } from 'pinia';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { mapGetters } from 'vuex';
import { externalHooks } from './mixins/externalHooks'; import { externalHooks } from './mixins/externalHooks';
export default mixins(externalHooks).extend({ export default mixins(externalHooks).extend({
@ -17,12 +19,20 @@ export default mixins(externalHooks).extend({
}; };
}, },
computed: { computed: {
...mapGetters('settings', ['telemetry']), ...mapStores(
...mapGetters('users', ['currentUserId']), useRootStore,
...mapGetters(['instanceId']), useSettingsStore,
useUsersStore,
),
currentUserId(): string {
return this.usersStore.currentUserId || '';
},
isTelemetryEnabledOnRoute(): boolean { isTelemetryEnabledOnRoute(): boolean {
return this.$route.meta && this.$route.meta.telemetry ? !this.$route.meta.telemetry.disabled: true; return this.$route.meta && this.$route.meta.telemetry ? !this.$route.meta.telemetry.disabled: true;
}, },
telemetry(): ITelemetrySettings {
return this.settingsStore.telemetry;
},
}, },
mounted() { mounted() {
this.init(); this.init();
@ -40,15 +50,15 @@ export default mixins(externalHooks).extend({
this.$telemetry.init( this.$telemetry.init(
telemetrySettings, telemetrySettings,
{ {
instanceId: this.instanceId, instanceId: this.rootStore.instanceId,
userId: this.currentUserId, userId: this.currentUserId,
store: this.$store, store: this.$store,
versionCli: this.$store.getters['settings/versionCli'], versionCli: this.rootStore.versionCli,
}, },
); );
this.$externalHooks().run('telemetry.currentUserIdChanged', { this.$externalHooks().run('telemetry.currentUserIdChanged', {
instanceId: this.instanceId, instanceId: this.rootStore.instanceId,
userId: this.currentUserId, userId: this.currentUserId,
}); });
@ -60,9 +70,9 @@ export default mixins(externalHooks).extend({
this.init(); this.init();
}, },
currentUserId(userId) { currentUserId(userId) {
this.$telemetry.identify(this.instanceId, userId); this.$telemetry.identify(this.rootStore.instanceId, userId);
this.$externalHooks().run('telemetry.currentUserIdChanged', { this.$externalHooks().run('telemetry.currentUserIdChanged', {
instanceId: this.instanceId, instanceId: this.rootStore.instanceId,
userId, userId,
}); });
}, },

View file

@ -20,7 +20,7 @@
</template-details-block> </template-details-block>
<template-details-block <template-details-block
v-if="!loading && template.categories.length > 0" v-if="!loading && template?.categories.length > 0"
:title="$locale.baseText('template.details.categories')" :title="$locale.baseText('template.details.categories')"
> >
<n8n-tags :tags="template.categories" @click="redirectToCategory" /> <n8n-tags :tags="template.categories" @click="redirectToCategory" />
@ -52,6 +52,8 @@ import TemplateDetailsBlock from '@/components/TemplateDetailsBlock.vue';
import NodeIcon from '@/components/NodeIcon.vue'; import NodeIcon from '@/components/NodeIcon.vue';
import { abbreviateNumber, filterTemplateNodes } from '@/components/helpers'; import { abbreviateNumber, filterTemplateNodes } from '@/components/helpers';
import { ITemplatesNode, ITemplatesWorkflow, ITemplatesWorkflowFull } from '@/Interface'; import { ITemplatesNode, ITemplatesWorkflow, ITemplatesWorkflowFull } from '@/Interface';
import { mapStores } from 'pinia';
import { useTemplatesStore } from '@/stores/templates';
export default Vue.extend({ export default Vue.extend({
name: 'TemplateDetails', name: 'TemplateDetails',
props: { props: {
@ -69,15 +71,20 @@ export default Vue.extend({
NodeIcon, NodeIcon,
TemplateDetailsBlock, TemplateDetailsBlock,
}, },
computed: {
...mapStores(
useTemplatesStore,
),
},
methods: { methods: {
abbreviateNumber, abbreviateNumber,
filterTemplateNodes, filterTemplateNodes,
redirectToCategory(id: string) { redirectToCategory(id: string) {
this.$store.commit('templates/resetSessionId'); this.templatesStore.resetSessionId();
this.$router.push(`/templates?categories=${id}`); this.$router.push(`/templates?categories=${id}`);
}, },
redirectToSearchPage(node: ITemplatesNode) { redirectToSearchPage(node: ITemplatesNode) {
this.$store.commit('templates/resetSessionId'); this.templatesStore.resetSessionId();
this.$router.push(`/templates?search=${node.displayName}`); this.$router.push(`/templates?search=${node.displayName}`);
}, },
}, },

View file

@ -9,6 +9,8 @@ import { format, LocaleFunc, register } from 'timeago.js';
import { convertToHumanReadableDate } from './helpers'; import { convertToHumanReadableDate } from './helpers';
import Vue from 'vue'; import Vue from 'vue';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { mapStores } from 'pinia';
import { useRootStore } from '@/stores/n8nRootStore';
export default Vue.extend({ export default Vue.extend({
name: 'TimeAgo', name: 'TimeAgo',
@ -48,7 +50,12 @@ export default Vue.extend({
}, },
}, },
computed: { computed: {
...mapGetters(['defaultLocale']), ...mapStores(
useRootStore,
),
defaultLocale(): string {
return this.rootStore.defaultLocale;
},
format(): string { format(): string {
const text = format(this.date, this.defaultLocale); const text = format(this.date, this.defaultLocale);

View file

@ -108,6 +108,11 @@ import NodeIcon from './NodeIcon.vue';
import { copyPaste } from './mixins/copyPaste'; import { copyPaste } from './mixins/copyPaste';
import { showMessage } from '@/components/mixins/showMessage'; import { showMessage } from '@/components/mixins/showMessage';
import Vue from 'vue'; import Vue from 'vue';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import { useNodeTypesStore } from '@/stores/nodeTypes';
export default mixins(workflowHelpers, copyPaste, showMessage).extend({ export default mixins(workflowHelpers, copyPaste, showMessage).extend({
name: 'TriggerPanel', name: 'TriggerPanel',
@ -125,12 +130,18 @@ export default mixins(workflowHelpers, copyPaste, showMessage).extend({
}, },
}, },
computed: { computed: {
...mapStores(
useNodeTypesStore,
useNDVStore,
useUIStore,
useWorkflowsStore,
),
node(): INodeUi | null { node(): INodeUi | null {
return this.$store.getters.getNodeByName(this.nodeName); return this.workflowsStore.getNodeByName(this.nodeName);
}, },
nodeType(): INodeTypeDescription | null { nodeType(): INodeTypeDescription | null {
if (this.node) { if (this.node) {
return this.$store.getters['nodeTypes/getNodeType'](this.node.type, this.node.typeVersion); return this.nodeTypesStore.getNodeType(this.node.type, this.node.typeVersion);
} }
return null; return null;
@ -195,8 +206,8 @@ export default mixins(workflowHelpers, copyPaste, showMessage).extend({
return Boolean(this.nodeType && this.nodeType.polling); return Boolean(this.nodeType && this.nodeType.polling);
}, },
isListeningForEvents(): boolean { isListeningForEvents(): boolean {
const waitingOnWebhook = this.$store.getters.executionWaitingForWebhook as boolean; const waitingOnWebhook = this.workflowsStore.executionWaitingForWebhook as boolean;
const executedNode = this.$store.getters.executedNode as string | undefined; const executedNode = this.workflowsStore.executedNode as string | undefined;
return ( return (
!!this.node && !!this.node &&
!this.node.disabled && !this.node.disabled &&
@ -206,15 +217,15 @@ export default mixins(workflowHelpers, copyPaste, showMessage).extend({
); );
}, },
workflowRunning(): boolean { workflowRunning(): boolean {
return this.$store.getters.isActionActive('workflowRunning'); return this.uiStore.isActionActive('workflowRunning');
}, },
isActivelyPolling(): boolean { isActivelyPolling(): boolean {
const triggeredNode = this.$store.getters.executedNode; const triggeredNode = this.workflowsStore.executedNode;
return this.workflowRunning && this.isPollingNode && this.nodeName === triggeredNode; return this.workflowRunning && this.isPollingNode && this.nodeName === triggeredNode;
}, },
isWorkflowActive(): boolean { isWorkflowActive(): boolean {
return this.$store.getters.isActive; return this.workflowsStore.isWorkflowActive;
}, },
header(): string { header(): string {
const serviceName = this.nodeType ? getTriggerNodeServiceName(this.nodeType) : ''; const serviceName = this.nodeType ? getTriggerNodeServiceName(this.nodeType) : '';
@ -369,15 +380,15 @@ export default mixins(workflowHelpers, copyPaste, showMessage).extend({
this.$emit('activate'); this.$emit('activate');
} else if (target.dataset.key === 'executions') { } else if (target.dataset.key === 'executions') {
this.$telemetry.track('User clicked ndv link', { this.$telemetry.track('User clicked ndv link', {
workflow_id: this.$store.getters.workflowId, workflow_id: this.workflowsStore.workflowId,
session_id: this.sessionId, session_id: this.sessionId,
pane: 'input', pane: 'input',
type: 'open-executions-log', type: 'open-executions-log',
}); });
this.$store.commit('ndv/setActiveNodeName', null); this.ndvStore.activeNodeName = null;
this.$store.dispatch('ui/openModal', EXECUTIONS_MODAL_KEY); this.uiStore.openModal(EXECUTIONS_MODAL_KEY);
} else if (target.dataset.key === 'settings') { } else if (target.dataset.key === 'settings') {
this.$store.dispatch('ui/openModal', WORKFLOW_SETTINGS_MODAL_KEY); this.uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
} }
} }
}, },

View file

@ -64,6 +64,9 @@ import ModalDrawer from './ModalDrawer.vue';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { workflowHelpers } from '@/components/mixins/workflowHelpers'; import { workflowHelpers } from '@/components/mixins/workflowHelpers';
import Vue from 'vue'; import Vue from 'vue';
import { mapStores } from 'pinia';
import { useSettingsStore } from '@/stores/settings';
import { useRootStore } from '@/stores/n8nRootStore';
const DEFAULT_TITLE = `How likely are you to recommend n8n to a friend or colleague?`; const DEFAULT_TITLE = `How likely are you to recommend n8n to a friend or colleague?`;
const GREAT_FEEDBACK_TITLE = `Great to hear! Can we reach out to see how we can make n8n even better for you?`; const GREAT_FEEDBACK_TITLE = `Great to hear! Can we reach out to see how we can make n8n even better for you?`;
@ -79,12 +82,16 @@ export default mixins(workflowHelpers).extend({
isActive(isActive) { isActive(isActive) {
if (isActive) { if (isActive) {
this.$telemetry.track('User shown value survey', { this.$telemetry.track('User shown value survey', {
instance_id: this.$store.getters.instanceId, instance_id: this.rootStore.instanceId,
}); });
} }
}, },
}, },
computed: { computed: {
...mapStores(
useRootStore,
useSettingsStore,
),
getTitle(): string { getTitle(): string {
if (this.form.value !== '') { if (this.form.value !== '') {
if (Number(this.form.value) > 7) { if (Number(this.form.value) > 7) {
@ -115,13 +122,13 @@ export default mixins(workflowHelpers).extend({
closeDialog(): void { closeDialog(): void {
if (this.form.value === '') { if (this.form.value === '') {
this.$telemetry.track('User responded value survey score', { this.$telemetry.track('User responded value survey score', {
instance_id: this.$store.getters.instanceId, instance_id: this.rootStore.instanceId,
nps: '', nps: '',
}); });
} }
if (this.form.value !== '' && this.form.email === '') { if (this.form.value !== '' && this.form.email === '') {
this.$telemetry.track('User responded value survey email', { this.$telemetry.track('User responded value survey email', {
instance_id: this.$store.getters.instanceId, instance_id: this.rootStore.instanceId,
email: '', email: '',
}); });
} }
@ -133,31 +140,25 @@ export default mixins(workflowHelpers).extend({
this.form.value = value; this.form.value = value;
this.showButtons = false; this.showButtons = false;
const response: IN8nPromptResponse = await this.$store.dispatch( const response: IN8nPromptResponse | undefined = await this.settingsStore.submitValueSurvey({ value: this.form.value });
'settings/submitValueSurvey',
{ value: this.form.value },
);
if (response.updated) { if (response && response.updated) {
this.$telemetry.track('User responded value survey score', { this.$telemetry.track('User responded value survey score', {
instance_id: this.$store.getters.instanceId, instance_id: this.rootStore.instanceId,
nps: this.form.value, nps: this.form.value,
}); });
} }
}, },
async send() { async send() {
if (this.isEmailValid) { if (this.isEmailValid) {
const response: IN8nPromptResponse = await this.$store.dispatch( const response: IN8nPromptResponse | undefined = await this.settingsStore.submitValueSurvey({
'settings/submitValueSurvey', email: this.form.email,
{ value: this.form.value,
email: this.form.email, });
value: this.form.value,
},
);
if (response.updated) { if (response && response.updated) {
this.$telemetry.track('User responded value survey email', { this.$telemetry.track('User responded value survey email', {
instance_id: this.$store.getters.instanceId, instance_id: this.rootStore.instanceId,
email: this.form.email, email: this.form.email,
}); });
this.$showMessage({ this.$showMessage({

View file

@ -40,6 +40,10 @@ import {
import { workflowHelpers } from '@/components/mixins/workflowHelpers'; import { workflowHelpers } from '@/components/mixins/workflowHelpers';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useRootStore } from '@/stores/n8nRootStore';
import { useNDVStore } from '@/stores/ndv';
// Node types that should not be displayed in variable selector // Node types that should not be displayed in variable selector
const SKIPPED_NODE_TYPES = [ const SKIPPED_NODE_TYPES = [
@ -64,6 +68,11 @@ export default mixins(
}; };
}, },
computed: { computed: {
...mapStores(
useNDVStore,
useRootStore,
useWorkflowsStore,
),
extendAll (): boolean { extendAll (): boolean {
if (this.variableFilter) { if (this.variableFilter) {
return true; return true;
@ -406,7 +415,7 @@ export default mixins(
const runIndex = 0; const runIndex = 0;
const returnData: IVariableSelectorOption[] = []; const returnData: IVariableSelectorOption[] = [];
const activeNode: INodeUi | null = this.$store.getters['ndv/activeNode']; const activeNode: INodeUi | null = this.ndvStore.activeNode;
if (activeNode === null) { if (activeNode === null) {
return returnData; return returnData;
@ -431,7 +440,7 @@ export default mixins(
$resumeWebhookUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME, $resumeWebhookUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
}; };
const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, nodeName, connectionInputData, {}, 'manual', this.$store.getters.timezone, additionalKeys); const dataProxy = new WorkflowDataProxy(workflow, runExecutionData, runIndex, itemIndex, nodeName, connectionInputData, {}, 'manual', this.rootStore.timezone, additionalKeys);
const proxy = dataProxy.getDataProxy(); const proxy = dataProxy.getDataProxy();
// @ts-ignore // @ts-ignore
@ -486,15 +495,15 @@ export default mixins(
getFilterResults (filterText: string, itemIndex: number): IVariableSelectorOption[] { getFilterResults (filterText: string, itemIndex: number): IVariableSelectorOption[] {
const inputName = 'main'; const inputName = 'main';
const activeNode: INodeUi | null = this.$store.getters['ndv/activeNode']; const activeNode: INodeUi | null = this.ndvStore.activeNode;
if (activeNode === null) { if (activeNode === null) {
return []; return [];
} }
const executionData = this.$store.getters.getWorkflowExecution as IExecutionResponse | null; const executionData = this.workflowsStore.getWorkflowExecution;
let parentNode = this.workflow.getParentNodes(activeNode.name, inputName, 1); let parentNode = this.workflow.getParentNodes(activeNode.name, inputName, 1);
let runData = this.$store.getters.getWorkflowRunData as IRunData | null; let runData = this.workflowsStore.getWorkflowRunData;
if (runData === null) { if (runData === null) {
runData = {}; runData = {};
@ -543,7 +552,7 @@ export default mixins(
}, },
]; ];
parentNode.forEach((parentNodeName) => { parentNode.forEach((parentNodeName) => {
const pinData = this.$store.getters['pinDataByNodeName'](parentNodeName); const pinData = this.workflowsStore.pinDataByNodeName(parentNodeName);
if (pinData) { if (pinData) {
const output = this.getNodePinDataOutput(parentNodeName, pinData, filterText, true); const output = this.getNodePinDataOutput(parentNodeName, pinData, filterText, true);
@ -677,7 +686,7 @@ export default mixins(
if (upstreamNodes.includes(nodeName)) { if (upstreamNodes.includes(nodeName)) {
// If the node is an upstream node add also the output data which can be referenced // If the node is an upstream node add also the output data which can be referenced
const pinData = this.$store.getters['pinDataByNodeName'](nodeName); const pinData = this.workflowsStore.pinDataByNodeName(nodeName);
tempOutputData = pinData tempOutputData = pinData
? this.getNodePinDataOutput(nodeName, pinData, filterText) ? this.getNodePinDataOutput(nodeName, pinData, filterText)
: this.getNodeRunDataOutput(nodeName, runData, filterText, itemIndex); : this.getNodeRunDataOutput(nodeName, runData, filterText, itemIndex);

View file

@ -35,10 +35,10 @@
import { showMessage } from '@/components/mixins/showMessage'; import { showMessage } from '@/components/mixins/showMessage';
import { workflowActivate } from '@/components/mixins/workflowActivate'; import { workflowActivate } from '@/components/mixins/workflowActivate';
import { useUIStore } from '@/stores/ui';
import { useWorkflowsStore } from '@/stores/workflows';
import { mapStores } from 'pinia';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { mapGetters } from "vuex";
import { getActivatableTriggerNodes } from './helpers'; import { getActivatableTriggerNodes } from './helpers';
export default mixins( export default mixins(
@ -53,14 +53,18 @@ export default mixins(
'workflowId', 'workflowId',
], ],
computed: { computed: {
...mapGetters({ ...mapStores(
dirtyState: "getStateIsDirty", useUIStore,
}), useWorkflowsStore,
),
getStateIsDirty (): boolean {
return this.uiStore.stateIsDirty;
},
nodesIssuesExist (): boolean { nodesIssuesExist (): boolean {
return this.$store.getters.nodesIssuesExist; return this.workflowsStore.nodesIssuesExist;
}, },
isWorkflowActive (): boolean { isWorkflowActive (): boolean {
const activeWorkflows = this.$store.getters.getActiveWorkflows; const activeWorkflows = this.workflowsStore.activeWorkflows;
return activeWorkflows.includes(this.workflowId); return activeWorkflows.includes(this.workflowId);
}, },
couldNotBeStarted (): boolean { couldNotBeStarted (): boolean {
@ -73,7 +77,7 @@ export default mixins(
return '#13ce66'; return '#13ce66';
}, },
isCurrentWorkflow(): boolean { isCurrentWorkflow(): boolean {
return this.$store.getters['workflowId'] === this.workflowId; return this.workflowsStore.workflowId === this.workflowId;
}, },
disabled(): boolean { disabled(): boolean {
const isNewWorkflow = !this.workflowId; const isNewWorkflow = !this.workflowId;
@ -84,7 +88,7 @@ export default mixins(
return false; return false;
}, },
containsTrigger(): boolean { containsTrigger(): boolean {
const foundTriggers = getActivatableTriggerNodes(this.$store.getters.workflowTriggerNodes); const foundTriggers = getActivatableTriggerNodes(this.workflowsStore.workflowTriggerNodes);
return foundTriggers.length > 0; return foundTriggers.length > 0;
}, },
}, },

View file

@ -12,7 +12,7 @@
<n8n-text color="text-light" size="small"> <n8n-text color="text-light" size="small">
<span v-show="data">{{$locale.baseText('workflows.item.updated')}} <time-ago :date="data.updatedAt" /> | </span> <span v-show="data">{{$locale.baseText('workflows.item.updated')}} <time-ago :date="data.updatedAt" /> | </span>
<span v-show="data" class="mr-2xs">{{$locale.baseText('workflows.item.created')}} {{ formattedCreatedAtDate }} </span> <span v-show="data" class="mr-2xs">{{$locale.baseText('workflows.item.created')}} {{ formattedCreatedAtDate }} </span>
<span v-if="areTagsEnabled && data.tags && data.tags.length > 0" v-show="data"> <span v-if="settingsStore.areTagsEnabled && data.tags && data.tags.length > 0" v-show="data">
<n8n-tags <n8n-tags
:tags="data.tags" :tags="data.tags"
:truncateAt="3" :truncateAt="3"
@ -62,6 +62,11 @@ import dateformat from "dateformat";
import { restApi } from '@/components/mixins/restApi'; import { restApi } from '@/components/mixins/restApi';
import WorkflowActivator from '@/components/WorkflowActivator.vue'; import WorkflowActivator from '@/components/WorkflowActivator.vue';
import Vue from "vue"; import Vue from "vue";
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useSettingsStore } from '@/stores/settings';
import { useUsersStore } from '@/stores/users';
import { useWorkflowsStore } from '@/stores/workflows';
export const WORKFLOW_LIST_ITEM_ACTIONS = { export const WORKFLOW_LIST_ITEM_ACTIONS = {
OPEN: 'open', OPEN: 'open',
@ -103,11 +108,14 @@ export default mixins(
}, },
}, },
computed: { computed: {
...mapStores(
useSettingsStore,
useUIStore,
useUsersStore,
useWorkflowsStore,
),
currentUser (): IUser { currentUser (): IUser {
return this.$store.getters['users/currentUser']; return this.usersStore.currentUser || {} as IUser;
},
areTagsEnabled(): boolean {
return this.$store.getters['settings/areTagsEnabled'];
}, },
credentialPermissions(): IPermissions { credentialPermissions(): IPermissions {
return getWorkflowPermissions(this.currentUser, this.data, this.$store); return getWorkflowPermissions(this.currentUser, this.data, this.$store);
@ -162,7 +170,7 @@ export default mixins(
if (action === WORKFLOW_LIST_ITEM_ACTIONS.OPEN) { if (action === WORKFLOW_LIST_ITEM_ACTIONS.OPEN) {
await this.onClick(); await this.onClick();
} else if (action === WORKFLOW_LIST_ITEM_ACTIONS.DUPLICATE) { } else if (action === WORKFLOW_LIST_ITEM_ACTIONS.DUPLICATE) {
await this.$store.dispatch('ui/openModalWithData', { this.uiStore.openModalWithData({
name: DUPLICATE_MODAL_KEY, name: DUPLICATE_MODAL_KEY,
data: { data: {
id: this.data.id, id: this.data.id,
@ -188,7 +196,7 @@ export default mixins(
try { try {
await this.restApi().deleteWorkflow(this.data.id); await this.restApi().deleteWorkflow(this.data.id);
this.$store.commit('deleteWorkflow', this.data.id); this.workflowsStore.deleteWorkflow(this.data.id);
} catch (error) { } catch (error) {
this.$showError( this.$showError(
error, error,

View file

@ -220,10 +220,12 @@ import { restApi } from '@/components/mixins/restApi';
import { genericHelpers } from '@/components/mixins/genericHelpers'; import { genericHelpers } from '@/components/mixins/genericHelpers';
import { showMessage } from '@/components/mixins/showMessage'; import { showMessage } from '@/components/mixins/showMessage';
import { import {
IN8nUISettings,
ITimeoutHMS, ITimeoutHMS,
IWorkflowDataUpdate, IWorkflowDataUpdate,
IWorkflowSettings, IWorkflowSettings,
IWorkflowShortResponse, IWorkflowShortResponse,
WorkflowCallerPolicyDefaultOption,
} from '@/Interface'; } from '@/Interface';
import Modal from './Modal.vue'; import Modal from './Modal.vue';
import { PLACEHOLDER_EMPTY_WORKFLOW_ID, WORKFLOW_SETTINGS_MODAL_KEY } from '../constants'; import { PLACEHOLDER_EMPTY_WORKFLOW_ID, WORKFLOW_SETTINGS_MODAL_KEY } from '../constants';
@ -232,6 +234,10 @@ import mixins from 'vue-typed-mixins';
import { mapGetters } from "vuex"; import { mapGetters } from "vuex";
import { deepCopy } from "n8n-workflow"; import { deepCopy } from "n8n-workflow";
import { mapStores } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows';
import { useSettingsStore } from '@/stores/settings';
import { useRootStore } from '@/stores/n8nRootStore';
export default mixins( export default mixins(
externalHooks, externalHooks,
@ -274,8 +280,8 @@ export default mixins(
timezones: [] as Array<{ key: string, value: string }>, timezones: [] as Array<{ key: string, value: string }>,
workflowSettings: {} as IWorkflowSettings, workflowSettings: {} as IWorkflowSettings,
workflows: [] as IWorkflowShortResponse[], workflows: [] as IWorkflowShortResponse[],
executionTimeout: this.$store.getters.executionTimeout, executionTimeout: 0,
maxExecutionTimeout: this.$store.getters.maxExecutionTimeout, maxExecutionTimeout: 0,
timeoutHMS: { hours: 0, minutes: 0, seconds: 0 } as ITimeoutHMS, timeoutHMS: { hours: 0, minutes: 0, seconds: 0 } as ITimeoutHMS,
modalBus: new Vue(), modalBus: new Vue(),
WORKFLOW_SETTINGS_MODAL_KEY, WORKFLOW_SETTINGS_MODAL_KEY,
@ -283,13 +289,25 @@ export default mixins(
}, },
computed: { computed: {
...mapGetters(['workflowName', 'workflowId']), ...mapStores(
useRootStore,
useSettingsStore,
useWorkflowsStore,
),
workflowName(): string {
return this.workflowsStore.workflowName;
},
workflowId(): string {
return this.workflowsStore.workflowId;
},
isWorkflowSharingEnabled(): boolean { isWorkflowSharingEnabled(): boolean {
return this.$store.getters['settings/isWorkflowSharingEnabled']; return this.settingsStore.isWorkflowSharingEnabled;
}, },
}, },
async mounted () { async mounted () {
this.executionTimeout = this.rootStore.executionTimeout;
this.maxExecutionTimeout = this.rootStore.maxExecutionTimeout;
if (!this.workflowId || this.workflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID) { if (!this.workflowId || this.workflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID) {
this.$showMessage({ this.$showMessage({
title: 'No workflow active', title: 'No workflow active',
@ -301,11 +319,11 @@ export default mixins(
return; return;
} }
this.defaultValues.saveDataErrorExecution = this.$store.getters.saveDataErrorExecution; this.defaultValues.saveDataErrorExecution = this.settingsStore.saveDataErrorExecution;
this.defaultValues.saveDataSuccessExecution = this.$store.getters.saveDataSuccessExecution; this.defaultValues.saveDataSuccessExecution = this.settingsStore.saveDataSuccessExecution;
this.defaultValues.saveManualExecutions = this.$store.getters.saveManualExecutions; this.defaultValues.saveManualExecutions = this.settingsStore.saveManualExecutions;
this.defaultValues.timezone = this.$store.getters.timezone; this.defaultValues.timezone = this.rootStore.timezone;
this.defaultValues.workflowCallerPolicy = this.$store.getters['settings/workflowCallerPolicyDefaultOption']; this.defaultValues.workflowCallerPolicy = this.settingsStore.workflowCallerPolicyDefaultOption;
this.isLoading = true; this.isLoading = true;
const promises = []; const promises = [];
@ -323,7 +341,7 @@ export default mixins(
this.$showError(error, 'Problem loading settings', 'The following error occurred loading the data:'); this.$showError(error, 'Problem loading settings', 'The following error occurred loading the data:');
} }
const workflowSettings = deepCopy(this.$store.getters.workflowSettings); const workflowSettings = deepCopy(this.workflowsStore.workflowSettings) as IWorkflowSettings;
if (workflowSettings.timezone === undefined) { if (workflowSettings.timezone === undefined) {
workflowSettings.timezone = 'DEFAULT'; workflowSettings.timezone = 'DEFAULT';
@ -338,16 +356,16 @@ export default mixins(
workflowSettings.saveExecutionProgress = 'DEFAULT'; workflowSettings.saveExecutionProgress = 'DEFAULT';
} }
if (workflowSettings.saveManualExecutions === undefined) { if (workflowSettings.saveManualExecutions === undefined) {
workflowSettings.saveManualExecutions = 'DEFAULT'; workflowSettings.saveManualExecutions = this.defaultValues.saveManualExecutions;
} }
if (workflowSettings.callerPolicy === undefined) { if (workflowSettings.callerPolicy === undefined) {
workflowSettings.callerPolicy = this.defaultValues.workflowCallerPolicy; workflowSettings.callerPolicy = this.defaultValues.workflowCallerPolicy as WorkflowCallerPolicyDefaultOption;
} }
if (workflowSettings.executionTimeout === undefined) { if (workflowSettings.executionTimeout === undefined) {
workflowSettings.executionTimeout = this.$store.getters.executionTimeout; workflowSettings.executionTimeout = this.rootStore.executionTimeout;
} }
if (workflowSettings.maxExecutionTimeout === undefined) { if (workflowSettings.maxExecutionTimeout === undefined) {
workflowSettings.maxExecutionTimeout = this.$store.getters.maxExecutionTimeout; workflowSettings.maxExecutionTimeout = this.rootStore.maxExecutionTimeout;
} }
Vue.set(this, 'workflowSettings', workflowSettings); Vue.set(this, 'workflowSettings', workflowSettings);
@ -355,7 +373,7 @@ export default mixins(
this.isLoading = false; this.isLoading = false;
this.$externalHooks().run('workflowSettings.dialogVisibleChanged', { dialogVisible: true }); this.$externalHooks().run('workflowSettings.dialogVisibleChanged', { dialogVisible: true });
this.$telemetry.track('User opened workflow settings', { workflow_id: this.$store.getters.workflowId }); this.$telemetry.track('User opened workflow settings', { workflow_id: this.workflowsStore.workflowId });
}, },
methods: { methods: {
onCallerIdsInput(str: string) { onCallerIdsInput(str: string) {
@ -589,11 +607,11 @@ export default mixins(
delete data.settings!.maxExecutionTimeout; delete data.settings!.maxExecutionTimeout;
this.isLoading = true; this.isLoading = true;
data.hash = this.$store.getters.workflowHash; data.hash = this.workflowsStore.workflowHash;
try { try {
const workflow = await this.restApi().updateWorkflow(this.$route.params.name, data); const workflow = await this.restApi().updateWorkflow(this.$route.params.name, data);
this.$store.commit('setWorkflowHash', workflow.hash); this.workflowsStore.setWorkflowHash(workflow.hash || '');
} catch (error) { } catch (error) {
this.$showError( this.$showError(
error, error,
@ -611,9 +629,9 @@ export default mixins(
} }
} }
const oldSettings = deepCopy(this.$store.getters.workflowSettings); const oldSettings = deepCopy(this.workflowsStore.workflowSettings);
this.$store.commit('setWorkflowSettings', localWorkflowSettings); this.workflowsStore.setWorkflowSettings(localWorkflowSettings);
this.isLoading = false; this.isLoading = false;
@ -625,7 +643,7 @@ export default mixins(
this.closeDialog(); this.closeDialog();
this.$externalHooks().run('workflowSettings.saveSettings', { oldSettings }); this.$externalHooks().run('workflowSettings.saveSettings', { oldSettings });
this.$telemetry.track('User updated workflow settings', { workflow_id: this.$store.getters.workflowId }); this.$telemetry.track('User updated workflow settings', { workflow_id: this.workflowsStore.workflowId });
}, },
toggleTimeout() { toggleTimeout() {
this.workflowSettings.executionTimeout = this.workflowSettings.executionTimeout === -1 ? 0 : -1; this.workflowSettings.executionTimeout = this.workflowSettings.executionTimeout === -1 ? 0 : -1;

View file

@ -32,7 +32,7 @@
/> />
<n8n-user-select <n8n-user-select
:users="ownedByUsers" :users="ownedByUsers"
:currentUserId="currentUser.id" :currentUserId="usersStore.currentUser.id"
:value="value.ownedBy" :value="value.ownedBy"
size="small" size="small"
@input="setKeyValue('ownedBy', $event)" @input="setKeyValue('ownedBy', $event)"
@ -48,7 +48,7 @@
/> />
<n8n-user-select <n8n-user-select
:users="sharedWithUsers" :users="sharedWithUsers"
:currentUserId="currentUser.id" :currentUserId="usersStore.currentUser.id"
:value="value.sharedWith" :value="value.sharedWith"
size="small" size="small"
@input="setKeyValue('sharedWith', $event)" @input="setKeyValue('sharedWith', $event)"
@ -66,8 +66,9 @@
<script lang="ts"> <script lang="ts">
import Vue, { PropType } from 'vue'; import Vue, { PropType } from 'vue';
import {EnterpriseEditionFeature} from "@/constants"; import {EnterpriseEditionFeature} from "@/constants";
import {IResource} from "@/components/layouts/ResourcesListLayout.vue";
import {IUser} from "@/Interface"; import {IUser} from "@/Interface";
import { mapStores } from 'pinia';
import { useUsersStore } from '@/stores/users';
export type IResourceFiltersType = Record<string, boolean | string | string[]>; export type IResourceFiltersType = Record<string, boolean | string | string[]>;
@ -95,17 +96,12 @@ export default Vue.extend({
}; };
}, },
computed: { computed: {
currentUser(): IUser { ...mapStores(useUsersStore),
return this.$store.getters['users/currentUser'];
},
allUsers(): IUser[] {
return this.$store.getters['users/allUsers'];
},
ownedByUsers(): IUser[] { ownedByUsers(): IUser[] {
return this.allUsers.map((user) => user.id === this.value.sharedWith ? { ...user, disabled: true } : user); return this.usersStore.allUsers.map((user) => user.id === this.value.sharedWith ? { ...user, disabled: true } : user);
}, },
sharedWithUsers(): IUser[] { sharedWithUsers(): IUser[] {
return this.allUsers.map((user) => user.id === this.value.ownedBy ? { ...user, disabled: true } : user); return this.usersStore.allUsers.map((user) => user.id === this.value.ownedBy ? { ...user, disabled: true } : user);
}, },
filtersLength(): number { filtersLength(): number {
let length = 0; let length = 0;

View file

@ -1,5 +1,5 @@
<template> <template>
<div :class="[$style.wrapper, !sidebarMenuCollapsed && $style.expandedSidebar]"> <div :class="[$style.wrapper, !this.uiStore.sidebarMenuCollapsed && $style.expandedSidebar]">
<div :class="$style.container"> <div :class="$style.container">
<aside :class="$style.aside" v-if="$slots.aside"> <aside :class="$style.aside" v-if="$slots.aside">
<slot name="aside" /> <slot name="aside" />
@ -12,6 +12,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { useUIStore } from '@/stores/ui';
import { mapStores } from 'pinia';
import Vue from 'vue'; import Vue from 'vue';
export default Vue.extend({ export default Vue.extend({
@ -22,9 +24,7 @@ export default Vue.extend({
}; };
}, },
computed: { computed: {
sidebarMenuCollapsed(): boolean { ...mapStores(useUIStore),
return this.$store.getters['ui/sidebarMenuCollapsed'];
},
}, },
}); });
</script> </script>

Some files were not shown because too many files have changed in this diff Show more