diff --git a/packages/editor-ui/src/App.vue b/packages/editor-ui/src/App.vue
index bb29530e52..d07062db18 100644
--- a/packages/editor-ui/src/App.vue
+++ b/packages/editor-ui/src/App.vue
@@ -20,7 +20,7 @@
-
+
diff --git a/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue b/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue
index 9cda6ebc9d..2e56fcb60a 100644
--- a/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue
+++ b/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue
@@ -3,6 +3,7 @@ import {
DUPLICATE_MODAL_KEY,
EnterpriseEditionFeature,
MAX_WORKFLOW_NAME_LENGTH,
+ MODAL_CLOSE,
MODAL_CONFIRM,
PLACEHOLDER_EMPTY_WORKFLOW_ID,
SOURCE_CONTROL_PUSH_MODAL_KEY,
@@ -55,6 +56,7 @@ import { useI18n } from '@/composables/useI18n';
import { useTelemetry } from '@/composables/useTelemetry';
import type { BaseTextKey } from '../../plugins/i18n';
import { useNpsSurveyStore } from '@/stores/npsSurvey.store';
+import { useLocalStorage } from '@vueuse/core';
const props = defineProps<{
workflow: IWorkflowDb;
@@ -93,6 +95,9 @@ const importFileRef = ref();
const tagsEventBus = createEventBus();
const sourceControlModalEventBus = createEventBus();
+const nodeViewSwitcher = useLocalStorage('NodeView.switcher', '');
+const nodeViewVersion = useLocalStorage('NodeView.version', '1');
+
const hasChanged = (prev: string[], curr: string[]) => {
if (prev.length !== curr.length) {
return true;
@@ -178,6 +183,17 @@ const workflowMenuItems = computed(() => {
disabled: !onWorkflowPage.value || isNewWorkflow.value,
});
+ if (nodeViewSwitcher.value === 'true') {
+ actions.push({
+ id: WORKFLOW_MENU_ACTIONS.SWITCH_NODE_VIEW_VERSION,
+ label:
+ nodeViewVersion.value === '2'
+ ? locale.baseText('menuActions.switchToOldNodeViewVersion')
+ : locale.baseText('menuActions.switchToNewNodeViewVersion'),
+ disabled: !onWorkflowPage.value,
+ });
+ }
+
if ((workflowPermissions.value.delete && !props.readOnly) || isNewWorkflow.value) {
actions.push({
id: WORKFLOW_MENU_ACTIONS.DELETE,
@@ -488,6 +504,38 @@ async function onWorkflowMenuSelect(action: WORKFLOW_MENU_ACTIONS): Promise()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
@@ -451,9 +452,7 @@ export const enum VIEWS {
CREDENTIALS = 'CredentialsView',
VARIABLES = 'VariablesView',
NEW_WORKFLOW = 'NodeViewNew',
- NEW_WORKFLOW_V2 = 'NodeViewNewV2',
WORKFLOW = 'NodeViewExisting',
- WORKFLOW_V2 = 'NodeViewV2',
DEMO = 'WorkflowDemo',
TEMPLATE_IMPORT = 'WorkflowTemplate',
WORKFLOW_ONBOARDING = 'WorkflowOnboarding',
@@ -487,13 +486,7 @@ export const enum VIEWS {
PROJECT_SETTINGS = 'ProjectSettings',
}
-export const EDITABLE_CANVAS_VIEWS = [
- VIEWS.WORKFLOW,
- VIEWS.NEW_WORKFLOW,
- VIEWS.WORKFLOW_V2,
- VIEWS.NEW_WORKFLOW_V2,
- VIEWS.EXECUTION_DEBUG,
-];
+export const EDITABLE_CANVAS_VIEWS = [VIEWS.WORKFLOW, VIEWS.NEW_WORKFLOW, VIEWS.EXECUTION_DEBUG];
export const enum FAKE_DOOR_FEATURES {
ENVIRONMENTS = 'environments',
@@ -547,6 +540,7 @@ export const enum WORKFLOW_MENU_ACTIONS {
PUSH = 'push',
SETTINGS = 'settings',
DELETE = 'delete',
+ SWITCH_NODE_VIEW_VERSION = 'switch-node-view-version',
}
/**
diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json
index e4eae1e4e3..7d20b3139d 100644
--- a/packages/editor-ui/src/plugins/i18n/locales/en.json
+++ b/packages/editor-ui/src/plugins/i18n/locales/en.json
@@ -860,6 +860,8 @@
"menuActions.importFromUrl": "Import from URL...",
"menuActions.importFromFile": "Import from File...",
"menuActions.delete": "Delete",
+ "menuActions.switchToNewNodeViewVersion": "Switch to new canvas",
+ "menuActions.switchToOldNodeViewVersion": "Switch to old canvas",
"multipleParameter.addItem": "Add item",
"multipleParameter.currentlyNoItemsExist": "Currently no items exist",
"multipleParameter.deleteItem": "Delete item",
diff --git a/packages/editor-ui/src/router.ts b/packages/editor-ui/src/router.ts
index 0a02ddac31..6c6a68b22d 100644
--- a/packages/editor-ui/src/router.ts
+++ b/packages/editor-ui/src/router.ts
@@ -24,8 +24,7 @@ const ErrorView = async () => await import('./views/ErrorView.vue');
const ForgotMyPasswordView = async () => await import('./views/ForgotMyPasswordView.vue');
const MainHeader = async () => await import('@/components/MainHeader/MainHeader.vue');
const MainSidebar = async () => await import('@/components/MainSidebar.vue');
-const NodeView = async () => await import('@/views/NodeView.vue');
-const NodeViewV2 = async () => await import('@/views/NodeView.v2.vue');
+const NodeView = async () => await import('@/views/NodeViewSwitcher.vue');
const WorkflowExecutionsView = async () => await import('@/views/WorkflowExecutionsView.vue');
const WorkflowExecutionsLandingPage = async () =>
await import('@/components/executions/workflow/WorkflowExecutionsLandingPage.vue');
@@ -70,10 +69,6 @@ function getTemplatesRedirect(defaultRedirect: VIEWS[keyof VIEWS]): { name: stri
return false;
}
-function nodeViewV2CustomMiddleware() {
- return !!localStorage.getItem('features.NodeViewV2');
-}
-
export const routes: RouteRecordRaw[] = [
{
path: '/',
@@ -362,40 +357,6 @@ export const routes: RouteRecordRaw[] = [
path: '/workflow',
redirect: '/workflow/new',
},
- {
- path: '/workflow-v2/:name',
- name: VIEWS.WORKFLOW_V2,
- components: {
- default: NodeViewV2,
- header: MainHeader,
- sidebar: MainSidebar,
- },
- meta: {
- nodeView: true,
- keepWorkflowAlive: true,
- middleware: ['authenticated', 'custom'],
- middlewareOptions: {
- custom: nodeViewV2CustomMiddleware,
- },
- },
- },
- {
- path: '/workflow-v2/new',
- name: VIEWS.NEW_WORKFLOW_V2,
- components: {
- default: NodeViewV2,
- header: MainHeader,
- sidebar: MainSidebar,
- },
- meta: {
- nodeView: true,
- keepWorkflowAlive: true,
- middleware: ['authenticated', 'custom'],
- middlewareOptions: {
- custom: nodeViewV2CustomMiddleware,
- },
- },
- },
{
path: '/signin',
name: VIEWS.SIGNIN,
diff --git a/packages/editor-ui/src/views/NodeView.v2.vue b/packages/editor-ui/src/views/NodeView.v2.vue
index 1f9c4086e9..dedc2e239e 100644
--- a/packages/editor-ui/src/views/NodeView.v2.vue
+++ b/packages/editor-ui/src/views/NodeView.v2.vue
@@ -153,7 +153,7 @@ const hideNodeIssues = ref(false);
const workflowId = computed(() => route.params.name as string);
const workflow = computed(() => workflowsStore.workflowsById[workflowId.value]);
-const isNewWorkflowRoute = computed(() => route.name === VIEWS.NEW_WORKFLOW_V2);
+const isNewWorkflowRoute = computed(() => route.name === VIEWS.NEW_WORKFLOW);
const isDemoRoute = computed(() => route.name === VIEWS.DEMO);
const isReadOnlyRoute = computed(() => route?.meta?.readOnlyCanvas === true);
const isReadOnlyEnvironment = computed(() => {
@@ -265,7 +265,7 @@ async function initializeView() {
toast.showError(error, i18n.baseText('openWorkflow.workflowNotFoundError'));
void router.push({
- name: VIEWS.NEW_WORKFLOW_V2,
+ name: VIEWS.NEW_WORKFLOW,
});
}
}
@@ -844,11 +844,11 @@ onBeforeRouteLeave(async (to, from, next) => {
}
uiStore.stateIsDirty = false;
- if (from.name === VIEWS.NEW_WORKFLOW_V2) {
+ if (from.name === VIEWS.NEW_WORKFLOW) {
// Replace the current route with the new workflow route
// before navigating to the new route when saving new workflow.
await router.replace({
- name: VIEWS.WORKFLOW_V2,
+ name: VIEWS.WORKFLOW,
params: { name: workflowId.value },
});
diff --git a/packages/editor-ui/src/views/NodeViewSwitcher.vue b/packages/editor-ui/src/views/NodeViewSwitcher.vue
new file mode 100644
index 0000000000..0c6f80166d
--- /dev/null
+++ b/packages/editor-ui/src/views/NodeViewSwitcher.vue
@@ -0,0 +1,20 @@
+
+
+
+
+
+