diff --git a/viewer/src/app/app.component.ts b/viewer/src/app/app.component.ts index aa39bef..85b9ad4 100644 --- a/viewer/src/app/app.component.ts +++ b/viewer/src/app/app.component.ts @@ -1,6 +1,11 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { RouterModule } from '@angular/router'; +import { + ActivatedRoute, + NavigationEnd, + Router, + RouterModule, +} from '@angular/router'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatDialog } from '@angular/material/dialog'; import { InfoComponent } from './info/info.component'; @@ -10,6 +15,9 @@ import { FlexLayoutServerModule } from '@ngbracket/ngx-layout/server'; import { MatMenuModule } from '@angular/material/menu'; import { MatIconModule } from '@angular/material/icon'; import { MatDividerModule } from '@angular/material/divider'; +import { Title, Meta } from '@angular/platform-browser'; +import { filter, switchMap, map } from 'rxjs'; +import { SeoInformation } from './seo-resolver'; @Component({ selector: 'app-root', @@ -31,9 +39,60 @@ import { MatDividerModule } from '@angular/material/divider'; /** * The main component of the application */ -export class AppComponent { - constructor(private dialog: MatDialog) {} +export class AppComponent implements OnInit { + constructor( + private titleService: Title, + private metaService: Meta, + private router: Router, + private activatedRoute: ActivatedRoute, + private dialog: MatDialog + ) {} + showInfo() { this.dialog.open(InfoComponent); } + + ngOnInit() { + this.router.events + .pipe( + filter((event) => event instanceof NavigationEnd), + switchMap(() => { + let route = this.activatedRoute; + while (route.firstChild) { + route = route.firstChild; + } + return route.data; + }), + map((data) => (data['seo'] as SeoInformation) ?? data) + ) + .subscribe((information) => { + if (information) { + this.titleService.setTitle(information.title); + this.metaService.updateTag({ + name: 'description', + content: `Description for ${information.description}`, + }); + this.metaService.updateTag({ + property: 'og:title', + content: information.title, + }); + this.metaService.updateTag({ + property: 'og:description', + content: `Description for ${information.description}`, + }); + if (information.image) { + this.metaService.updateTag({ + property: 'og:image', + content: information.image, + }); + } else { + this.metaService.removeTag('property="og:image"'); + } + this.metaService.updateTag({ + property: 'og:url', + content: this.router.url, + }); + } + }); + } } diff --git a/viewer/src/app/app.routes.ts b/viewer/src/app/app.routes.ts index eb39c0e..fa42205 100644 --- a/viewer/src/app/app.routes.ts +++ b/viewer/src/app/app.routes.ts @@ -6,12 +6,13 @@ import { CaseStudiesShowComponent } from './case-studies/case-studies-show/case- import { DependenciesListComponent } from './dependencies/dependencies-list/dependencies-list.component'; import { DependenciesShowComponent } from './dependencies/dependencies-show/dependencies-show.component'; import { HomeComponent } from './home/home.component'; -import { TitleResolver } from './title-resolver'; +import { SeoResolver } from './seo-resolver'; export const routes: Routes = [ { path: '', component: HomeComponent, + data: { title: 'Wallet and Agent Overview' }, }, { path: 'wallets', @@ -19,12 +20,12 @@ export const routes: Routes = [ { path: '', component: WalletsListComponent, - title: 'Wallets', + data: { title: 'Wallets' }, }, { path: ':id', component: WalletsShowComponent, - title: TitleResolver, + resolve: { seo: SeoResolver }, }, ], }, @@ -34,12 +35,12 @@ export const routes: Routes = [ { path: '', component: CaseStudiesListComponent, - title: 'Case Studies', + data: { title: 'Case Studies' }, }, { path: ':id', component: CaseStudiesShowComponent, - title: TitleResolver, + resolve: { seo: SeoResolver }, }, ], }, @@ -49,12 +50,12 @@ export const routes: Routes = [ { path: '', component: DependenciesListComponent, - title: 'Dependencies', + data: { title: 'Dependencies' }, }, { path: ':id', component: DependenciesShowComponent, - title: TitleResolver, + resolve: { seo: SeoResolver }, }, ], }, diff --git a/viewer/src/app/dependencies/dependencies-list-embedded/dependencies-list-embedded.component.ts b/viewer/src/app/dependencies/dependencies-list-embedded/dependencies-list-embedded.component.ts index 211db3a..d4bc4e0 100644 --- a/viewer/src/app/dependencies/dependencies-list-embedded/dependencies-list-embedded.component.ts +++ b/viewer/src/app/dependencies/dependencies-list-embedded/dependencies-list-embedded.component.ts @@ -24,6 +24,7 @@ import { DependencyFilter, } from '../dependencies-filter/dependencies-filter.component'; import { DependenciesAddComponent } from '../dependencies-add/dependencies-add.component'; +import { FlexLayoutServerModule } from '@ngbracket/ngx-layout/server'; type DependenciesColumn = keyof typeof schema.properties | 'wallets'; @@ -40,6 +41,7 @@ type DependenciesColumn = keyof typeof schema.properties | 'wallets'; MatTooltipModule, MatSortModule, FlexLayoutModule, + FlexLayoutServerModule, MatDialogModule, MatChipsModule, NgOptimizedImage, diff --git a/viewer/src/app/title-resolver.ts b/viewer/src/app/seo-resolver.ts similarity index 54% rename from viewer/src/app/title-resolver.ts rename to viewer/src/app/seo-resolver.ts index 84d1b63..2408576 100644 --- a/viewer/src/app/title-resolver.ts +++ b/viewer/src/app/seo-resolver.ts @@ -1,43 +1,58 @@ import { Injectable } from '@angular/core'; -import { - Resolve, - ActivatedRouteSnapshot, - RouterStateSnapshot, -} from '@angular/router'; +import { Resolve, ActivatedRouteSnapshot } from '@angular/router'; import { Observable, of } from 'rxjs'; import { WalletsService } from './wallets/wallets.service'; import { CaseStudiesService } from './case-studies/case-studies.service'; import { DependenciesService } from './dependencies/dependencies.service'; +export interface SeoInformation { + title: string; + description?: string; + image?: string; +} + @Injectable({ providedIn: 'root', }) -export class TitleResolver implements Resolve { +export class SeoResolver implements Resolve { constructor( private walletsService: WalletsService, private caseStudiesService: CaseStudiesService, private dependenciesService: DependenciesService ) {} - resolve(route: ActivatedRouteSnapshot): Observable { + resolve(route: ActivatedRouteSnapshot): Observable { const id = route.paramMap.get('id'); const path = route.parent?.routeConfig?.path; if (path?.startsWith('wallets')) { const wallet = this.walletsService.find(id!); - return of(wallet ? `Wallet: ${wallet.name}` : 'Wallet Not Found'); + return of( + wallet + ? { + title: `Wallet: ${wallet.name}`, + image: wallet.logo, + } + : { + title: 'Wallet Not Found', + } + ); } else if (path?.startsWith('case-studies')) { const caseStudy = this.caseStudiesService.find(id!); return of( - caseStudy ? `Case Study: ${caseStudy.headline}` : 'Case Study Not Found' + caseStudy + ? { title: `Case Study: ${caseStudy.headline}` } + : { title: 'Case Study Not Found' } ); } else if (path?.startsWith('dependencies')) { const dependency = this.dependenciesService.find(id!); return of( - dependency ? `Dependency: ${dependency.name}` : 'Dependency Not Found' + dependency + ? { title: `Dependency: ${dependency.name}` } + : { title: 'Dependency Not Found' } ); } - return of('Not Found'); + return of({ title: 'Not Found' }); } } diff --git a/viewer/src/app/wallets/wallets.service.ts b/viewer/src/app/wallets/wallets.service.ts index dfbf22f..c44e661 100644 --- a/viewer/src/app/wallets/wallets.service.ts +++ b/viewer/src/app/wallets/wallets.service.ts @@ -120,6 +120,7 @@ export class WalletsService { } find(id: string) { + console.log('id', id); return this.loadWallets().find((wallet) => wallet.id === id); } diff --git a/viewer/src/environments/environment.development.ts b/viewer/src/environments/environment.development.ts index 065e943..8113fe0 100644 --- a/viewer/src/environments/environment.development.ts +++ b/viewer/src/environments/environment.development.ts @@ -1,5 +1,5 @@ -import { HashLocationStrategy } from '@angular/common'; +import { PathLocationStrategy } from '@angular/common'; export const environment = { - locationStrategy: HashLocationStrategy, + locationStrategy: PathLocationStrategy, };