diff --git a/apps/api/tsconfig.app.json b/apps/api/tsconfig.app.json index 73ae2470c..9bb954ba8 100644 --- a/apps/api/tsconfig.app.json +++ b/apps/api/tsconfig.app.json @@ -4,7 +4,7 @@ "outDir": "../../dist/apps/api", "module": "commonjs", "target": "es6", - "types": [] + "types": ["node"] }, "exclude": ["**/*.spec.ts"], "include": ["**/*.ts"] diff --git a/apps/backend/tsconfig.app.json b/apps/backend/tsconfig.app.json index b5ef1f411..38afccadf 100644 --- a/apps/backend/tsconfig.app.json +++ b/apps/backend/tsconfig.app.json @@ -2,7 +2,7 @@ "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc/apps/backend", - "types": [] + "types": ["node"] }, "exclude": ["**/*.spec.ts"], "include": ["**/*.ts"] diff --git a/apps/webapp/src/app/app.module.ts b/apps/webapp/src/app/app.module.ts index 62345e8c7..8ed2f9eec 100644 --- a/apps/webapp/src/app/app.module.ts +++ b/apps/webapp/src/app/app.module.ts @@ -28,7 +28,7 @@ export class MyHammerConfig extends HammerGestureConfig { { path: '', redirectTo: 'home', pathMatch: 'full' }, { path: 'home', loadChildren: '@ngx-starter-kit/home#HomeModule', data: { preload: true } }, { path: 'dashboard', loadChildren: '@ngx-starter-kit/dashboard#DashboardModule', data: { preload: true } }, - { path: '404', loadChildren: '@ngx-starter-kit/not-found#NotFoundModule', data: { title: '404' } }, + { path: '404', loadChildren: '@ngx-starter-kit/not-found#NotFoundModule', data: { title: '404', preload: false } }, // 404 should be last { path: '**', redirectTo: '404', pathMatch: 'full' }, ], diff --git a/libs/animations/src/lib/route.animation.ts b/libs/animations/src/lib/route.animation.ts index 516b9c4f5..0fc54f5be 100644 --- a/libs/animations/src/lib/route.animation.ts +++ b/libs/animations/src/lib/route.animation.ts @@ -32,41 +32,29 @@ export const routeAnimation = trigger('routeAnimation', [ ]), ]); -export const routeAnimation2 = trigger('routeAnimation', [ - transition('void => *', [ - style({ - opacity: 0, - }), - animate( - '400ms 150ms ease-in-out', - style({ - opacity: 1, - }), - ), - ]), -]); - export const hierarchicalRouteAnimation = trigger('routeAnimation', [ - transition('1 => 2, 2 => 3', [ + transition(':increment', [ style({ height: '!' }), - query(':enter', style({ transform: 'translateX(100%)' })), - query(':enter, :leave', style({ position: 'absolute', top: 0, left: 0, right: 0 })), + query(':enter', style({ transform: 'translateX(100%)' }), {optional: true}), + query(':enter, :leave', style({ position: 'absolute', top: 0, left: 0, right: 0 }), {optional: true}), // animate the leave page away group([ - query(':leave', [animate('0.3s cubic-bezier(.35,0,.25,1)', style({ transform: 'translateX(-100%)' }))]), + query(':leave', [animate('0.3s cubic-bezier(.35,0,.25,1)', style({ transform: 'translateX(-100%)' }))], {optional: true}), // and now reveal the enter - query(':enter', animate('0.3s cubic-bezier(.35,0,.25,1)', style({ transform: 'translateX(0)' }))), + query(':enter', animate('0.3s cubic-bezier(.35,0,.25,1)', style({ transform: 'translateX(0)' })), {optional: true}), + // query('@fadeAnimation', animateChild(), {optional: true}), ]), ]), - transition('3 => 2, 2 => 1', [ + transition(':decrement', [ style({ height: '!' }), - query(':enter', style({ transform: 'translateX(-100%)' })), - query(':enter, :leave', style({ position: 'absolute', top: 0, left: 0, right: 0 })), + query(':enter', style({ transform: 'translateX(-100%)' }), {optional: true}), + query(':enter, :leave', style({ position: 'absolute', top: 0, left: 0, right: 0 }), {optional: true}), // animate the leave page away group([ - query(':leave', [animate('0.3s cubic-bezier(.35,0,.25,1)', style({ transform: 'translateX(100%)' }))]), + query(':leave', [animate('0.3s cubic-bezier(.35,0,.25,1)', style({ transform: 'translateX(100%)' }))], {optional: true}), // and now reveal the enter - query(':enter', animate('0.3s cubic-bezier(.35,0,.25,1)', style({ transform: 'translateX(0)' }))), + query(':enter', animate('0.3s cubic-bezier(.35,0,.25,1)', style({ transform: 'translateX(0)' })), {optional: true}), + // query('@fadeAnimation', animateChild() , {optional: true}), ]), ]), ]); diff --git a/libs/core/src/index.ts b/libs/core/src/index.ts index 91606ff75..c90ea2c32 100644 --- a/libs/core/src/index.ts +++ b/libs/core/src/index.ts @@ -3,3 +3,4 @@ export { PageTitleService } from './lib/services/page-title.service'; export { ServiceWorkerService } from './lib/services/service-worker.service'; export { MediaQueryService } from './lib/services/media-query.service'; export { DeepLinkService } from './lib/services/deep-link.service'; +export { RouterStateData} from './lib/state/custom-router-state.serializer'; diff --git a/libs/core/src/lib/core.module.ts b/libs/core/src/lib/core.module.ts index ccc08fe13..b63a3aaed 100644 --- a/libs/core/src/lib/core.module.ts +++ b/libs/core/src/lib/core.module.ts @@ -7,7 +7,7 @@ import { NgxsModule } from '@ngxs/store'; import { NgxsFormPluginModule } from '@ngxs/form-plugin'; import { NgxPageScrollModule } from 'ngx-page-scroll'; import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin'; -import { NgxsRouterPluginModule } from '@ngxs/router-plugin'; +import { NgxsRouterPluginModule, RouterStateSerializer } from '@ngxs/router-plugin'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { library } from '@fortawesome/fontawesome-svg-core'; import { faTwitter, faGithub, faGoogle } from '@fortawesome/free-brands-svg-icons'; @@ -22,6 +22,7 @@ import { PreferenceState } from './state/preference.state'; import { InMemoryDataService } from './services/in-memory-data.service'; import { ErrorInterceptor } from './interceptors/error.interceptor'; import { JwtInterceptor } from './interceptors/jwt.interceptor'; +import { CustomRouterStateSerializer } from './state/custom-router-state.serializer'; // Noop handler for factory function export function noop() { @@ -79,6 +80,10 @@ library.add(faTwitter, faGithub, faGoogle); deps: [EventBus], multi: true, }, + { + provide: RouterStateSerializer, + useClass: CustomRouterStateSerializer, + }, ], }) export class CoreModule { diff --git a/libs/core/src/lib/services/page-title.service.ts b/libs/core/src/lib/services/page-title.service.ts index 68840df38..3cd030d07 100644 --- a/libs/core/src/lib/services/page-title.service.ts +++ b/libs/core/src/lib/services/page-title.service.ts @@ -1,7 +1,8 @@ import { Injectable } from '@angular/core'; import { Title } from '@angular/platform-browser'; -import { ActivatedRouteSnapshot, NavigationEnd, Router, RouterState } from '@angular/router'; -import { filter } from 'rxjs/operators'; +import { Store } from '@ngxs/store'; +import { RouterState } from '@ngxs/router-plugin'; +import { RouterStateData } from '../state/custom-router-state.serializer'; declare var ga: any; /** @@ -12,30 +13,20 @@ declare var ga: any; }) export class PageTitleService { private readonly defaultTitle; - public titleSet: Set; - constructor(private router: Router, private bodyTitle: Title) { + constructor(private store: Store, private bodyTitle: Title) { this.defaultTitle = bodyTitle.getTitle() || 'WebApp'; - // Automatically set pageTitle from route data - router.events.pipe(filter((event: any) => event instanceof NavigationEnd)).subscribe((n: NavigationEnd) => { - const titleSet = new Set(); - let root = this.router.routerState.snapshot.root; - do { - root = root.children[0]; - if (root.data['title']) { - titleSet.add(root.data['title']); - } - } while (root.children.length > 0); - - this.titleSet = titleSet; + // Automatically set pageTitle from router state data + store.select(RouterState.state).subscribe((routerStateData: RouterStateData) => { + console.log(routerStateData); bodyTitle.setTitle( - `${Array.from(titleSet) + `${Array.from(routerStateData.breadcrumbs.keys()) .reverse() .join(' | ')} | ${this.defaultTitle}`, ); - ga('send', 'pageview', n.urlAfterRedirects); + ga('send', 'pageview', routerStateData.url); }); } } diff --git a/libs/core/src/lib/state/custom-router-state.serializer.ts b/libs/core/src/lib/state/custom-router-state.serializer.ts index 29f8a0df0..b0a05bf19 100644 --- a/libs/core/src/lib/state/custom-router-state.serializer.ts +++ b/libs/core/src/lib/state/custom-router-state.serializer.ts @@ -1,25 +1,32 @@ -import { Params, RouterStateSnapshot } from '@angular/router'; -import { NgxsModule } from '@ngxs/store'; -import { NgxsRouterPluginModule, RouterStateSerializer } from '@ngxs/router-plugin'; +import { Params, RouterStateSnapshot, UrlSegment } from '@angular/router'; +import { RouterStateSerializer } from '@ngxs/router-plugin'; -export interface RouterStateParams { +export interface RouterStateData { url: string; params: Params; queryParams: Params; + breadcrumbs: Map; + data: any; } -// Map the router snapshot to { url, params, queryParams } -export class CustomRouterStateSerializer implements RouterStateSerializer { - serialize(routerState: RouterStateSnapshot): RouterStateParams { +// Map the router snapshot to { url, params, queryParams, titleSet } +export class CustomRouterStateSerializer implements RouterStateSerializer { + serialize(routerState: RouterStateSnapshot): RouterStateData { const { url, root: { queryParams }, } = routerState; let { root: route } = routerState; + + const breadcrumbs = new Map(); while (route.firstChild) { route = route.firstChild; + if (route.data['title']) { + breadcrumbs.set(route.data['title'], route.pathFromRoot.flatMap(segment => segment.url).join('/')); + } } const { params } = route; - return { url, params, queryParams }; + const { data } = route; + return { url, params, queryParams, breadcrumbs, data }; } } diff --git a/libs/dashboard/src/lib/containers/dashboard-layout/dashboard-layout.component.html b/libs/dashboard/src/lib/containers/dashboard-layout/dashboard-layout.component.html index c8af8675f..46e948bef 100644 --- a/libs/dashboard/src/lib/containers/dashboard-layout/dashboard-layout.component.html +++ b/libs/dashboard/src/lib/containers/dashboard-layout/dashboard-layout.component.html @@ -13,8 +13,9 @@ -
- +
+ +
diff --git a/libs/dashboard/src/lib/containers/dashboard-layout/dashboard-layout.component.ts b/libs/dashboard/src/lib/containers/dashboard-layout/dashboard-layout.component.ts index 1a2ed08bd..f9d711dae 100644 --- a/libs/dashboard/src/lib/containers/dashboard-layout/dashboard-layout.component.ts +++ b/libs/dashboard/src/lib/containers/dashboard-layout/dashboard-layout.component.ts @@ -2,17 +2,20 @@ import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { Subscription } from 'rxjs'; import { MediaChange, ObservableMedia } from '@angular/flex-layout'; import { NavigationEnd, Router } from '@angular/router'; -import { routeAnimation } from '@ngx-starter-kit/animations'; +import { routeAnimation, hierarchicalRouteAnimation } from '@ngx-starter-kit/animations'; import { Actions, Store } from '@ngxs/store'; import { ConnectWebSocket, DisconnectWebSocket } from '@ngx-starter-kit/socketio-plugin'; import { OAuthService } from 'angular-oauth2-oidc'; import { environment } from '@env/environment'; +import { RouterState } from '@ngxs/router-plugin'; +import { map } from 'rxjs/operators'; @Component({ selector: 'ngx-dashboard-layout', templateUrl: './dashboard-layout.component.html', styleUrls: ['./dashboard-layout.component.scss'], animations: [routeAnimation], + // animations: [hierarchicalRouteAnimation], // encapsulation: ViewEncapsulation.None }) export class DashboardLayoutComponent implements OnInit, OnDestroy { @@ -26,6 +29,8 @@ export class DashboardLayoutComponent implements OnInit, OnDestroy { sidenavOpen = true; sidenavMode = 'side'; isMobile = false; + crumbs$; + depth$; constructor( private router: Router, @@ -36,6 +41,12 @@ export class DashboardLayoutComponent implements OnInit, OnDestroy { ) {} ngOnInit() { + this.crumbs$ = this.store + .select(RouterState.state) + .pipe(map(state => Array.from(state.breadcrumbs, ([key, value]) => ({ name: key, link: '/' + value })))); + + this.depth$ = this.store.select(RouterState.state).pipe(map(state => state.data.depth)); + this._mediaSubscription = this.media.subscribe((change: MediaChange) => { const isMobile = change.mqAlias === 'xs' || change.mqAlias === 'sm'; @@ -70,8 +81,8 @@ export class DashboardLayoutComponent implements OnInit, OnDestroy { this.store.dispatch(new DisconnectWebSocket()); } - getRouteAnimation(outlet) { - return outlet.activatedRouteData['animation'] || 'one'; + getRouteDepth(outlet) { + return outlet.activatedRouteData['depth'] || 1; // return outlet.isActivated ? outlet.activatedRoute : '' } } diff --git a/libs/dashboard/src/lib/dashboard.module.ts b/libs/dashboard/src/lib/dashboard.module.ts index 0859e5cca..dbe02d352 100644 --- a/libs/dashboard/src/lib/dashboard.module.ts +++ b/libs/dashboard/src/lib/dashboard.module.ts @@ -29,7 +29,7 @@ import { environment } from '@env/environment'; path: '', component: DashboardLayoutComponent, canActivate: [AuthGuard], - data: { title: 'Dashboard', animation: 'dashboard' }, + data: { title: 'Dashboard', depth: 1 }, children: [ { path: 'overview', @@ -39,17 +39,17 @@ import { environment } from '@env/environment'; { path: '', loadChildren: '@ngx-starter-kit/widgets#WidgetsModule', - data: { title: 'Widgets', animation: 'overview', preload: true }, + data: { title: 'Widgets', preload: true }, }, { path: 'grid', loadChildren: '@ngx-starter-kit/grid#GridModule', - data: { title: 'Grid', animation: 'grid', preload: true }, + data: { title: 'Grid', depth: 2, preload: false }, }, { path: 'experiments', loadChildren: '@ngx-starter-kit/experiments#ExperimentsModule', - data: { title: 'Experiments', animation: 'experiments' }, + data: { title: 'Experiments', depth: 2, preload: false }, }, ], }, diff --git a/libs/experiments/src/lib/experiments.module.ts b/libs/experiments/src/lib/experiments.module.ts index 967db364c..c9e70cbf4 100644 --- a/libs/experiments/src/lib/experiments.module.ts +++ b/libs/experiments/src/lib/experiments.module.ts @@ -41,56 +41,56 @@ registerPlugin(FilePondPluginFileValidateType, FilepondPluginFileValidateSize, F ImageComparisonModule, RouterModule.forChild([ /* {path: '', pathMatch: 'full', component: InsertYourComponentHere} */ - { path: '', redirectTo: 'animations', pathMatch: 'full', data: { animation: 'experiments' } }, + { path: '', redirectTo: 'animations', pathMatch: 'full' }, { path: 'animations', component: AnimationsComponent, - data: { title: 'Animations', animations: 'animations' }, + data: { title: 'Animations', depth: 2 }, }, { path: 'file-upload', component: FileUploadComponent, - data: { title: 'File Upload', animation: 'file-upload' }, + data: { title: 'File Upload', depth: 3 }, }, { path: 'context-menu', component: ContextMenuComponent, - data: { title: 'Context Menu', animation: 'context-menu' }, + data: { title: 'Context Menu', depth: 3 }, }, { path: 'virtual-scroll', component: VirtualScrollComponent, - data: { title: 'Virtual Scroll', animation: 'virtual-scroll' }, + data: { title: 'Virtual Scroll', depth: 3 }, }, { path: 'table', component: StickyTableComponent, - data: { title: 'Sticky Table', animation: 'sticky-table' }, + data: { title: 'Sticky Table', depth: 3 }, }, { path: 'clap', component: ClapButtonComponent, - data: { title: 'Clap', animation: 'clap' }, + data: { title: 'Clap', depth: 3 }, }, { path: 'led', component: LedDemoComponent, - data: { title: 'Led', animation: 'led' }, + data: { title: 'Led', depth: 3 }, }, { path: 'knob', component: KnobDemoComponent, - data: { title: 'Knob', animation: 'Knob' }, + data: { title: 'Knob', depth: 3 }, }, { path: 'image-comp', component: ImageCompComponent, - data: { title: 'ImageComp', animation: 'imagecomp' }, + data: { title: 'ImageComp', depth: 3 }, }, { path: 'layout', component: LayoutComponent, - data: { title: 'Layout', animation: 'layout' }, + data: { title: 'Layout', depth: 3 }, }, ]), ], diff --git a/libs/grid/src/lib/grid.module.ts b/libs/grid/src/lib/grid.module.ts index a0b4797a8..9a5f63f07 100644 --- a/libs/grid/src/lib/grid.module.ts +++ b/libs/grid/src/lib/grid.module.ts @@ -3,7 +3,7 @@ import { RouterModule } from '@angular/router'; import { SharedModule } from '@ngx-starter-kit/shared'; import { AppConfirmModule } from '@ngx-starter-kit/app-confirm'; import { DragDropModule } from '@angular/cdk/drag-drop'; -import { NgxPipesModule } from '@ngx-starter-kit/ngx-pipes'; +import { TruncateModule, HelperModule } from '@ngx-starter-kit/ngx-utils'; import { AccountsGridListComponent } from './containers/accounts-grid-list/accounts-grid-list.component'; import { AccountsTableComponent } from './containers/accounts-table/accounts-table.component'; import { AccountService } from './services/account.service'; @@ -16,26 +16,27 @@ import { AccountEditComponent } from './components/account-edit/account-edit.com SharedModule, DragDropModule, AppConfirmModule, - NgxPipesModule, + TruncateModule, + HelperModule, RouterModule.forChild([ /* {path: '', pathMatch: 'full', component: InsertYourComponentHere} */ - { path: '', redirectTo: 'crud-table', pathMatch: 'full', data: { animation: 'grid' } }, + { path: '', redirectTo: 'crud-table', pathMatch: 'full' }, { path: 'crud-table', component: AccountsTableComponent, - data: { title: 'Accounts Table', animation: 'accounts-table' }, + data: { title: 'Accounts', depth: 2 }, children: [ { path: ':id', component: AccountDetailComponent, - data: { title: 'Account Detail', animation: 'account-detail' }, + data: { title: 'Account Detail'}, }, ], }, { path: 'grid-list', component: AccountsGridListComponent, - data: { title: 'Accounts Grid List', animation: 'accounts-grid-list' }, + data: { title: 'Accounts Grid-List', depth: 3 }, }, ]), ], diff --git a/libs/grid/src/lib/services/account.service.spec.ts b/libs/grid/src/lib/services/account.service.spec.ts index dd417b62d..992796a85 100644 --- a/libs/grid/src/lib/services/account.service.spec.ts +++ b/libs/grid/src/lib/services/account.service.spec.ts @@ -9,7 +9,7 @@ describe('AccountService', () => { }); }); - it('should be created', inject([Account1Service], (service: Account1Service) => { + it('should be created', inject([AccountService], (service: AccountService) => { expect(service).toBeTruthy(); })); });