From d718ca3718760328f5484e71c99596c78a7d09ea Mon Sep 17 00:00:00 2001 From: Naomi Carrigan Date: Tue, 15 Jul 2025 18:48:26 -0700 Subject: [PATCH] feat: set up comic view and routing logic --- package.json | 2 ++ pnpm-lock.yaml | 39 +++++++++++++++++++++++ src/app/app.config.ts | 4 +-- src/app/app.routes.ts | 2 ++ src/app/comic-service.ts | 22 +++++++++++++ src/app/comic/comic.css | 7 +++++ src/app/comic/comic.html | 13 +++++++- src/app/comic/comic.ts | 67 +++++++++++++++++++++++++++++++++++++--- 8 files changed, 148 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 8bdd3e1..5931c6e 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,8 @@ "@angular/forms": "20.1.0", "@angular/platform-browser": "20.1.0", "@angular/router": "20.1.0", + "@fortawesome/angular-fontawesome": "2.0.1", + "@fortawesome/free-solid-svg-icons": "6.7.2", "rxjs": "7.8.2", "tslib": "2.8.1", "zone.js": "0.15.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7aa6f52..185e159 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,12 @@ importers: '@angular/router': specifier: 20.1.0 version: 20.1.0(@angular/common@20.1.0(@angular/core@20.1.0(@angular/compiler@20.1.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.1.0(@angular/compiler@20.1.0)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.1.0(@angular/common@20.1.0(@angular/core@20.1.0(@angular/compiler@20.1.0)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.1.0(@angular/compiler@20.1.0)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@fortawesome/angular-fontawesome': + specifier: 2.0.1 + version: 2.0.1(@angular/core@20.1.0(@angular/compiler@20.1.0)(rxjs@7.8.2)(zone.js@0.15.1)) + '@fortawesome/free-solid-svg-icons': + specifier: 6.7.2 + version: 6.7.2 rxjs: specifier: 7.8.2 version: 7.8.2 @@ -512,6 +518,23 @@ packages: resolution: {integrity: sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fortawesome/angular-fontawesome@2.0.1': + resolution: {integrity: sha512-IdklZkuw+WS2GQWhFnr1EX/tOALnrKaj4YGnUmPaUg2Uf+Amj8Xi+M/qDrr915YJ5MaDxd9tZ1kqOHRcvQqq2A==} + peerDependencies: + '@angular/core': ^20.0.0 + + '@fortawesome/fontawesome-common-types@6.7.2': + resolution: {integrity: sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==} + engines: {node: '>=6'} + + '@fortawesome/fontawesome-svg-core@6.7.2': + resolution: {integrity: sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==} + engines: {node: '>=6'} + + '@fortawesome/free-solid-svg-icons@6.7.2': + resolution: {integrity: sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==} + engines: {node: '>=6'} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -4400,6 +4423,22 @@ snapshots: '@eslint/core': 0.15.1 levn: 0.4.1 + '@fortawesome/angular-fontawesome@2.0.1(@angular/core@20.1.0(@angular/compiler@20.1.0)(rxjs@7.8.2)(zone.js@0.15.1))': + dependencies: + '@angular/core': 20.1.0(@angular/compiler@20.1.0)(rxjs@7.8.2)(zone.js@0.15.1) + '@fortawesome/fontawesome-svg-core': 6.7.2 + tslib: 2.8.1 + + '@fortawesome/fontawesome-common-types@6.7.2': {} + + '@fortawesome/fontawesome-svg-core@6.7.2': + dependencies: + '@fortawesome/fontawesome-common-types': 6.7.2 + + '@fortawesome/free-solid-svg-icons@6.7.2': + dependencies: + '@fortawesome/fontawesome-common-types': 6.7.2 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.6': diff --git a/src/app/app.config.ts b/src/app/app.config.ts index f036b5d..46e3ad8 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -9,13 +9,13 @@ import { provideBrowserGlobalErrorListeners, provideZoneChangeDetection, } from "@angular/core"; -import { provideRouter } from "@angular/router"; +import { provideRouter, withComponentInputBinding } from "@angular/router"; import { routes } from "./app.routes"; export const appConfig: ApplicationConfig = { providers: [ provideBrowserGlobalErrorListeners(), provideZoneChangeDetection({ eventCoalescing: true }), - provideRouter(routes), + provideRouter(routes, withComponentInputBinding()), ], }; diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index c3b2ef6..29a4da1 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -13,6 +13,8 @@ import { Home } from "./home/home.js"; export const routes: Routes = [ { component: Home, path: "", pathMatch: "full" }, + { component: Comic, path: "comic/:id" }, + // This line is necessary to handle the fallback when no ID is provided. { component: Comic, path: "comic" }, { component: About, path: "about" }, { component: Archive, path: "archive" }, diff --git a/src/app/comic-service.ts b/src/app/comic-service.ts index d76fa82..17752e7 100644 --- a/src/app/comic-service.ts +++ b/src/app/comic-service.ts @@ -72,4 +72,26 @@ export class ComicService { public getComics(): Array<{ number: number; title: string; date: string }> { return this.comics; } + + public getComicById( + id: string, + ): { number: number; title: string; date: string } | undefined { + const comicId = Number.parseInt(id, 10); + return this.comics.find((comic) => { + return comic.number === comicId; + }); + } + + public getLatestComic(): + | { number: number; title: string; date: string } + | undefined { + return this.comics.at(-1); + } + + public getLatestComicId(): string | undefined { + const latestComic = this.getLatestComic(); + return latestComic + ? latestComic.number.toString() + : undefined; + } } diff --git a/src/app/comic/comic.css b/src/app/comic/comic.css index e69de29..ddb405a 100644 --- a/src/app/comic/comic.css +++ b/src/app/comic/comic.css @@ -0,0 +1,7 @@ +img { + width: 500px; +} + +.ng-fa-icon { + font-size: 2rem; +} diff --git a/src/app/comic/comic.html b/src/app/comic/comic.html index 9654cc2..8b9cacf 100644 --- a/src/app/comic/comic.html +++ b/src/app/comic/comic.html @@ -1 +1,12 @@ -

comic works!

+
+

{{ comic!.title }}

+

Published on: {{ comic!.date }}

+ {{ comic!.title }} +
+
+

Error loading comic: {{ error }}

+
+
+ + +
diff --git a/src/app/comic/comic.ts b/src/app/comic/comic.ts index 0eb3ee0..54b02d1 100644 --- a/src/app/comic/comic.ts +++ b/src/app/comic/comic.ts @@ -1,11 +1,68 @@ -import { Component } from '@angular/core'; +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +import { CommonModule } from "@angular/common"; +import { Component, inject, input, signal } from "@angular/core"; +import { ActivatedRoute, RouterModule } from "@angular/router"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; +import { faLeftLong, faRightLong } from "@fortawesome/free-solid-svg-icons"; +import { ComicService } from "../comic-service.js"; @Component({ - selector: 'app-comic', - imports: [], - templateUrl: './comic.html', - styleUrl: './comic.css' + imports: [ CommonModule, RouterModule, FontAwesomeModule ], + selector: "app-comic", + styleUrl: "./comic.css", + templateUrl: "./comic.html", }) export class Comic { + public comicId = signal(""); + public readonly id = input("id"); + // eslint-disable-next-line stylistic/max-len -- I dunno. + public comic: { number: number; title: string; date: string } | undefined; + public error: string | undefined; + public previousComicId: string | undefined; + public nextComicId: string | undefined; + public readonly backIcon = faLeftLong; + public readonly forwardIcon = faRightLong; + private readonly activatedRoute = inject(ActivatedRoute); + public constructor(private readonly comicService: ComicService) { + this.comicService. + loadComics(). + then(() => { + this.activatedRoute.params.subscribe((parameters_) => { + const parameters: Record = parameters_; + console.log(`FoundID: ${parameters["id"]}`); + this.comicId.set( + parameters["id"] ?? this.comicService.getLatestComicId(), + ); + this.previousComicId = this.comicService. + getComicById(String(Number.parseInt(this.comicId(), 10) - 1))?. + number.toString(); + this.nextComicId = this.comicService. + getComicById(String(Number.parseInt(this.comicId(), 10) + 1))?. + number.toString(); + + // Load the comic data for the new ID + this.comic = this.comicService.getComicById(this.comicId()); + if (this.comic) { + // Clear any previous error + this.error = undefined; + } else { + this.error = `Cannot find comic with ID ${this.comicId()}.`; + } + }); + }). + catch((error: unknown) => { + this.error = `Failed to load comics: ${ + error instanceof Error + ? error.message + : "Please check the browser console for more details." + }`; + console.error("Error loading comics:", error); + }); + } }