generated from nhcarrigan/template
feat: set up comic view and routing logic
This commit is contained in:
@ -29,6 +29,8 @@
|
|||||||
"@angular/forms": "20.1.0",
|
"@angular/forms": "20.1.0",
|
||||||
"@angular/platform-browser": "20.1.0",
|
"@angular/platform-browser": "20.1.0",
|
||||||
"@angular/router": "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",
|
"rxjs": "7.8.2",
|
||||||
"tslib": "2.8.1",
|
"tslib": "2.8.1",
|
||||||
"zone.js": "0.15.1"
|
"zone.js": "0.15.1"
|
||||||
|
39
pnpm-lock.yaml
generated
39
pnpm-lock.yaml
generated
@ -26,6 +26,12 @@ importers:
|
|||||||
'@angular/router':
|
'@angular/router':
|
||||||
specifier: 20.1.0
|
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)
|
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:
|
rxjs:
|
||||||
specifier: 7.8.2
|
specifier: 7.8.2
|
||||||
version: 7.8.2
|
version: 7.8.2
|
||||||
@ -512,6 +518,23 @@ packages:
|
|||||||
resolution: {integrity: sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==}
|
resolution: {integrity: sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
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':
|
'@humanfs/core@0.19.1':
|
||||||
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
||||||
engines: {node: '>=18.18.0'}
|
engines: {node: '>=18.18.0'}
|
||||||
@ -4400,6 +4423,22 @@ snapshots:
|
|||||||
'@eslint/core': 0.15.1
|
'@eslint/core': 0.15.1
|
||||||
levn: 0.4.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/core@0.19.1': {}
|
||||||
|
|
||||||
'@humanfs/node@0.16.6':
|
'@humanfs/node@0.16.6':
|
||||||
|
@ -9,13 +9,13 @@ import {
|
|||||||
provideBrowserGlobalErrorListeners,
|
provideBrowserGlobalErrorListeners,
|
||||||
provideZoneChangeDetection,
|
provideZoneChangeDetection,
|
||||||
} from "@angular/core";
|
} from "@angular/core";
|
||||||
import { provideRouter } from "@angular/router";
|
import { provideRouter, withComponentInputBinding } from "@angular/router";
|
||||||
import { routes } from "./app.routes";
|
import { routes } from "./app.routes";
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
provideBrowserGlobalErrorListeners(),
|
provideBrowserGlobalErrorListeners(),
|
||||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||||
provideRouter(routes),
|
provideRouter(routes, withComponentInputBinding()),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -13,6 +13,8 @@ import { Home } from "./home/home.js";
|
|||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{ component: Home, path: "", pathMatch: "full" },
|
{ 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: Comic, path: "comic" },
|
||||||
{ component: About, path: "about" },
|
{ component: About, path: "about" },
|
||||||
{ component: Archive, path: "archive" },
|
{ component: Archive, path: "archive" },
|
||||||
|
@ -72,4 +72,26 @@ export class ComicService {
|
|||||||
public getComics(): Array<{ number: number; title: string; date: string }> {
|
public getComics(): Array<{ number: number; title: string; date: string }> {
|
||||||
return this.comics;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
img {
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ng-fa-icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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({
|
@Component({
|
||||||
selector: 'app-comic',
|
imports: [ CommonModule, RouterModule, FontAwesomeModule ],
|
||||||
imports: [],
|
selector: "app-comic",
|
||||||
templateUrl: './comic.html',
|
styleUrl: "./comic.css",
|
||||||
styleUrl: './comic.css'
|
templateUrl: "./comic.html",
|
||||||
})
|
})
|
||||||
export class Comic {
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user