build(angular): upgrade to angular 22, add to-be-played project
Build, Test & Push Frontend / quality-check (push) Failing after 35s
Build, Test & Push Frontend / docker (push) Skipped

Move to Angular 22 and update all dependencies. TypeScript stays on 6.0
(7 is blocked by Angular 22). Drop the ip-address override, add
fast-uri and basic-ftp overrides for audit fixes.
Set ChangeDetectionStrategy.Eager on components, as the ng update
migration does, and turn off the new OnPush lint rule.
Add the To Be Played project to the projects page and the about page.
This commit is contained in:
lobo committed 2026-10-12 09:48:29 +02:00
1 parent 6121fa553a
commit 775845b801
39 files changed
+3425 -3676

No files matched your search

+2 -2
View File
@@ -3,7 +3,7 @@ import { provideRouter, withInMemoryScrolling } from '@angular/router';
import { routes } from './app.routes';
import {provideAnimations} from '@angular/platform-browser/animations';
import {provideHttpClient} from '@angular/common/http';
import {provideHttpClient, withXhr} from '@angular/common/http';
import {provideServiceWorker} from '@angular/service-worker';
import {provideTranslateService} from '@ngx-translate/core';
import {provideTranslateHttpLoader} from '@ngx-translate/http-loader';
@@ -25,7 +25,7 @@ export const appConfig: ApplicationConfig = {
anchorScrolling: 'enabled'
})),
provideAnimations(),
provideHttpClient(),
provideHttpClient(withXhr()),
provideTranslateService({
lang: INITIAL_LANG,
fallbackLang: 'en',
+7
View File
@@ -51,4 +51,11 @@ export class AssetsConstants {
'/assets/projects/tribble-the-homeserver/3.png',
'/assets/projects/tribble-the-homeserver/4.png'
];
static readonly TBP_IMAGES = [
'/assets/projects/to-be-played/1.png',
'/assets/projects/to-be-played/2.png',
'/assets/projects/to-be-played/3.png',
'/assets/projects/to-be-played/4.png'
];
}
+2 -1
View File
@@ -1,4 +1,4 @@
import { Component, inject } from '@angular/core';
import { Component, inject, ChangeDetectionStrategy } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';
import {TopbarComponent} from '../topbar/topbar.component';
import {TranslatePipe} from '@ngx-translate/core';
@@ -10,6 +10,7 @@ import {SeoService} from '../../service/seo.service';
selector: 'app-root',
imports: [RouterLink, RouterOutlet, TopbarComponent, TranslatePipe, ParticleBackgroundComponent],
templateUrl: './app.component.html',
changeDetection: ChangeDetectionStrategy.Eager,
styleUrl: './app.component.scss'
})
export class AppComponent {
@@ -1,4 +1,4 @@
import { Component, inject } from '@angular/core';
import { Component, inject, ChangeDetectionStrategy } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
@@ -19,6 +19,7 @@ import { MatIconModule } from '@angular/material/icon';
</div>
</div>
`,
changeDetection: ChangeDetectionStrategy.Eager,
styles: [`
.dialog {
display: flex;
+2 -1
View File
@@ -1,4 +1,4 @@
import { Component, computed, inject } from '@angular/core';
import { Component, computed, inject, ChangeDetectionStrategy } from '@angular/core';
import { RouterLink, RouterLinkActive } from '@angular/router';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatIconModule } from '@angular/material/icon';
@@ -20,6 +20,7 @@ import {RouterConstants} from '../../constants/RouterConstants';
TranslatePipe, MatDivider
],
templateUrl: './topbar.component.html',
changeDetection: ChangeDetectionStrategy.Eager,
styleUrl: './topbar.component.scss'
})
export class TopbarComponent {
@@ -151,6 +151,19 @@ describe('AboutComponent', () => {
});
});
describe('projects', () => {
it('lists To Be Played and links to its project dialog', () => {
expect(textOf('mat-card strong')).toContain('ABOUT.PROJECT.TBP.TITLE');
const hrefs = fixture.debugElement
.queryAll(By.css('a[href]'))
.map(anchor => anchor.nativeElement.getAttribute('href'));
expect(hrefs).toContain('/projects?project=to-be-played');
expect(hrefs).toContain('https://tbp.andreas-dahm.eu');
});
});
describe('education', () => {
it('shows only the post-school entries until the toggle button is clicked', () => {
expect(textOf('[data-testid="education-where"]')).toContain('ABOUT.EDUCATION.E6.WHERE');
+8 -1
View File
@@ -1,4 +1,4 @@
import { Component, computed, signal } from '@angular/core';
import { Component, computed, signal, ChangeDetectionStrategy } from '@angular/core';
import { NgOptimizedImage } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatChipsModule } from '@angular/material/chips';
@@ -37,6 +37,7 @@ interface SkillGroup {
RouterLink
],
templateUrl: './about.component.html',
changeDetection: ChangeDetectionStrategy.Eager,
styleUrl: './about.component.scss'
})
export class AboutComponent {
@@ -112,6 +113,12 @@ export class AboutComponent {
];
readonly projectKeys = [
{
key: 'ABOUT.PROJECT.TBP',
externalLink: 'https://tbp.andreas-dahm.eu',
internalLink: 'projects',
identifier: 'to-be-played',
},
{
key: 'ABOUT.PROJECT.CLAUDE_BOX',
externalLink: '',
@@ -1,4 +1,4 @@
import { Component, inject } from '@angular/core';
import { Component, inject, ChangeDetectionStrategy } from '@angular/core';
import { AlgorithmsService } from './algorithms.service';
import { AlgorithmCategory } from './algorithm-category';
import { RouterLink } from '@angular/router';
@@ -10,6 +10,7 @@ import {TranslatePipe} from '@ngx-translate/core';
selector: 'app-algorithms',
templateUrl: './algorithms.component.html',
styleUrl: './algorithms.component.scss',
changeDetection: ChangeDetectionStrategy.Eager,
imports: [RouterLink, MatCardModule, MatIconModule, TranslatePipe],
})
export class AlgorithmsComponent {
@@ -1,4 +1,4 @@
import {Component} from '@angular/core';
import {Component, ChangeDetectionStrategy} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
import {MatSliderModule} from '@angular/material/slider';
@@ -28,6 +28,7 @@ import {ClothCpuStrategy} from './strategies/cloth-cpu.strategy';
Information
],
templateUrl: './cloth.component.html',
changeDetection: ChangeDetectionStrategy.Eager,
styleUrl: './cloth.component.scss',
})
export class ClothComponent {
@@ -1,4 +1,4 @@
import {AfterViewInit, Component, signal, ViewChild} from '@angular/core';
import {AfterViewInit, Component, signal, ViewChild, ChangeDetectionStrategy} from '@angular/core';
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from "@angular/material/card";
import {TranslatePipe} from "@ngx-translate/core";
import {UrlConstants} from '../../../constants/UrlConstants';
@@ -29,6 +29,7 @@ import {GenericGridComponent, GridPos} from '../../../shared/components/generic-
FormsModule,
GenericGridComponent
],
changeDetection: ChangeDetectionStrategy.Eager,
templateUrl: './conway-gol.component.html',
})
export class ConwayGolComponent implements AfterViewInit {
@@ -1,4 +1,4 @@
import {AfterViewInit, Component, ElementRef, inject, ViewChild} from '@angular/core';
import {AfterViewInit, Component, ElementRef, inject, ViewChild, ChangeDetectionStrategy} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms';
import {MatButtonModule} from '@angular/material/button';
@@ -29,6 +29,7 @@ import {UrlConstants} from '../../../constants/UrlConstants';
Information
],
templateUrl: './four-color.component.html',
changeDetection: ChangeDetectionStrategy.Eager,
styleUrl: './four-color.component.scss'
})
export class FourColorComponent implements AfterViewInit {
@@ -1,4 +1,4 @@
import {Component, OnInit, signal} from '@angular/core';
import {Component, OnInit, signal, ChangeDetectionStrategy} from '@angular/core';
import {Information} from '../information/information';
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
import {TranslatePipe} from '@ngx-translate/core';
@@ -35,6 +35,7 @@ import {NgxSliderModule, Options} from '@angular-slider/ngx-slider';
NgxSliderModule
],
templateUrl: './fractal.component.html',
changeDetection: ChangeDetectionStrategy.Eager,
styleUrl: './fractal.component.scss',
})
export class FractalComponent implements OnInit {
@@ -1,4 +1,4 @@
import {Component} from '@angular/core';
import {Component, ChangeDetectionStrategy} from '@angular/core';
import {ArcRotateCamera, Camera, ShaderMaterial} from '@babylonjs/core';
import {MANDELBULB_FRAGMENT, MANDELBULB_VERTEX} from './fractal3d.shader';
import {Information} from '../information/information';
@@ -22,6 +22,7 @@ import {BabylonCanvas, RenderCallback, RenderConfig} from '../../../shared/compo
BabylonCanvas
],
templateUrl: './fractal3d.component.html',
changeDetection: ChangeDetectionStrategy.Eager,
styleUrl: './fractal3d.component.scss',
})
export class Fractal3dComponent {
@@ -1,4 +1,4 @@
import {Component, Input} from '@angular/core';
import {Component, Input, ChangeDetectionStrategy} from '@angular/core';
import {TranslatePipe} from "@ngx-translate/core";
import {AlgorithmInformation} from './information.models';
@@ -8,6 +8,7 @@ import {AlgorithmInformation} from './information.models';
TranslatePipe
],
templateUrl: './information.html',
changeDetection: ChangeDetectionStrategy.Eager,
styleUrl: './information.scss',
})
export class Information {
@@ -1,4 +1,4 @@
import {AfterViewInit, Component, inject, signal, ViewChild} from '@angular/core';
import {AfterViewInit, Component, inject, signal, ViewChild, ChangeDetectionStrategy} from '@angular/core';
import {Information} from '../../information/information';
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
import {TranslatePipe} from '@ngx-translate/core';
@@ -25,6 +25,7 @@ import {PathfindingService} from '../service/pathfinding.service';
DecimalPipe
],
templateUrl: './labyrinth.component.html',
changeDetection: ChangeDetectionStrategy.Eager,
styleUrl: './labyrinth.component.scss',
})
export class LabyrinthComponent implements AfterViewInit {
@@ -1,4 +1,4 @@
import {AfterViewInit, Component, inject, ViewChild} from '@angular/core';
import {AfterViewInit, Component, inject, ViewChild, ChangeDetectionStrategy} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms';
@@ -43,6 +43,7 @@ enum NodeType {
Information,
GenericGridComponent
],
changeDetection: ChangeDetectionStrategy.Eager,
templateUrl: './pathfinding.component.html',
})
export class PathfindingComponent implements AfterViewInit {
@@ -1,4 +1,4 @@
import {Component} from '@angular/core';
import {Component, ChangeDetectionStrategy} from '@angular/core';
import {BabylonCanvas, RenderConfig, SceneEventData} from '../../../shared/components/render-canvas/babylon-canvas.component';
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
@@ -29,6 +29,7 @@ import {PendulumCpuStrategy} from './strategies/pendulum-cpu.strategy';
Information,
],
templateUrl: './pendulum.component.html',
changeDetection: ChangeDetectionStrategy.Eager,
styleUrl: './pendulum.component.scss',
})
class PendulumComponent {
@@ -1,4 +1,4 @@
import {ChangeDetectorRef, Component, inject, OnInit} from '@angular/core';
import {ChangeDetectorRef, Component, inject, OnInit, ChangeDetectionStrategy} from '@angular/core';
import { CommonModule } from '@angular/common';
import {MatCardModule} from "@angular/material/card";
import {MatFormFieldModule} from "@angular/material/form-field";
@@ -18,6 +18,7 @@ import {Information} from '../information/information';
selector: 'app-sorting',
imports: [CommonModule, MatCardModule, MatFormFieldModule, MatSelectModule, MatButtonModule, MatIconModule, TranslatePipe, FormsModule, MatInput, Information],
templateUrl: './sorting.component.html',
changeDetection: ChangeDetectionStrategy.Eager,
styleUrl: './sorting.component.scss'
})
export class SortingComponent implements OnInit {
+2 -1
View File
@@ -1,4 +1,4 @@
import { Component } from '@angular/core';
import { Component, ChangeDetectionStrategy } from '@angular/core';
import {MatCard} from '@angular/material/card';
import {TranslatePipe} from '@ngx-translate/core';
import {SharedFunctions} from '../../shared/SharedFunctions';
@@ -10,6 +10,7 @@ import {SharedFunctions} from '../../shared/SharedFunctions';
TranslatePipe
],
templateUrl: './imprint.component.html',
changeDetection: ChangeDetectionStrategy.Eager,
styleUrl: './imprint.component.scss',
})
export class ImprintComponent {
@@ -1,4 +1,4 @@
import { Component } from '@angular/core';
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { RouterLink } from '@angular/router';
import { MatCard } from '@angular/material/card';
import { MatButton } from '@angular/material/button';
@@ -8,6 +8,7 @@ import { RouterConstants } from '../../constants/RouterConstants';
@Component({
selector: 'app-not-found',
imports: [RouterLink, MatCard, MatButton, TranslatePipe],
changeDetection: ChangeDetectionStrategy.Eager,
templateUrl: './not-found.component.html',
})
export class NotFoundComponent {
@@ -1,4 +1,4 @@
import {AfterViewInit, Component, inject, CUSTOM_ELEMENTS_SCHEMA, ViewChild, ElementRef} from '@angular/core';
import {AfterViewInit, Component, inject, CUSTOM_ELEMENTS_SCHEMA, ViewChild, ElementRef, ChangeDetectionStrategy} from '@angular/core';
import {
MAT_DIALOG_DATA,
MatDialogActions,
@@ -27,6 +27,7 @@ import {MatButton} from '@angular/material/button';
MatButton,
MatDialogClose
],
changeDetection: ChangeDetectionStrategy.Eager,
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class ProjectDialogComponent implements AfterViewInit {
@@ -0,0 +1,63 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { By } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { provideTranslateService } from '@ngx-translate/core';
import { ProjectsComponent } from './projects.component';
import en from '../../../assets/i18n/en.json';
import de from '../../../assets/i18n/de.json';
/**
* No translations are loaded, so the translate pipe echoes the key back.
* Whether the keys exist in the language files is checked separately below.
*/
describe('ProjectsComponent', () => {
let fixture: ComponentFixture<ProjectsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ProjectsComponent],
providers: [
provideZonelessChangeDetection(),
provideRouter([]),
provideTranslateService({ lang: 'en', fallbackLang: 'en' }),
],
}).compileComponents();
fixture = TestBed.createComponent(ProjectsComponent);
fixture.detectChanges();
});
it('renders the TBP card', () => {
const titles = fixture.debugElement
.queryAll(By.css('mat-card-title'))
.map(title => title.nativeElement.textContent.trim());
expect(titles).toContain('PROJECTS.TBP.TITLE');
});
it('every project translation key exists in en and de', () => {
const keys = fixture.componentInstance.allProjects.flatMap(project => [
project.title,
project.shortDescription,
project.introduction,
...project.bulletPoints,
...project.challenges,
...project.learnings,
]);
const missing = Object.entries({ en, de }).flatMap(([language, translations]) =>
keys
.filter(key => typeof resolve(translations, key) !== 'string')
.map(key => `${language}: ${key}`)
);
expect(missing).toEqual([]);
});
function resolve(translations: unknown, key: string): unknown {
return key
.split('.')
.reduce<unknown>((node, part) => (node as Record<string, unknown> | undefined)?.[part], translations);
}
});
+34 -1
View File
@@ -1,4 +1,4 @@
import { Component, computed, inject, CUSTOM_ELEMENTS_SCHEMA, OnDestroy, OnInit } from '@angular/core';
import { Component, computed, inject, CUSTOM_ELEMENTS_SCHEMA, OnDestroy, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from "rxjs";
import { MatCardModule } from "@angular/material/card";
@@ -43,6 +43,7 @@ export interface Projects {
],
templateUrl: './projects.component.html',
styleUrl: './projects.component.scss',
changeDetection: ChangeDetectionStrategy.Eager,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class ProjectsComponent implements OnInit, OnDestroy {
@@ -53,6 +54,38 @@ export class ProjectsComponent implements OnInit, OnDestroy {
private queryParamSub: Subscription | undefined;
allProjects: Projects[] = [
{
identifier: "to-be-played",
title: 'PROJECTS.TBP.TITLE',
shortDescription: 'PROJECTS.TBP.SHORT_DESCRIPTION',
introduction: 'PROJECTS.TBP.INTRODUCTION',
images: AssetsConstants.TBP_IMAGES.map(url => ({ url, source: '' })),
icon: 'playlist_add_check',
assets: '',
links: [
{ name: 'PROJECTS.LINK_TO_PROJECT', url: 'https://tbp.andreas-dahm.eu' },
{ name: 'ToBePlayed-Frontend', url: 'https://git.andreas-dahm.eu/Lobo_Corp/ToBePlayed-Frontend' },
{ name: 'ToBePlayed-Core', url: 'https://git.andreas-dahm.eu/Lobo_Corp/ToBePlayed-Core' },
{ name: 'ToBePlayed-Auth', url: 'https://git.andreas-dahm.eu/Lobo_Corp/ToBePlayed-Auth' }
],
bulletPoints: [
'PROJECTS.TBP.BULLET_1',
'PROJECTS.TBP.BULLET_2',
'PROJECTS.TBP.BULLET_3',
'PROJECTS.TBP.BULLET_4',
],
challenges: [
'PROJECTS.TBP.CHALLENGE_1',
'PROJECTS.TBP.CHALLENGE_2',
'PROJECTS.TBP.CHALLENGE_3',
],
learnings: [
'PROJECTS.TBP.LEARNING_1',
'PROJECTS.TBP.LEARNING_2',
],
isFeatured: false,
technologies: ['Java', 'Spring Boot', 'PostgreSQL', 'React', 'Tailwind CSS', 'Docker']
},
{
identifier: "claude-in-a-box",
title: 'PROJECTS.CLAUDE_BOX.TITLE',
@@ -1,4 +1,4 @@
import {Component, computed, effect, inject, OnDestroy, signal, WritableSignal} from '@angular/core';
import {Component, computed, effect, inject, OnDestroy, signal, WritableSignal, ChangeDetectionStrategy} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {MatButtonModule} from '@angular/material/button';
import {MatCardModule} from '@angular/material/card';
@@ -30,6 +30,7 @@ const SECOND_INTERVAL_FREQUENCY_HZ = 587;
MatCheckboxModule,
TranslatePipe
],
changeDetection: ChangeDetectionStrategy.Eager,
templateUrl: './stopwatch.component.html'
})
export class StopwatchComponent implements OnDestroy {
@@ -1,4 +1,4 @@
import {AfterViewInit, Component, ElementRef, EventEmitter, Input, Output, ViewChild} from '@angular/core';
import {AfterViewInit, Component, ElementRef, EventEmitter, Input, Output, ViewChild, ChangeDetectionStrategy} from '@angular/core';
import {CommonModule} from '@angular/common';
export interface GridPos { row: number; col: number }
@@ -7,6 +7,7 @@ export interface GridPos { row: number; col: number }
selector: 'app-generic-grid',
imports: [CommonModule],
templateUrl: './generic-grid.html',
changeDetection: ChangeDetectionStrategy.Eager,
styleUrl: './generic-grid.scss',
})
export class GenericGridComponent implements AfterViewInit {
@@ -1,10 +1,11 @@
import {AfterViewInit, Component, ElementRef, HostListener, inject, NgZone, OnDestroy, PLATFORM_ID, ViewChild} from '@angular/core';
import {AfterViewInit, Component, ElementRef, HostListener, inject, NgZone, OnDestroy, PLATFORM_ID, ViewChild, ChangeDetectionStrategy} from '@angular/core';
import {isPlatformBrowser} from '@angular/common';
@Component({
selector: 'app-particles-background',
imports: [],
templateUrl: './particles-background.component.html',
changeDetection: ChangeDetectionStrategy.Eager,
styleUrl: './particles-background.component.scss',
})
export class ParticleBackgroundComponent implements AfterViewInit, OnDestroy {
@@ -1,4 +1,4 @@
import {AfterViewInit, Component, ElementRef, EventEmitter, inject, Input, NgZone, OnDestroy, Output, ViewChild} from '@angular/core';
import {AfterViewInit, Component, ElementRef, EventEmitter, inject, Input, NgZone, OnDestroy, Output, ViewChild, ChangeDetectionStrategy} from '@angular/core';
import {MatSnackBar} from '@angular/material/snack-bar';
import {TranslateService} from '@ngx-translate/core';
import {ArcRotateCamera, Camera, Engine, MeshBuilder, Scene, ShaderLanguage, ShaderMaterial, Vector2, Vector3, WebGPUEngine} from '@babylonjs/core';
@@ -26,6 +26,7 @@ export interface SceneEventData {
selector: 'app-babylon-canvas',
imports: [],
templateUrl: './babylon-canvas.component.html',
changeDetection: ChangeDetectionStrategy.Eager,
styleUrl: './babylon-canvas.component.scss',
})
export class BabylonCanvas implements AfterViewInit, OnDestroy {