feat: set up comic view and routing logic

This commit is contained in:
2025-07-15 18:48:26 -07:00
parent 9b34db3fed
commit d718ca3718
8 changed files with 148 additions and 8 deletions

View File

@ -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"

39
pnpm-lock.yaml generated
View File

@ -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':

View File

@ -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()),
],
};

View File

@ -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" },

View File

@ -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;
}
}

View File

@ -0,0 +1,7 @@
img {
width: 500px;
}
.ng-fa-icon {
font-size: 2rem;
}

View File

@ -1 +1,12 @@
<p>comic works!</p>
<div *ngIf="comic">
<h2>{{ comic!.title }}</h2>
<p>Published on: {{ comic!.date }}</p>
<img [src]="`https://cdn.yurigpt.com/comics/${comic!.number}.png`" alt="{{ comic!.title }}" />
</div>
<div *ngIf="error">
<p>Error loading comic: {{ error }}</p>
</div>
<div id="buttons">
<a *ngIf="previousComicId" [routerLink]="['/comic', previousComicId]"><fa-icon [icon]="backIcon"></fa-icon></a>
<a *ngIf="nextComicId" [routerLink]="['/comic', nextComicId]"><fa-icon [icon]="forwardIcon"></fa-icon></a>
</div>

View File

@ -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<string | undefined>("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<string, string> = 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);
});
}
}