mirror of
https://github.com/n8n-io/n8n.git
synced 2024-08-17 00:50:42 +03:00
feat(editor): Add workflow action to switch between new and old canvas (no-changelog) (#9969)
This commit is contained in:
parent
b5b96eb43e
commit
4e2f0adb2a
@ -20,7 +20,7 @@
|
||||
</div>
|
||||
<div id="content" :class="$style.content">
|
||||
<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" />
|
||||
</keep-alive>
|
||||
<component :is="Component" v-else />
|
||||
|
@ -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<HTMLInputElement | undefined>();
|
||||
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<ActionDropdownItem[]>(() => {
|
||||
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<void
|
||||
uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
|
||||
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: {
|
||||
const deleteConfirmed = await message.confirm(
|
||||
locale.baseText('mainSidebar.confirmMessage.workflowDelete.message', {
|
||||
|
@ -399,6 +399,7 @@ export const ROLE_OTHER = 'other';
|
||||
|
||||
export const MODAL_CANCEL = 'cancel';
|
||||
export const MODAL_CONFIRM = 'confirm';
|
||||
export const MODAL_CLOSE = 'close';
|
||||
|
||||
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,}))$/;
|
||||
@ -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',
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -153,7 +153,7 @@ const hideNodeIssues = ref(false);
|
||||
const workflowId = computed<string>(() => 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 },
|
||||
});
|
||||
|
||||
|
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>
|
Loading…
Reference in New Issue
Block a user