diff --git a/src/app/common-components/n64-box/n64-box.component.scss b/src/app/common-components/n64-box/n64-box.component.scss index b7062a7..386d5c4 100644 --- a/src/app/common-components/n64-box/n64-box.component.scss +++ b/src/app/common-components/n64-box/n64-box.component.scss @@ -16,7 +16,6 @@ section { $color-bg-hl 80%, $color-bg); - max-width: 800px; margin: auto; } diff --git a/src/app/games/game-summaries/external-game-summary/external-game-summary.component.html b/src/app/games/game-summaries/external-game-summary/external-game-summary.component.html new file mode 100644 index 0000000..95e5aad --- /dev/null +++ b/src/app/games/game-summaries/external-game-summary/external-game-summary.component.html @@ -0,0 +1,26 @@ + + + + + {{ data.title }} + + + + Code decompiled: {{ total }} +   + + + + + + +
GitHub
+
+ + +
Progress history
+ +
+
+
+ diff --git a/src/app/games/game-summaries/external-game-summary/external-game-summary.component.scss b/src/app/games/game-summaries/external-game-summary/external-game-summary.component.scss new file mode 100644 index 0000000..ea32660 --- /dev/null +++ b/src/app/games/game-summaries/external-game-summary/external-game-summary.component.scss @@ -0,0 +1,25 @@ +mat-card > a { + text-decoration: none; + } + +img[mat-card-avatar] { + border-radius: 0; + object-fit: contain; +} + +mat-card-actions { + text-align: center; +} + +mat-card-title > fa-icon { + font-size: 16px; + vertical-align: center; +} + +mat-card-actions fa-icon { + vertical-align: center; +} + +mat-card-actions a { + color: white; +} diff --git a/src/app/games/game-summaries/external-game-summary/external-game-summary.component.spec.ts b/src/app/games/game-summaries/external-game-summary/external-game-summary.component.spec.ts new file mode 100644 index 0000000..ebb3435 --- /dev/null +++ b/src/app/games/game-summaries/external-game-summary/external-game-summary.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GameComponent } from '../../game/game.component'; + +describe('GameComponent', () => { + let component: GameComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ GameComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(GameComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/games/game-summaries/external-game-summary/external-game-summary.component.ts b/src/app/games/game-summaries/external-game-summary/external-game-summary.component.ts new file mode 100644 index 0000000..af5c34f --- /dev/null +++ b/src/app/games/game-summaries/external-game-summary/external-game-summary.component.ts @@ -0,0 +1,48 @@ +import { Component, Input, OnChanges } from '@angular/core'; +import { IExternalGame } from '../../games.service.interface'; +import { faGithub } from '@fortawesome/free-brands-svg-icons'; +import { faChartLine, faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons'; +import { GamesService } from '../../games.service'; + +/** + * Displays an externally-linked game's name, decomp progress, and links. + */ +@Component({ + selector: 'external-game-summary', + templateUrl: './external-game-summary.component.html', + styleUrls: ['./external-game-summary.component.scss'] +}) +export class ExternalGameSummaryComponent implements OnChanges { + + constructor(private gamesService: GamesService) { } + + /** + * The game data to use. + */ + @Input() data: IExternalGame; + /** + * Shield data. + */ + shield: string; + /** + * The percentage to display for completion. + */ + total?: string; + /** + * Icon definitions. + */ + github = faGithub; + external = faExternalLinkAlt; + chart = faChartLine; + + ngOnChanges(): void { + + if (this.data.shieldURL) { + this.gamesService.getGameShield(`${this.data.shieldURL}`).subscribe( + res => this.total = res.message, + err => console.error(err) + ); + } + } + +} diff --git a/src/app/games/game-summaries/game-summaries.component.html b/src/app/games/game-summaries/game-summaries.component.html index 8a6d91d..546f1f5 100644 --- a/src/app/games/game-summaries/game-summaries.component.html +++ b/src/app/games/game-summaries/game-summaries.component.html @@ -1,3 +1,4 @@
+
diff --git a/src/app/games/game-summaries/game-summaries.component.scss b/src/app/games/game-summaries/game-summaries.component.scss index 793e7a6..06eb588 100644 --- a/src/app/games/game-summaries/game-summaries.component.scss +++ b/src/app/games/game-summaries/game-summaries.component.scss @@ -2,7 +2,12 @@ div { margin-right: -1em; } -game-summary { +h2 { + margin: auto; + width: max-content; +} + +game-summary, external-game-summary { flex-grow: 0; margin-bottom: 12px; margin-right: 1em; diff --git a/src/app/games/game-summaries/game-summaries.component.ts b/src/app/games/game-summaries/game-summaries.component.ts index ab0a19c..18bad9f 100644 --- a/src/app/games/game-summaries/game-summaries.component.ts +++ b/src/app/games/game-summaries/game-summaries.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { GamesService } from '../games.service'; -import { IGame } from '../games.service.interface'; +import { IGame, IExternalGame } from '../games.service.interface'; /** * Container for all game summaries. @@ -18,12 +18,20 @@ export class GameSummariesComponent implements OnInit { * List of games to use for "GameSummary" components. */ games: IGame[] = []; + /** + * List of external games to use for "ExternalGameSummary" components. + */ + externalGames: IExternalGame[] = []; ngOnInit(): void { this.gamesService.getGames().subscribe( res => this.games = res, err => console.error(err) ) + this.gamesService.getExternalGames().subscribe( + res => this.externalGames = res, + err => console.error(err) + ) } } diff --git a/src/app/games/game-summaries/game-summary/game-summary.component.html b/src/app/games/game-summaries/game-summary/game-summary.component.html index afe15c9..31e6931 100644 --- a/src/app/games/game-summaries/game-summary/game-summary.component.html +++ b/src/app/games/game-summaries/game-summary/game-summary.component.html @@ -1,18 +1,19 @@ - + {{ data.title }} - Percent completion: {{ (total * 100).toFixed(2) }}% + + Code decompiled: {{ total }} +   + - + - - - +
GitHub
diff --git a/src/app/games/game-summaries/game-summary/game-summary.component.scss b/src/app/games/game-summaries/game-summary/game-summary.component.scss index 0c6c651..9bb6823 100644 --- a/src/app/games/game-summaries/game-summary/game-summary.component.scss +++ b/src/app/games/game-summaries/game-summary/game-summary.component.scss @@ -10,3 +10,11 @@ img[mat-card-avatar] { mat-card-actions { text-align: center; } + +mat-card-actions fa-icon { + vertical-align: center; +} + +mat-card-actions a { + color: white; +} diff --git a/src/app/games/game-summaries/game-summary/game-summary.component.ts b/src/app/games/game-summaries/game-summary/game-summary.component.ts index 79b9556..36bb79a 100644 --- a/src/app/games/game-summaries/game-summary/game-summary.component.ts +++ b/src/app/games/game-summaries/game-summary/game-summary.component.ts @@ -22,29 +22,20 @@ export class GameSummaryComponent implements OnChanges { /** * The percentage to display for completion. */ - total: number = null; + total?: string; /** - * Icon definition. + * Icon definitions. */ github = faGithub; - /** - * Icon definition. - */ trello = faTrello; ngOnChanges(): void { - // Matching is second - this.gamesService.getGameCSV(this.data.csvs.matching).subscribe( - res => { - const points = res.split("\n").filter((line) => line != ""); - const latestPoint = points[points.length - 1]; - const column = latestPoint.split(","); - - let i = this.data.progressMeta[0].index; - this.total = +column[i] / +column[i + 1]; - }, - err => console.error(err) - ); + if (this.data.shieldURL) { + this.gamesService.getGameShield(`${this.data.shieldURL}`).subscribe( + res => this.total = res.message, + err => console.error(err) + ); + } } } diff --git a/src/app/games/game/game.component.ts b/src/app/games/game/game.component.ts index 8cb1f55..726613c 100644 --- a/src/app/games/game/game.component.ts +++ b/src/app/games/game/game.component.ts @@ -39,13 +39,15 @@ export class GameComponent implements OnInit { } else { this.game = res[index]; - forkJoin([ - this.gamesService.getGameCSV(this.game.csvs.nonmatching), - this.gamesService.getGameCSV(this.game.csvs.matching) - ]).subscribe( - res => this.csvData = res, - err => console.error(err) - ); + if (this.game.progressMeta.length > 0) { + forkJoin([ + this.gamesService.getGameCSV(this.game.csvs.nonmatching), + this.gamesService.getGameCSV(this.game.csvs.matching) + ]).subscribe( + res => this.csvData = res, + err => console.error(err) + ); + } } }, err => { diff --git a/src/app/games/games.module.ts b/src/app/games/games.module.ts index fa0ce9b..b4120fa 100644 --- a/src/app/games/games.module.ts +++ b/src/app/games/games.module.ts @@ -5,6 +5,7 @@ import { GamesService } from './games.service'; import { GameSummariesComponent } from './game-summaries/game-summaries.component'; import { GameSummaryComponent } from './game-summaries/game-summary/game-summary.component'; import { GameComponent } from './game/game.component'; +import { ExternalGameSummaryComponent } from './game-summaries/external-game-summary/external-game-summary.component' import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatGridListModule } from '@angular/material/grid-list'; @@ -17,6 +18,8 @@ import { ChartModule } from 'angular-highcharts'; import { GameFAQComponent } from './game/game-faq/game-faq.component'; import { MarkdownModule, MarkedOptions, MarkedRenderer } from 'ngx-markdown'; + + // function that returns `MarkedOptions` with renderer override export function markedOptionsFactory(): MarkedOptions { const renderer = new MarkedRenderer(); @@ -48,7 +51,8 @@ export function markedOptionsFactory(): MarkedOptions { GameComponent, GameProgressComponent, GameChartComponent, - GameFAQComponent + GameFAQComponent, + ExternalGameSummaryComponent ], imports: [ CommonModule, diff --git a/src/app/games/games.service.interface.ts b/src/app/games/games.service.interface.ts index 0918ea2..1379595 100644 --- a/src/app/games/games.service.interface.ts +++ b/src/app/games/games.service.interface.ts @@ -12,6 +12,14 @@ export interface IGame { * The game's full title. */ title: string; + /** + * The URL for the card title to link to. + */ + routingURL: string; + /** + * The URL of the shield json to read. + */ + shieldURL: string; /** * The name of the "FAQ" MD to read. */ @@ -34,6 +42,32 @@ export interface IGame { progressMeta: IChart[]; } +/** + * Game information to display and for project progress processing. + */ +export interface IExternalGame { + /** + * The string to use to identify the game. + */ + slug: string; + /** + * The game's full title. + */ + title: string; + /** + * The URL for the card title to link to. + */ + routingURL: string; + /** + * Links to display as an icon. The key defines the icon, and the value is the link href. + */ + links: {[type: string]: string}; + /** + * The URL of the shield json to read. + */ + shieldURL: string; +} + /** * Resources to display within the Resources tab. */ @@ -133,6 +167,25 @@ export interface ISubdivision { description: string; } +/** + * Describes a game's shield output. + */ +export interface IShield { + /** + * Static label in left half. + */ + label: string; + /** + * Generally the progress number. + */ + message: string; + /** + * Colour of the message background. + */ + color: string; +} + + /** * Functions to implement within "GamesService". */ @@ -143,6 +196,11 @@ export interface IGamesService { */ getGames(): Observable; + /** + * Get the information for all supported external games. + */ + getExternalGames(): Observable; + /** * Get all of the resources. */ @@ -155,6 +213,13 @@ export interface IGamesService { */ getGameCSV(filename: string): Observable; + /** + * Get the Shield data for a game. + * + * @param filename The filename of the desired CSV. + */ + getGameShield(filename: string): Observable; + /** * Get the FAQ data for a game. * diff --git a/src/app/games/games.service.ts b/src/app/games/games.service.ts index b16125d..3649d2c 100644 --- a/src/app/games/games.service.ts +++ b/src/app/games/games.service.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { IGame, IGamesService, IResource } from './games.service.interface'; +import { IExternalGame, IGame, IGamesService, IResource, IShield } from './games.service.interface'; /** * Obtains data relating to the games. @@ -17,6 +17,10 @@ export class GamesService implements IGamesService { return this.http.get("/assets/json/games.json"); } + getExternalGames(): Observable { + return this.http.get("/assets/json/external-games.json"); + } + getResources(): Observable { return this.http.get("/assets/json/resources.json"); } @@ -25,6 +29,10 @@ export class GamesService implements IGamesService { return this.http.get(`/assets/csv/${filename}.csv`, { responseType: "text" }); } + getGameShield(filename: string): Observable { + return this.http.get(filename); + } + getGameFAQ(filename: string): Observable { return this.http.get(`/assets/md/${filename}.md`, { responseType: "text" }); } diff --git a/src/assets/json/external-games.json b/src/assets/json/external-games.json new file mode 100644 index 0000000..da1a4dc --- /dev/null +++ b/src/assets/json/external-games.json @@ -0,0 +1,12 @@ +[ + { + "slug": "botw", + "title": "Breath of the Wild", + "routingURL": "https://botw.link", + "links": { + "github": "https://github.com/zeldaret/botw", + "progress": "https://botw.link/progress" + }, + "shieldURL": "https://botw.link/badges/progress.json" + } +] \ No newline at end of file diff --git a/src/assets/json/games.json b/src/assets/json/games.json index 8f5149a..707693d 100644 --- a/src/assets/json/games.json +++ b/src/assets/json/games.json @@ -2,9 +2,11 @@ { "slug": "oot", "title": "Ocarina of Time", + "routingURL": "games/oot", "links": { "github": "https://www.github.com/zeldaret/oot" }, + "shieldURL": "assets/csv/progress-oot-shield.json", "faq": "oot_faq", "csvs": { "matching": "progress-oot-matching", @@ -44,9 +46,11 @@ { "slug": "mm", "title": "Majora's Mask", + "routingURL": "games/mm", "links": { "github": "https://www.github.com/zeldaret/mm" }, + "shieldURL": "assets/csv/progress-mm-shield.json", "faq": "mm_faq", "csvs": { "matching": "progress-mm-matching", @@ -130,9 +134,11 @@ { "slug": "tmc", "title": "The Minish Cap", + "routingURL": "games/tmc", "links": { "github": "https://www.github.com/zeldaret/tmc" }, + "shieldURL": "assets/csv/progress-tmc-shield.json", "faq": "tmc_faq", "csvs": { "matching": "progress-tmc-matching", @@ -175,6 +181,7 @@ { "slug": "tp", "title": "Twilight Princess", + "routingURL": "games/tp", "links": { "github": "https://www.github.com/zeldaret/tp" },