From 17dd0a25a7dc2fa6268ce68f4f2f5d78f78a1f23 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Thu, 19 Dec 2019 11:02:16 +0300 Subject: [PATCH 1/5] feat(core): fill the parentName property in flattedRoutes for child rotues --- .../core/src/lib/plugins/config.plugin.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/plugins/config.plugin.ts b/npm/ng-packs/packages/core/src/lib/plugins/config.plugin.ts index 621d1c39d1..bcd226c941 100644 --- a/npm/ng-packs/packages/core/src/lib/plugins/config.plugin.ts +++ b/npm/ng-packs/packages/core/src/lib/plugins/config.plugin.ts @@ -1,6 +1,13 @@ import { Inject, Injectable, InjectionToken } from '@angular/core'; import { Router, Routes } from '@angular/router'; -import { actionMatcher, InitState, NgxsNextPluginFn, NgxsPlugin, setValue, UpdateState } from '@ngxs/store'; +import { + actionMatcher, + InitState, + NgxsNextPluginFn, + NgxsPlugin, + setValue, + UpdateState, +} from '@ngxs/store'; import snq from 'snq'; import { ABP } from '../models'; import { organizeRoutes, getAbpRoutes } from '../utils/route-utils'; @@ -12,7 +19,10 @@ export const NGXS_CONFIG_PLUGIN_OPTIONS = new InjectionToken('NGXS_CONFIG_PLUGIN export class ConfigPlugin implements NgxsPlugin { private initialized = false; - constructor(@Inject(NGXS_CONFIG_PLUGIN_OPTIONS) private options: ABP.Root, private router: Router) {} + constructor( + @Inject(NGXS_CONFIG_PLUGIN_OPTIONS) private options: ABP.Root, + private router: Router, + ) {} handle(state: any, event: any, next: NgxsNextPluginFn) { const matches = actionMatcher(event); @@ -54,7 +64,9 @@ function transformRoutes(routes: Routes = [], wrappers: ABP.FullRoute[] = []): a routes .filter(route => route.component || route.loadChildren) .forEach(route => { - const abpPackage = abpRoutes.find(abp => abp.path.toLowerCase() === route.path.toLowerCase() && !abp.wrapper); + const abpPackage = abpRoutes.find( + abp => abp.path.toLowerCase() === route.path.toLowerCase() && !abp.wrapper, + ); const { length } = transformed; @@ -104,6 +116,7 @@ function flatRoutes(routes: ABP.FullRoute[]): ABP.FullRoute[] { return r.reduce((acc, val) => { let value: ABP.FullRoute[] = [val]; if (val.children) { + val.children = val.children.map(child => ({ ...child, parentName: val.name })); value = [val, ...flat(val.children)]; } From 15a82794ac5173bd080d3aec46efb43bfc0614ae Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Thu, 19 Dec 2019 11:05:35 +0300 Subject: [PATCH 2/5] fix(core): update flattedRoutes when patchRoute action dispatched --- .../packages/core/src/lib/states/config.state.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/states/config.state.ts b/npm/ng-packs/packages/core/src/lib/states/config.state.ts index d984a8d9b3..a20fdcdf40 100644 --- a/npm/ng-packs/packages/core/src/lib/states/config.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/config.state.ts @@ -246,10 +246,20 @@ export class ConfigState { ) { let routes: ABP.FullRoute[] = getState().routes; - const index = routes.findIndex(route => route.name === name); - routes = patchRouteDeep(routes, name, newValue); + const flattedRoutes = getState().flattedRoutes; + const index = flattedRoutes.findIndex(route => route.name === name); + + if (index > -1) { + flattedRoutes[index] = newValue as ABP.FullRoute; + } + + return patchState({ + routes, + flattedRoutes, + }); + } return patchState({ routes, }); From 2b6b05c6392d47c36c4d53171084e14e0b203619 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Thu, 19 Dec 2019 11:14:17 +0300 Subject: [PATCH 3/5] feat(core): add AddRoute action resolves #2186 --- .../core/src/lib/actions/config.actions.ts | 5 + .../core/src/lib/states/config.state.ts | 144 ++++++++++++------ 2 files changed, 102 insertions(+), 47 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts b/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts index 3e33420578..1992de7888 100644 --- a/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts +++ b/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts @@ -8,3 +8,8 @@ export class PatchRouteByName { export class GetAppConfiguration { static readonly type = '[Config] Get App Configuration'; } + +export class AddRoute { + static readonly type = '[Config] Add Route'; + constructor(public payload: Omit) {} +} diff --git a/npm/ng-packs/packages/core/src/lib/states/config.state.ts b/npm/ng-packs/packages/core/src/lib/states/config.state.ts index a20fdcdf40..a32a101de2 100644 --- a/npm/ng-packs/packages/core/src/lib/states/config.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/config.state.ts @@ -1,18 +1,8 @@ -import { - Action, - createSelector, - Selector, - State, - StateContext, - Store, -} from '@ngxs/store'; +import { Action, createSelector, Selector, State, StateContext, Store } from '@ngxs/store'; import { of } from 'rxjs'; import { switchMap, tap } from 'rxjs/operators'; import snq from 'snq'; -import { - GetAppConfiguration, - PatchRouteByName, -} from '../actions/config.actions'; +import { GetAppConfiguration, PatchRouteByName, AddRoute } from '../actions/config.actions'; import { SetLanguage } from '../actions/session.actions'; import { ABP } from '../models/common'; import { Config } from '../models/config'; @@ -83,12 +73,9 @@ export class ConfigState { } static getApiUrl(key?: string) { - const selector = createSelector( - [ConfigState], - (state: Config.State): string => { - return state.environment.apis[key || 'default'].url; - }, - ); + const selector = createSelector([ConfigState], (state: Config.State): string => { + return state.environment.apis[key || 'default'].url; + }); return selector; } @@ -104,18 +91,12 @@ export class ConfigState { const selector = createSelector([ConfigState], (state: Config.State) => { if (keyword) { const keys = snq( - () => - Object.keys(state.setting.values).filter( - key => key.indexOf(keyword) > -1, - ), + () => Object.keys(state.setting.values).filter(key => key.indexOf(keyword) > -1), [], ); if (keys.length) { - return keys.reduce( - (acc, key) => ({ ...acc, [key]: state.setting.values[key] }), - {}, - ); + return keys.reduce((acc, key) => ({ ...acc, [key]: state.setting.values[key] }), {}); } } @@ -125,32 +106,29 @@ export class ConfigState { } static getGrantedPolicy(key: string) { - const selector = createSelector( - [ConfigState], - (state: Config.State): boolean => { - if (!key) return true; - const getPolicy = k => snq(() => state.auth.grantedPolicies[k], false); + const selector = createSelector([ConfigState], (state: Config.State): boolean => { + if (!key) return true; + const getPolicy = k => snq(() => state.auth.grantedPolicies[k], false); - const orRegexp = /\|\|/g; - const andRegexp = /&&/g; + const orRegexp = /\|\|/g; + const andRegexp = /&&/g; - if (orRegexp.test(key)) { - const keys = key.split('||').filter(k => !!k); + if (orRegexp.test(key)) { + const keys = key.split('||').filter(k => !!k); - if (keys.length !== 2) return false; + if (keys.length !== 2) return false; - return getPolicy(keys[0].trim()) || getPolicy(keys[1].trim()); - } else if (andRegexp.test(key)) { - const keys = key.split('&&').filter(k => !!k); + return getPolicy(keys[0].trim()) || getPolicy(keys[1].trim()); + } else if (andRegexp.test(key)) { + const keys = key.split('&&').filter(k => !!k); - if (keys.length !== 2) return false; + if (keys.length !== 2) return false; - return getPolicy(keys[0].trim()) && getPolicy(keys[1].trim()); - } + return getPolicy(keys[0].trim()) && getPolicy(keys[1].trim()); + } - return getPolicy(key); - }, - ); + return getPolicy(key); + }); return selector; } @@ -225,8 +203,7 @@ export class ConfigState { }), ), switchMap(configuration => { - let defaultLang: string = - configuration.setting.values['Abp.Localization.DefaultLanguage']; + let defaultLang: string = configuration.setting.values['Abp.Localization.DefaultLanguage']; if (defaultLang.includes(';')) { defaultLang = defaultLang.split(';')[0]; @@ -260,8 +237,59 @@ export class ConfigState { flattedRoutes, }); } + + @Action(AddRoute) + addRoute({ patchState, getState }: StateContext, { payload }: AddRoute) { + let routes: ABP.FullRoute[] = getState().routes; + const flattedRoutes = getState().flattedRoutes; + const route: ABP.FullRoute = { ...payload }; + + if (route.parentName) { + const index = flattedRoutes.findIndex(r => r.name === route.parentName); + + if (index < 0) return; + + const parent = flattedRoutes[index]; + if (parent.url.replace('/', '')) { + route.url = `${parent.url}/${route.path}`; + } else { + route.url = `/${route.path}`; + } + + route.order = route.order || route.order === 0 ? route.order : parent.children.length; + parent.children = [...(parent.children || []), route].sort((a, b) => a.order - b.order); + + flattedRoutes[index] = parent; + flattedRoutes.push(route); + + let parentName = parent.name; + const parentNameArr = [parentName]; + + while (parentName) { + parentName = snq(() => flattedRoutes.find(r => r.name === parentName).parentName); + + if (parentName) { + parentNameArr.unshift(parentName); + } + } + + routes = updateRouteDeep(routes, parentNameArr, parent); + } else { + route.url = `/${route.path}`; + + if (route.order || route.order === 0) { + routes = [...routes, route].sort((a, b) => a.order - b.order); + } else { + route.order = routes.length; + routes = [...routes, route]; + } + + flattedRoutes.push(route); + } + return patchState({ routes, + flattedRoutes, }); } } @@ -305,3 +333,25 @@ function patchRouteDeep( return organizeRoutes(routes); } + +function updateRouteDeep( + routes: ABP.FullRoute[], + parentNameArr: string[], + newValue: ABP.FullRoute, + parentIndex = 0, +) { + const index = routes.findIndex(route => route.name === parentNameArr[parentIndex]); + + if (parentIndex === parentNameArr.length - 1) { + routes[index] = newValue; + } else { + routes[index].children = updateRouteDeep( + routes[index].children, + parentNameArr, + newValue, + parentIndex + 1, + ); + } + + return routes; +} From da3a2cd7a63279b7baaa7ec6e1d6054e925ae5be Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Thu, 19 Dec 2019 11:45:41 +0300 Subject: [PATCH 4/5] tests(core): update config.plugin.spec.ts --- .../core/src/lib/tests/config.plugin.spec.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/npm/ng-packs/packages/core/src/lib/tests/config.plugin.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/config.plugin.spec.ts index 85730a3d68..a5bc046012 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/config.plugin.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/config.plugin.spec.ts @@ -170,6 +170,7 @@ const expectedState = { path: 'users', name: 'AbpIdentity::Users', order: 1, + parentName: 'AbpIdentity::Menu:IdentityManagement', requiredPolicy: 'AbpIdentity.Users', url: '/identity/users', }, @@ -177,6 +178,7 @@ const expectedState = { path: 'roles', name: 'AbpIdentity::Roles', order: 2, + parentName: 'AbpIdentity::Menu:IdentityManagement', requiredPolicy: 'AbpIdentity.Roles', url: '/identity/roles', }, @@ -194,6 +196,7 @@ const expectedState = { path: 'tenants', name: 'AbpTenantManagement::Tenants', order: 1, + parentName: 'AbpTenantManagement::Menu:TenantManagement', requiredPolicy: 'AbpTenantManagement.Tenants', url: '/tenant-management/tenants', }, @@ -215,6 +218,7 @@ const expectedState = { path: 'users', name: 'AbpIdentity::Users', order: 1, + parentName: 'AbpIdentity::Menu:IdentityManagement', requiredPolicy: 'AbpIdentity.Users', url: '/identity/users', }, @@ -222,6 +226,7 @@ const expectedState = { path: 'roles', name: 'AbpIdentity::Roles', order: 2, + parentName: 'AbpIdentity::Menu:IdentityManagement', requiredPolicy: 'AbpIdentity.Roles', url: '/identity/roles', }, @@ -232,6 +237,7 @@ const expectedState = { path: 'users', name: 'AbpIdentity::Users', order: 1, + parentName: 'AbpIdentity::Menu:IdentityManagement', requiredPolicy: 'AbpIdentity.Users', url: '/identity/users', }, @@ -239,6 +245,7 @@ const expectedState = { path: 'roles', name: 'AbpIdentity::Roles', order: 2, + parentName: 'AbpIdentity::Menu:IdentityManagement', requiredPolicy: 'AbpIdentity.Roles', url: '/identity/roles', }, @@ -253,6 +260,7 @@ const expectedState = { path: 'tenants', name: 'AbpTenantManagement::Tenants', order: 1, + parentName: 'AbpTenantManagement::Menu:TenantManagement', requiredPolicy: 'AbpTenantManagement.Tenants', url: '/tenant-management/tenants', }, @@ -264,6 +272,7 @@ const expectedState = { path: 'tenants', name: 'AbpTenantManagement::Tenants', order: 1, + parentName: 'AbpTenantManagement::Menu:TenantManagement', requiredPolicy: 'AbpTenantManagement.Tenants', url: '/tenant-management/tenants', }, @@ -277,12 +286,14 @@ const expectedState = { path: 'login', name: 'AbpAccount::Login', order: 1, + parentName: 'AbpAccount::Menu:Account', url: '/account/login', }, { path: 'register', name: 'AbpAccount::Register', order: 2, + parentName: 'AbpAccount::Menu:Account', url: '/account/register', }, ], @@ -293,12 +304,14 @@ const expectedState = { path: 'login', name: 'AbpAccount::Login', order: 1, + parentName: 'AbpAccount::Menu:Account', url: '/account/login', }, { path: 'register', name: 'AbpAccount::Register', order: 2, + parentName: 'AbpAccount::Menu:Account', url: '/account/register', }, ], From 6a2b20f23378c7837c7141a4ff8bad11b45c6fad Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Thu, 19 Dec 2019 17:48:38 +0300 Subject: [PATCH 5/5] docs(core): add usage information to AddRoute action --- npm/ng-packs/packages/core/src/lib/actions/config.actions.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts b/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts index 1992de7888..7e7f932e24 100644 --- a/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts +++ b/npm/ng-packs/packages/core/src/lib/actions/config.actions.ts @@ -9,6 +9,9 @@ export class GetAppConfiguration { static readonly type = '[Config] Get App Configuration'; } +/** + * @see usage: https://github.com/abpframework/abp/pull/2425#issue-355018812 + */ export class AddRoute { static readonly type = '[Config] Add Route'; constructor(public payload: Omit) {}