n8n/packages/editor-ui/src/router.ts
Milorad FIlipović 3db53a1934
feat(editor): Main navigation redesign (#4144)
* refactor(editor): N8N-4540 Main navigation layout rework (#4060)

*  Implemented new editor layout using css grid

*  Reworking main navigation layout, migrating some styling to css modules

*  Reworking main sidebar layout and responsiveness

* 💄 Minor type update

*  Updated editor grid layout so empty cells are collapsed (`fit-content`), fixed updates menu items styling

*  Implemented new user area look & feel in main sidebar

* 💄 Adjusting sidebar bottom padding when user area is not shown

* 💄 CSS cleanup/refactor + minor vue refactoring

*  Fixing overscoll issue in chrome and scrolling behaviour of the content view

* 👌 Addressing review feedback

*  Added collapsed and expanded versions of n8n logo

*  Updating infinite scrolling in templates view to work with the new layout

* 💄 Updating main sidebar expanded width and templates view left margin

* 💄 Updating main content height

* 💄 Adding global styles for scrollable views with centered content, minor updates to user area

*  Updating zoomToFit logic, lasso select box position and new nodes positioning

*  Fixing new node drop position now that mouse detection has been adjusted

* 👌 Updating templates view scroll to top logic and responsive padding, aligning menu items titles

* 💄 Moving template layout style from global css class to component level

*  Moved 'Workflows'  menu to node view header. Added new dropdown component for user area and the new WF menu

* 💄 Updating disabled states in new WF menu

* 💄 Initial stab at new sidebar styling

*  Finished main navigation restyling

*  Updating `zoomToFit` and centering logic

*  Adding updates menu item to settings sidebar

* 💄 Adding updates item to the settings sidebar and final touches on main sidebar style

* 💄 Removing old code & refactoring

* 💄 Minor CSS tweaks

* 💄 Opening credentials modal on sidebar menu item click. Minor CSS updates

* 💄 Updating sidebar expand/collapse animation

* 💄 Few more refinements of sidebar animation

* 👌 Addressing code review comments

*  Moved ActionDropdown component to design system

* 👌 Fixing bugs reported during code review and testing

* 👌 Addressing design review comments for the new sidebar

* ✔️ Updating `N8nActionDropdown` component tests

*  Remembering scroll position when going back to templates list

*  Updating zoomToFit logic to account for footer content

* 👌 Addressing latest sidebar review comments

* 👌 Addressing main sidebar product review comments

* 💄 Updating css variable names after vite merge

* ✔️ Fixing linting errors in the design system

* ✔️ Fixing `element-ui` type import

* 👌 Addressing the code review comments.

*  Adding link to new credentials view, removed old modal

* 💄 Updating credentials view responsiveness and route highlight handling

* 💄 Adding highlight to workflows submenu when on new workflow page

* 💄 Updated active submenu text color
2022-09-26 15:25:19 +02:00

502 lines
10 KiB
TypeScript

import Vue from 'vue';
import ChangePasswordView from './views/ChangePasswordView.vue';
import ErrorView from './views/ErrorView.vue';
import ForgotMyPasswordView from './views/ForgotMyPasswordView.vue';
import MainHeader from '@/components/MainHeader/MainHeader.vue';
import MainSidebar from '@/components/MainSidebar.vue';
import NodeView from '@/views/NodeView.vue';
import SettingsPersonalView from './views/SettingsPersonalView.vue';
import SettingsUsersView from './views/SettingsUsersView.vue';
import SettingsCommunityNodesView from './views/SettingsCommunityNodesView.vue';
import SettingsApiView from './views/SettingsApiView.vue';
import SettingsFakeDoorView from './views/SettingsFakeDoorView.vue';
import SetupView from './views/SetupView.vue';
import SigninView from './views/SigninView.vue';
import SignupView from './views/SignupView.vue';
import Router, { Route } from 'vue-router';
import TemplatesCollectionView from '@/views/TemplatesCollectionView.vue';
import TemplatesWorkflowView from '@/views/TemplatesWorkflowView.vue';
import TemplatesSearchView from '@/views/TemplatesSearchView.vue';
import CredentialsView from '@/views/CredentialsView.vue';
import { Store } from 'vuex';
import { IPermissions, IRootState, IWorkflowsState } from './Interface';
import { LOGIN_STATUS, ROLE } from './modules/userHelpers';
import { RouteConfigSingleView } from 'vue-router/types/router';
import { VIEWS } from './constants';
import { store } from './store';
import e from 'express';
Vue.use(Router);
interface IRouteConfig extends RouteConfigSingleView {
meta: {
nodeView?: boolean;
templatesEnabled?: boolean;
getRedirect?: (store: Store<IRootState>) => {name: string} | false;
permissions: IPermissions;
telemetry?: {
disabled?: true;
getProperties: (route: Route, store: Store<IRootState>) => object;
};
scrollOffset?: number;
};
}
function getTemplatesRedirect(store: Store<IRootState>) {
const isTemplatesEnabled: boolean = store.getters['settings/isTemplatesEnabled'];
if (!isTemplatesEnabled) {
return {name: VIEWS.NOT_FOUND};
}
return false;
}
const router = new Router({
mode: 'history',
// @ts-ignore
base: window.BASE_PATH === '/%BASE_PATH%/' ? '/' : window.BASE_PATH,
scrollBehavior(to, from, savedPosition) {
// saved position == null means the page is NOT visited from history (back button)
if (savedPosition === null && to.name === VIEWS.TEMPLATES && to.meta) {
// for templates view, reset scroll position in this case
to.meta.setScrollPosition(0);
}
},
routes: [
{
path: '/',
name: VIEWS.HOMEPAGE,
meta: {
getRedirect(store: Store<IRootState>) {
return { name: VIEWS.NEW_WORKFLOW };
},
permissions: {
allow: {
loginStatus: [LOGIN_STATUS.LoggedIn],
},
},
},
},
{
path: '/collections/:id',
name: VIEWS.COLLECTION,
components: {
default: TemplatesCollectionView,
sidebar: MainSidebar,
},
meta: {
templatesEnabled: true,
telemetry: {
getProperties(route: Route, store: Store<IRootState>) {
return {
collection_id: route.params.id,
wf_template_repo_session_id: store.getters['templates/currentSessionId'],
};
},
},
getRedirect: getTemplatesRedirect,
permissions: {
allow: {
loginStatus: [LOGIN_STATUS.LoggedIn],
},
},
},
},
{
path: '/execution/:id',
name: VIEWS.EXECUTION,
components: {
default: NodeView,
header: MainHeader,
sidebar: MainSidebar,
},
meta: {
nodeView: true,
permissions: {
allow: {
loginStatus: [LOGIN_STATUS.LoggedIn],
},
},
},
},
{
path: '/templates/:id',
name: VIEWS.TEMPLATE,
components: {
default: TemplatesWorkflowView,
sidebar: MainSidebar,
},
meta: {
templatesEnabled: true,
getRedirect: getTemplatesRedirect,
telemetry: {
getProperties(route: Route, store: Store<IRootState>) {
return {
template_id: route.params.id,
wf_template_repo_session_id: store.getters['templates/currentSessionId'],
};
},
},
permissions: {
allow: {
loginStatus: [LOGIN_STATUS.LoggedIn],
},
},
},
},
{
path: '/templates/',
name: VIEWS.TEMPLATES,
components: {
default: TemplatesSearchView,
sidebar: MainSidebar,
},
meta: {
templatesEnabled: true,
getRedirect: getTemplatesRedirect,
// Templates view remembers it's scroll position on back
scrollOffset: 0,
telemetry: {
getProperties(route: Route, store: Store<IRootState>) {
return {
wf_template_repo_session_id: store.getters['templates/currentSessionId'],
};
},
},
setScrollPosition(pos: number) {
this.scrollOffset = pos;
},
permissions: {
allow: {
loginStatus: [LOGIN_STATUS.LoggedIn],
},
},
},
},
{
path: '/credentials',
name: VIEWS.CREDENTIALS,
components: {
default: CredentialsView,
sidebar: MainSidebar,
},
meta: {
permissions: {
allow: {
loginStatus: [LOGIN_STATUS.LoggedIn],
},
},
},
},
{
path: '/workflow',
name: VIEWS.NEW_WORKFLOW,
components: {
default: NodeView,
header: MainHeader,
sidebar: MainSidebar,
},
meta: {
nodeView: true,
permissions: {
allow: {
loginStatus: [LOGIN_STATUS.LoggedIn],
},
},
},
},
{
path: '/workflow/:name',
name: VIEWS.WORKFLOW,
components: {
default: NodeView,
header: MainHeader,
sidebar: MainSidebar,
},
meta: {
nodeView: true,
permissions: {
allow: {
loginStatus: [LOGIN_STATUS.LoggedIn],
},
},
},
},
{
path: '/workflows/demo',
name: VIEWS.DEMO,
components: {
default: NodeView,
},
meta: {
permissions: {
allow: {
loginStatus: [LOGIN_STATUS.LoggedIn],
},
},
},
},
{
path: '/workflows/templates/:id',
name: VIEWS.TEMPLATE_IMPORT,
components: {
default: NodeView,
header: MainHeader,
sidebar: MainSidebar,
},
meta: {
templatesEnabled: true,
getRedirect: getTemplatesRedirect,
permissions: {
allow: {
loginStatus: [LOGIN_STATUS.LoggedIn],
},
},
},
},
{
path: '/signin',
name: VIEWS.SIGNIN,
components: {
default: SigninView,
},
meta: {
telemetry: {
pageCategory: 'auth',
},
permissions: {
allow: {
loginStatus: [LOGIN_STATUS.LoggedOut],
},
},
},
},
{
path: '/signup',
name: VIEWS.SIGNUP,
components: {
default: SignupView,
},
meta: {
telemetry: {
pageCategory: 'auth',
},
permissions: {
allow: {
loginStatus: [LOGIN_STATUS.LoggedOut],
},
},
},
},
{
path: '/setup',
name: VIEWS.SETUP,
components: {
default: SetupView,
},
meta: {
telemetry: {
pageCategory: 'auth',
},
permissions: {
allow: {
role: [ROLE.Default],
},
deny: {
shouldDeny: () => {
return store.getters['settings/isUserManagementEnabled'] === false;
},
},
},
},
},
{
path: '/forgot-password',
name: VIEWS.FORGOT_PASSWORD,
components: {
default: ForgotMyPasswordView,
},
meta: {
telemetry: {
pageCategory: 'auth',
},
permissions: {
allow: {
loginStatus: [LOGIN_STATUS.LoggedOut],
},
},
},
},
{
path: '/change-password',
name: VIEWS.CHANGE_PASSWORD,
components: {
default: ChangePasswordView,
},
meta: {
telemetry: {
pageCategory: 'auth',
},
permissions: {
allow: {
loginStatus: [LOGIN_STATUS.LoggedOut],
},
},
},
},
{
path: '/settings',
redirect: '/settings/personal',
},
{
path: '/settings/users',
name: VIEWS.USERS_SETTINGS,
components: {
default: SettingsUsersView,
},
meta: {
telemetry: {
pageCategory: 'settings',
getProperties(route: Route, store: Store<IRootState>) {
return {
feature: 'users',
};
},
},
permissions: {
allow: {
role: [ROLE.Default, ROLE.Owner],
},
deny: {
shouldDeny: () => {
return store.getters['settings/isUserManagementEnabled'] === false;
},
},
},
},
},
{
path: '/settings/personal',
name: VIEWS.PERSONAL_SETTINGS,
components: {
default: SettingsPersonalView,
},
meta: {
telemetry: {
pageCategory: 'settings',
getProperties(route: Route, store: Store<IRootState>) {
return {
feature: 'personal',
};
},
},
permissions: {
allow: {
loginStatus: [LOGIN_STATUS.LoggedIn],
},
deny: {
role: [ROLE.Default],
},
},
},
},
{
path: '/settings/api',
name: VIEWS.API_SETTINGS,
components: {
default: SettingsApiView,
},
meta: {
telemetry: {
pageCategory: 'settings',
getProperties(route: Route, store: Store<IRootState>) {
return {
feature: 'api',
};
},
},
permissions: {
allow: {
loginStatus: [LOGIN_STATUS.LoggedIn],
},
deny: {
shouldDeny: () => {
return store.getters['settings/isPublicApiEnabled'] === false;
},
},
},
},
},
{
path: '/settings/community-nodes',
name: VIEWS.COMMUNITY_NODES,
components: {
default: SettingsCommunityNodesView,
},
meta: {
telemetry: {
pageCategory: 'settings',
},
permissions: {
allow: {
role: [ROLE.Default, ROLE.Owner],
},
deny: {
shouldDeny: () => {
return store.getters['settings/isCommunityNodesFeatureEnabled'] === false;
},
},
},
},
},
{
path: '/settings/coming-soon/:featureId',
name: VIEWS.FAKE_DOOR,
component: SettingsFakeDoorView,
props: true,
meta: {
telemetry: {
pageCategory: 'settings',
getProperties(route: Route, store: Store<IRootState>) {
return {
feature: route.params['featureId'],
};
},
},
permissions: {
allow: {
loginStatus: [LOGIN_STATUS.LoggedIn],
},
},
},
},
{
path: '*',
name: VIEWS.NOT_FOUND,
component: ErrorView,
props: {
messageKey: 'error.pageNotFound',
errorCode: 404,
redirectTextKey: 'error.goBack',
redirectPage: VIEWS.HOMEPAGE,
},
meta: {
nodeView: true,
telemetry: {
disabled: true,
},
permissions: {
allow: {
// TODO: Once custom permissions are merged, this needs to be updated with index validation
loginStatus: [LOGIN_STATUS.LoggedIn, LOGIN_STATUS.LoggedOut],
},
},
},
},
] as IRouteConfig[],
});
export default router;