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.
273 lines
7.2 KiB
TypeScript
273 lines
7.2 KiB
TypeScript
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';
|
|
import { MatIconModule } from '@angular/material/icon';
|
|
import { MatButtonModule } from '@angular/material/button';
|
|
import { MatDividerModule } from '@angular/material/divider';
|
|
import { TranslatePipe } from '@ngx-translate/core';
|
|
import {UrlConstants} from '../../constants/UrlConstants';
|
|
import {AssetsConstants} from '../../constants/AssetsConstants';
|
|
import {RouterLink} from '@angular/router';
|
|
import {SharedFunctions} from '../../shared/SharedFunctions';
|
|
|
|
/** One role. Consecutive entries sharing a `company` render as a single employer block. */
|
|
interface ExperienceEntry {
|
|
key: string;
|
|
company: string;
|
|
logo: string;
|
|
highlightCount: number;
|
|
}
|
|
|
|
interface SkillGroup {
|
|
titleKey: string;
|
|
skillKeys: string[];
|
|
}
|
|
|
|
@Component({
|
|
selector: 'app-about',
|
|
imports: [
|
|
NgOptimizedImage,
|
|
MatCardModule,
|
|
MatChipsModule,
|
|
MatIconModule,
|
|
MatButtonModule,
|
|
MatDividerModule,
|
|
TranslatePipe,
|
|
RouterLink
|
|
],
|
|
templateUrl: './about.component.html',
|
|
changeDetection: ChangeDetectionStrategy.Eager,
|
|
styleUrl: './about.component.scss'
|
|
})
|
|
export class AboutComponent {
|
|
|
|
/** Roles older than the current decade are hidden until the reader asks for them. */
|
|
readonly showEarlierExperience = signal(false);
|
|
readonly showEarlierEducation = signal(false);
|
|
|
|
readonly recentExperience: ExperienceEntry[] = [
|
|
{
|
|
key: 'ABOUT.XP.COMPANY9',
|
|
company: 'colordigital',
|
|
logo: AssetsConstants.COLORDIGITAL_LOGO,
|
|
highlightCount: 3
|
|
},
|
|
{
|
|
key: 'ABOUT.XP.COMPANY8',
|
|
company: 'teraport',
|
|
logo: AssetsConstants.TERAPORT_LOGO,
|
|
highlightCount: 3
|
|
},
|
|
{
|
|
key: 'ABOUT.XP.COMPANY7',
|
|
company: 'colordigital-earlier',
|
|
logo: AssetsConstants.COLORDIGITAL_LOGO,
|
|
highlightCount: 3
|
|
},
|
|
{
|
|
key: 'ABOUT.XP.COMPANY6',
|
|
company: 'colordigital-earlier',
|
|
logo: AssetsConstants.COLORDIGITAL_LOGO,
|
|
highlightCount: 3
|
|
},
|
|
{
|
|
key: 'ABOUT.XP.COMPANY5',
|
|
company: 'assyst',
|
|
logo: AssetsConstants.ASSYST_LOGO,
|
|
highlightCount: 3
|
|
},
|
|
{
|
|
key: 'ABOUT.XP.COMPANY4',
|
|
company: 'assyst',
|
|
logo: AssetsConstants.ASSYST_LOGO,
|
|
highlightCount: 3
|
|
},
|
|
{
|
|
key: 'ABOUT.XP.COMPANY3',
|
|
company: 'assyst',
|
|
logo: AssetsConstants.ASSYST_LOGO,
|
|
highlightCount: 3
|
|
}
|
|
];
|
|
|
|
readonly earlierExperience: ExperienceEntry[] = [
|
|
{
|
|
key: 'ABOUT.XP.COMPANY2',
|
|
company: 'th-bingen',
|
|
logo: AssetsConstants.TH_BINGEN_LOGO,
|
|
highlightCount: 3
|
|
},
|
|
{
|
|
key: 'ABOUT.XP.COMPANY1',
|
|
company: 'th-bingen',
|
|
logo: AssetsConstants.TH_BINGEN_LOGO,
|
|
highlightCount: 3
|
|
},
|
|
{
|
|
key: 'ABOUT.XP.COMPANY0',
|
|
company: 'chamaeleon',
|
|
logo: AssetsConstants.CHAMAELEON_LOGO,
|
|
highlightCount: 3
|
|
}
|
|
];
|
|
|
|
readonly projectKeys = [
|
|
{
|
|
key: 'ABOUT.PROJECT.TBP',
|
|
externalLink: 'https://tbp.andreas-dahm.eu',
|
|
internalLink: 'projects',
|
|
identifier: 'to-be-played',
|
|
},
|
|
{
|
|
key: 'ABOUT.PROJECT.CLAUDE_BOX',
|
|
externalLink: '',
|
|
internalLink: 'projects',
|
|
identifier: 'claude-in-a-box',
|
|
},
|
|
{
|
|
key: 'ABOUT.PROJECT.P2',
|
|
externalLink: 'https://andreas-dahm.eu/',
|
|
internalLink: 'projects',
|
|
identifier: 'playground',
|
|
},
|
|
{
|
|
key: 'ABOUT.PROJECT.P1',
|
|
externalLink: 'https://store.steampowered.com/app/1532640/El_Mucho/',
|
|
internalLink: 'projects',
|
|
identifier: 'elmucho',
|
|
},
|
|
{
|
|
key: 'ABOUT.PROJECT.P0',
|
|
externalLink: 'https://itch.io/c/6628860/lobos-collection',
|
|
internalLink: 'projects',
|
|
identifier: 'gamejams',
|
|
},
|
|
{
|
|
key: 'ABOUT.PROJECT.DIPLOMA',
|
|
externalLink: '',
|
|
internalLink: 'projects',
|
|
identifier: 'diploma',
|
|
},
|
|
{
|
|
key: 'ABOUT.PROJECT.TRIBBLE',
|
|
externalLink: '',
|
|
internalLink: 'projects',
|
|
identifier: 'tribble-the-homeserver',
|
|
}
|
|
];
|
|
|
|
readonly recentEducationKeys = [
|
|
'ABOUT.EDUCATION.E6',
|
|
'ABOUT.EDUCATION.E5',
|
|
'ABOUT.EDUCATION.E4'
|
|
];
|
|
|
|
readonly earlierEducationKeys = [
|
|
'ABOUT.EDUCATION.E3',
|
|
'ABOUT.EDUCATION.E2',
|
|
'ABOUT.EDUCATION.E1',
|
|
'ABOUT.EDUCATION.E0'
|
|
];
|
|
|
|
/**
|
|
* Heading and chips travel together so the two can no longer drift apart —
|
|
* they used to be declared in separate places and had ended up mismatched.
|
|
*/
|
|
readonly skillGroups: SkillGroup[] = [
|
|
{
|
|
titleKey: 'ABOUT.SECTION.LANGUAGES',
|
|
skillKeys: [
|
|
'ABOUT.SKILLS.JAVA',
|
|
'ABOUT.SKILLS.SPRING',
|
|
'ABOUT.SKILLS.TYPESCRIPT',
|
|
'ABOUT.SKILLS.ANGULAR',
|
|
'ABOUT.SKILLS.CSHARP',
|
|
'ABOUT.SKILLS.PYTHON',
|
|
'ABOUT.SKILLS.VUE',
|
|
'ABOUT.SKILLS.REACT'
|
|
]
|
|
},
|
|
{
|
|
titleKey: 'ABOUT.SECTION.ARCH_CLOUD',
|
|
skillKeys: [
|
|
'ABOUT.SKILLS.ARCH_MICROSERVICES',
|
|
'ABOUT.SKILLS.ARCH_CLOUD',
|
|
'ABOUT.TOOLS.DOCKER',
|
|
'ABOUT.TOOLS.K8S',
|
|
'ABOUT.TOOLS.POSTGRES',
|
|
'ABOUT.TOOLS.MONGO',
|
|
'ABOUT.TOOLS.GITLAB',
|
|
'ABOUT.TOOLS.JENKINS'
|
|
]
|
|
},
|
|
{
|
|
titleKey: 'ABOUT.SECTION.SIM_ALGO',
|
|
skillKeys: [
|
|
'ABOUT.SKILLS.ENG_ALGO',
|
|
'ABOUT.SKILLS.ENG_SIM',
|
|
'ABOUT.SKILLS.ENG_GPU',
|
|
'ABOUT.SKILLS.ENG_PERF',
|
|
'ABOUT.SKILLS.UNITY'
|
|
]
|
|
},
|
|
{
|
|
titleKey: 'ABOUT.SECTION.AI_ENG',
|
|
skillKeys: [
|
|
'ABOUT.SKILLS.AI_AGENTS',
|
|
'ABOUT.SKILLS.AI_MCP',
|
|
'ABOUT.SKILLS.AI_CONTEXT',
|
|
'ABOUT.SKILLS.AI_HARNESS',
|
|
'ABOUT.SKILLS.AI_GATES'
|
|
]
|
|
}
|
|
];
|
|
|
|
readonly visibleExperience = computed(() => {
|
|
if (this.showEarlierExperience()) {
|
|
return [...this.recentExperience, ...this.earlierExperience];
|
|
}
|
|
return this.recentExperience;
|
|
});
|
|
|
|
readonly visibleEducationKeys = computed(() => {
|
|
if (this.showEarlierEducation()) {
|
|
return [...this.recentEducationKeys, ...this.earlierEducationKeys];
|
|
}
|
|
return this.recentEducationKeys;
|
|
});
|
|
|
|
toggleEarlierExperience(): void {
|
|
this.showEarlierExperience.update(shown => !shown);
|
|
}
|
|
|
|
toggleEarlierEducation(): void {
|
|
this.showEarlierEducation.update(shown => !shown);
|
|
}
|
|
|
|
/** The employer header is drawn once per run of consecutive roles at the same company. */
|
|
isFirstRoleAtCompany(index: number): boolean {
|
|
if (index === 0) {
|
|
return true;
|
|
}
|
|
const entries = this.visibleExperience();
|
|
return entries[index - 1].company !== entries[index].company;
|
|
}
|
|
|
|
isLastRoleAtCompany(index: number): boolean {
|
|
const entries = this.visibleExperience();
|
|
if (index === entries.length - 1) {
|
|
return true;
|
|
}
|
|
return entries[index + 1].company !== entries[index].company;
|
|
}
|
|
|
|
highlightKeys(entry: ExperienceEntry): string[] {
|
|
return Array.from({ length: entry.highlightCount }, (_, i) => `${entry.key}.HIGHLIGHTS.P${i + 1}`);
|
|
}
|
|
|
|
protected readonly UrlConstants = UrlConstants;
|
|
protected readonly AssetsConstants = AssetsConstants;
|
|
protected readonly SharedFunctions = SharedFunctions;
|
|
}
|