1
1
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:
Alex Grozav 2024-07-09 15:58:36 +03:00 committed by GitHub
parent b5b96eb43e
commit 4e2f0adb2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 79 additions and 54 deletions

View File

@ -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 />

View File

@ -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', {

View File

@ -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',
}
/**

View File

@ -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",

View File

@ -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,

View File

@ -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 },
});

View 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>