mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
feat(editor): Add workflow action to switch between new and old canvas (no-changelog) (#9969)
This commit is contained in:
@@ -20,7 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="content" :class="$style.content">
|
<div id="content" :class="$style.content">
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<keep-alive v-if="$route.meta.keepWorkflowAlive" include="NodeView" :max="1">
|
<keep-alive v-if="$route.meta.keepWorkflowAlive" include="NodeViewSwitcher" :max="1">
|
||||||
<component :is="Component" />
|
<component :is="Component" />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
<component :is="Component" v-else />
|
<component :is="Component" v-else />
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
DUPLICATE_MODAL_KEY,
|
DUPLICATE_MODAL_KEY,
|
||||||
EnterpriseEditionFeature,
|
EnterpriseEditionFeature,
|
||||||
MAX_WORKFLOW_NAME_LENGTH,
|
MAX_WORKFLOW_NAME_LENGTH,
|
||||||
|
MODAL_CLOSE,
|
||||||
MODAL_CONFIRM,
|
MODAL_CONFIRM,
|
||||||
PLACEHOLDER_EMPTY_WORKFLOW_ID,
|
PLACEHOLDER_EMPTY_WORKFLOW_ID,
|
||||||
SOURCE_CONTROL_PUSH_MODAL_KEY,
|
SOURCE_CONTROL_PUSH_MODAL_KEY,
|
||||||
@@ -55,6 +56,7 @@ import { useI18n } from '@/composables/useI18n';
|
|||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
import type { BaseTextKey } from '../../plugins/i18n';
|
import type { BaseTextKey } from '../../plugins/i18n';
|
||||||
import { useNpsSurveyStore } from '@/stores/npsSurvey.store';
|
import { useNpsSurveyStore } from '@/stores/npsSurvey.store';
|
||||||
|
import { useLocalStorage } from '@vueuse/core';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
workflow: IWorkflowDb;
|
workflow: IWorkflowDb;
|
||||||
@@ -93,6 +95,9 @@ const importFileRef = ref<HTMLInputElement | undefined>();
|
|||||||
const tagsEventBus = createEventBus();
|
const tagsEventBus = createEventBus();
|
||||||
const sourceControlModalEventBus = createEventBus();
|
const sourceControlModalEventBus = createEventBus();
|
||||||
|
|
||||||
|
const nodeViewSwitcher = useLocalStorage('NodeView.switcher', '');
|
||||||
|
const nodeViewVersion = useLocalStorage('NodeView.version', '1');
|
||||||
|
|
||||||
const hasChanged = (prev: string[], curr: string[]) => {
|
const hasChanged = (prev: string[], curr: string[]) => {
|
||||||
if (prev.length !== curr.length) {
|
if (prev.length !== curr.length) {
|
||||||
return true;
|
return true;
|
||||||
@@ -178,6 +183,17 @@ const workflowMenuItems = computed<ActionDropdownItem[]>(() => {
|
|||||||
disabled: !onWorkflowPage.value || isNewWorkflow.value,
|
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) {
|
if ((workflowPermissions.value.delete && !props.readOnly) || isNewWorkflow.value) {
|
||||||
actions.push({
|
actions.push({
|
||||||
id: WORKFLOW_MENU_ACTIONS.DELETE,
|
id: WORKFLOW_MENU_ACTIONS.DELETE,
|
||||||
@@ -488,6 +504,38 @@ async function onWorkflowMenuSelect(action: WORKFLOW_MENU_ACTIONS): Promise<void
|
|||||||
uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
|
uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case WORKFLOW_MENU_ACTIONS.SWITCH_NODE_VIEW_VERSION: {
|
||||||
|
if (uiStore.stateIsDirty) {
|
||||||
|
const confirmModal = await message.confirm(
|
||||||
|
locale.baseText('generic.unsavedWork.confirmMessage.message'),
|
||||||
|
{
|
||||||
|
title: locale.baseText('generic.unsavedWork.confirmMessage.headline'),
|
||||||
|
type: 'warning',
|
||||||
|
confirmButtonText: locale.baseText(
|
||||||
|
'generic.unsavedWork.confirmMessage.confirmButtonText',
|
||||||
|
),
|
||||||
|
cancelButtonText: locale.baseText(
|
||||||
|
'generic.unsavedWork.confirmMessage.cancelButtonText',
|
||||||
|
),
|
||||||
|
showClose: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (confirmModal === MODAL_CONFIRM) {
|
||||||
|
await onSaveButtonClick();
|
||||||
|
} else if (confirmModal === MODAL_CLOSE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodeViewVersion.value === '1') {
|
||||||
|
nodeViewVersion.value = '2';
|
||||||
|
} else {
|
||||||
|
nodeViewVersion.value = '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
case WORKFLOW_MENU_ACTIONS.DELETE: {
|
case WORKFLOW_MENU_ACTIONS.DELETE: {
|
||||||
const deleteConfirmed = await message.confirm(
|
const deleteConfirmed = await message.confirm(
|
||||||
locale.baseText('mainSidebar.confirmMessage.workflowDelete.message', {
|
locale.baseText('mainSidebar.confirmMessage.workflowDelete.message', {
|
||||||
|
|||||||
@@ -399,6 +399,7 @@ export const ROLE_OTHER = 'other';
|
|||||||
|
|
||||||
export const MODAL_CANCEL = 'cancel';
|
export const MODAL_CANCEL = 'cancel';
|
||||||
export const MODAL_CONFIRM = 'confirm';
|
export const MODAL_CONFIRM = 'confirm';
|
||||||
|
export const MODAL_CLOSE = 'close';
|
||||||
|
|
||||||
export const VALID_EMAIL_REGEX =
|
export const VALID_EMAIL_REGEX =
|
||||||
/^(([^<>()[\]\\.,;:\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,}))$/;
|
/^(([^<>()[\]\\.,;:\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',
|
CREDENTIALS = 'CredentialsView',
|
||||||
VARIABLES = 'VariablesView',
|
VARIABLES = 'VariablesView',
|
||||||
NEW_WORKFLOW = 'NodeViewNew',
|
NEW_WORKFLOW = 'NodeViewNew',
|
||||||
NEW_WORKFLOW_V2 = 'NodeViewNewV2',
|
|
||||||
WORKFLOW = 'NodeViewExisting',
|
WORKFLOW = 'NodeViewExisting',
|
||||||
WORKFLOW_V2 = 'NodeViewV2',
|
|
||||||
DEMO = 'WorkflowDemo',
|
DEMO = 'WorkflowDemo',
|
||||||
TEMPLATE_IMPORT = 'WorkflowTemplate',
|
TEMPLATE_IMPORT = 'WorkflowTemplate',
|
||||||
WORKFLOW_ONBOARDING = 'WorkflowOnboarding',
|
WORKFLOW_ONBOARDING = 'WorkflowOnboarding',
|
||||||
@@ -487,13 +486,7 @@ export const enum VIEWS {
|
|||||||
PROJECT_SETTINGS = 'ProjectSettings',
|
PROJECT_SETTINGS = 'ProjectSettings',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EDITABLE_CANVAS_VIEWS = [
|
export const EDITABLE_CANVAS_VIEWS = [VIEWS.WORKFLOW, VIEWS.NEW_WORKFLOW, VIEWS.EXECUTION_DEBUG];
|
||||||
VIEWS.WORKFLOW,
|
|
||||||
VIEWS.NEW_WORKFLOW,
|
|
||||||
VIEWS.WORKFLOW_V2,
|
|
||||||
VIEWS.NEW_WORKFLOW_V2,
|
|
||||||
VIEWS.EXECUTION_DEBUG,
|
|
||||||
];
|
|
||||||
|
|
||||||
export const enum FAKE_DOOR_FEATURES {
|
export const enum FAKE_DOOR_FEATURES {
|
||||||
ENVIRONMENTS = 'environments',
|
ENVIRONMENTS = 'environments',
|
||||||
@@ -547,6 +540,7 @@ export const enum WORKFLOW_MENU_ACTIONS {
|
|||||||
PUSH = 'push',
|
PUSH = 'push',
|
||||||
SETTINGS = 'settings',
|
SETTINGS = 'settings',
|
||||||
DELETE = 'delete',
|
DELETE = 'delete',
|
||||||
|
SWITCH_NODE_VIEW_VERSION = 'switch-node-view-version',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -860,6 +860,8 @@
|
|||||||
"menuActions.importFromUrl": "Import from URL...",
|
"menuActions.importFromUrl": "Import from URL...",
|
||||||
"menuActions.importFromFile": "Import from File...",
|
"menuActions.importFromFile": "Import from File...",
|
||||||
"menuActions.delete": "Delete",
|
"menuActions.delete": "Delete",
|
||||||
|
"menuActions.switchToNewNodeViewVersion": "Switch to new canvas",
|
||||||
|
"menuActions.switchToOldNodeViewVersion": "Switch to old canvas",
|
||||||
"multipleParameter.addItem": "Add item",
|
"multipleParameter.addItem": "Add item",
|
||||||
"multipleParameter.currentlyNoItemsExist": "Currently no items exist",
|
"multipleParameter.currentlyNoItemsExist": "Currently no items exist",
|
||||||
"multipleParameter.deleteItem": "Delete item",
|
"multipleParameter.deleteItem": "Delete item",
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ const ErrorView = async () => await import('./views/ErrorView.vue');
|
|||||||
const ForgotMyPasswordView = async () => await import('./views/ForgotMyPasswordView.vue');
|
const ForgotMyPasswordView = async () => await import('./views/ForgotMyPasswordView.vue');
|
||||||
const MainHeader = async () => await import('@/components/MainHeader/MainHeader.vue');
|
const MainHeader = async () => await import('@/components/MainHeader/MainHeader.vue');
|
||||||
const MainSidebar = async () => await import('@/components/MainSidebar.vue');
|
const MainSidebar = async () => await import('@/components/MainSidebar.vue');
|
||||||
const NodeView = async () => await import('@/views/NodeView.vue');
|
const NodeView = async () => await import('@/views/NodeViewSwitcher.vue');
|
||||||
const NodeViewV2 = async () => await import('@/views/NodeView.v2.vue');
|
|
||||||
const WorkflowExecutionsView = async () => await import('@/views/WorkflowExecutionsView.vue');
|
const WorkflowExecutionsView = async () => await import('@/views/WorkflowExecutionsView.vue');
|
||||||
const WorkflowExecutionsLandingPage = async () =>
|
const WorkflowExecutionsLandingPage = async () =>
|
||||||
await import('@/components/executions/workflow/WorkflowExecutionsLandingPage.vue');
|
await import('@/components/executions/workflow/WorkflowExecutionsLandingPage.vue');
|
||||||
@@ -70,10 +69,6 @@ function getTemplatesRedirect(defaultRedirect: VIEWS[keyof VIEWS]): { name: stri
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function nodeViewV2CustomMiddleware() {
|
|
||||||
return !!localStorage.getItem('features.NodeViewV2');
|
|
||||||
}
|
|
||||||
|
|
||||||
export const routes: RouteRecordRaw[] = [
|
export const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
@@ -362,40 +357,6 @@ export const routes: RouteRecordRaw[] = [
|
|||||||
path: '/workflow',
|
path: '/workflow',
|
||||||
redirect: '/workflow/new',
|
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',
|
path: '/signin',
|
||||||
name: VIEWS.SIGNIN,
|
name: VIEWS.SIGNIN,
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ const hideNodeIssues = ref(false);
|
|||||||
const workflowId = computed<string>(() => route.params.name as string);
|
const workflowId = computed<string>(() => route.params.name as string);
|
||||||
const workflow = computed(() => workflowsStore.workflowsById[workflowId.value]);
|
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 isDemoRoute = computed(() => route.name === VIEWS.DEMO);
|
||||||
const isReadOnlyRoute = computed(() => route?.meta?.readOnlyCanvas === true);
|
const isReadOnlyRoute = computed(() => route?.meta?.readOnlyCanvas === true);
|
||||||
const isReadOnlyEnvironment = computed(() => {
|
const isReadOnlyEnvironment = computed(() => {
|
||||||
@@ -265,7 +265,7 @@ async function initializeView() {
|
|||||||
toast.showError(error, i18n.baseText('openWorkflow.workflowNotFoundError'));
|
toast.showError(error, i18n.baseText('openWorkflow.workflowNotFoundError'));
|
||||||
|
|
||||||
void router.push({
|
void router.push({
|
||||||
name: VIEWS.NEW_WORKFLOW_V2,
|
name: VIEWS.NEW_WORKFLOW,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -844,11 +844,11 @@ onBeforeRouteLeave(async (to, from, next) => {
|
|||||||
}
|
}
|
||||||
uiStore.stateIsDirty = false;
|
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
|
// Replace the current route with the new workflow route
|
||||||
// before navigating to the new route when saving new workflow.
|
// before navigating to the new route when saving new workflow.
|
||||||
await router.replace({
|
await router.replace({
|
||||||
name: VIEWS.WORKFLOW_V2,
|
name: VIEWS.WORKFLOW,
|
||||||
params: { name: workflowId.value },
|
params: { name: workflowId.value },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
20
packages/editor-ui/src/views/NodeViewSwitcher.vue
Normal file
20
packages/editor-ui/src/views/NodeViewSwitcher.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { useLocalStorage } from '@vueuse/core';
|
||||||
|
import { watch } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import NodeViewV1 from '@/views/NodeView.vue';
|
||||||
|
import NodeViewV2 from '@/views/NodeView.v2.vue';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const nodeViewVersion = useLocalStorage('NodeView.version', '1');
|
||||||
|
|
||||||
|
watch(nodeViewVersion, () => {
|
||||||
|
router.go(0);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NodeViewV2 v-if="nodeViewVersion === '2'" />
|
||||||
|
<NodeViewV1 v-else />
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user