diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 65621e2..8d05045 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -15,7 +15,7 @@ export const routes: Routes = [ loadComponent: () => import('./components/characters/create-character/create-character.component'), }, { - path: ':id', + path: ':characterId', children: [ { path: '', diff --git a/src/app/components/character-switcher/character-switcher-item/character-switcher-item.component.css b/src/app/components/character-switcher/character-switcher-item/character-switcher-item.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/character-switcher/character-switcher-item/character-switcher-item.component.html b/src/app/components/character-switcher/character-switcher-item/character-switcher-item.component.html new file mode 100644 index 0000000..72d989e --- /dev/null +++ b/src/app/components/character-switcher/character-switcher-item/character-switcher-item.component.html @@ -0,0 +1,13 @@ + + + {{ initials() }} + + + + {{ characterName() }} + + @if (group) { + {{ group }} + } + + diff --git a/src/app/components/character-switcher/character-switcher-item/character-switcher-item.component.ts b/src/app/components/character-switcher/character-switcher-item/character-switcher-item.component.ts new file mode 100644 index 0000000..24aa7ef --- /dev/null +++ b/src/app/components/character-switcher/character-switcher-item/character-switcher-item.component.ts @@ -0,0 +1,27 @@ +import { ChangeDetectionStrategy, Component, computed, input, Input } from '@angular/core'; + +const createInitials = (name: string) => { + const regExp = new RegExp(/(\p{L}{1})\p{L}+/, 'gu'); + + const initials = [...name.matchAll(regExp)] || []; + + return ((initials.shift()?.[1] || '') + (initials.pop()?.[1] || '')).toUpperCase(); +}; + +@Component({ + selector: 'pap-character-switcher-item', + standalone: true, + imports: [], + templateUrl: './character-switcher-item.component.html', + styleUrl: './character-switcher-item.component.css', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CharacterSwitcherItemComponent { + @Input() group = ''; + characterName = input.required(); + + initials = computed(() => { + const characterName = this.characterName(); + return createInitials(characterName); + }); +} diff --git a/src/app/components/character-switcher/character-switcher.component.css b/src/app/components/character-switcher/character-switcher.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/character-switcher/character-switcher.component.html b/src/app/components/character-switcher/character-switcher.component.html new file mode 100644 index 0000000..5f64d70 --- /dev/null +++ b/src/app/components/character-switcher/character-switcher.component.html @@ -0,0 +1,30 @@ + + + + + + + + @if (charactersWithoutCurrent().length) { + + @for (character of charactersWithoutCurrent(); track character.id) { + + } + + + } + + + + Neuen Charakter erstellen + + diff --git a/src/app/components/character-switcher/character-switcher.component.ts b/src/app/components/character-switcher/character-switcher.component.ts new file mode 100644 index 0000000..a043ae7 --- /dev/null +++ b/src/app/components/character-switcher/character-switcher.component.ts @@ -0,0 +1,46 @@ +import { AfterViewInit, ChangeDetectionStrategy, Component, computed, ElementRef, EventEmitter, input, Output, ViewChild } from '@angular/core'; +import { CharacterSwitcherItemComponent } from './character-switcher-item/character-switcher-item.component'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { faRepeat, faUser } from '@fortawesome/free-solid-svg-icons'; +import { RouterLink } from '@angular/router'; +import { CharacterEntity } from '../../models/character/character.entity'; +import { Dropdown } from 'flowbite'; + +@Component({ + selector: 'pap-character-switcher', + standalone: true, + imports: [CharacterSwitcherItemComponent, FaIconComponent, RouterLink], + templateUrl: './character-switcher.component.html', + styleUrl: './character-switcher.component.css', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CharacterSwitcherComponent implements AfterViewInit { + characters = input.required(); + currentCharacter = input.required(); + + @Output() characterSelected = new EventEmitter(); + protected readonly charactersWithoutCurrent = computed(() => { + const characters = this.characters(); + const currentCharacter = this.currentCharacter(); + + return characters.filter(character => character.id !== currentCharacter.id); + }); + protected readonly faRepeat = faRepeat; + protected readonly faUser = faUser; + @ViewChild('dropdownTrigger', { read: ElementRef, static: true }) + private readonly dropdownTrigger!: ElementRef; + @ViewChild('dropdownTarget', { static: true }) + private readonly dropdownTarget!: ElementRef; + private dropdown!: Dropdown; + + ngAfterViewInit() { + this.dropdown = new Dropdown(this.dropdownTarget.nativeElement, this.dropdownTrigger.nativeElement, { + placement: 'top', + }); + } + + protected selectCharacter(character: CharacterEntity) { + this.dropdown.hide(); + this.characterSelected.emit(character); + } +} diff --git a/src/app/components/characters/character-dashboard/character-dashboard.component.ts b/src/app/components/characters/character-dashboard/character-dashboard.component.ts index 93fcdbc..654ddec 100644 --- a/src/app/components/characters/character-dashboard/character-dashboard.component.ts +++ b/src/app/components/characters/character-dashboard/character-dashboard.component.ts @@ -26,7 +26,7 @@ export default class CharacterDashboardComponent { private readonly charactersService = inject(CharactersService); private readonly id = toSignal( this.activatedRoute.paramMap.pipe( - map(params => params.get('id')), + map(params => params.get('characterId')), filter(id => !!id), map(id => +id!), ), diff --git a/src/app/components/characters/create-character/create-character.component.html b/src/app/components/characters/create-character/create-character.component.html index 4d77e19..81212a8 100644 --- a/src/app/components/characters/create-character/create-character.component.html +++ b/src/app/components/characters/create-character/create-character.component.html @@ -1,20 +1,12 @@ -@if (charactersStore.count() > 0) { - Keine Charaktererstellung möglich +Neuen Charakter erstellen - - Du hast bereits einen Charakter erstellt, gehe daher bitte zu seinem - Dashboard. - -} @else { - Neuen Charakter erstellen + + + + + + + - - - - - - - - Charakter erstellen - -} + Charakter erstellen + diff --git a/src/app/components/characters/create-character/create-character.component.ts b/src/app/components/characters/create-character/create-character.component.ts index c41a7dd..9b5af27 100644 --- a/src/app/components/characters/create-character/create-character.component.ts +++ b/src/app/components/characters/create-character/create-character.component.ts @@ -5,7 +5,6 @@ import { TextInputComponent } from '../../inputs/text-input/text-input.component import { SubmitButtonComponent } from '../../inputs/submit-button/submit-button.component'; import { Router, RouterLink } from '@angular/router'; import { CharactersService } from '../../../services/characters.service'; -import { CharactersStore } from '../../../stores/characters.store'; @Component({ selector: 'pap-create-character', @@ -22,9 +21,9 @@ export default class CreateCharacterComponent { gender: ['', Validators.required], age: [0, [Validators.required, Validators.min(0)]], religion: ['', Validators.required], + group: ['', Validators.required], }); protected readonly charactersService = inject(CharactersService); - protected readonly charactersStore = inject(CharactersStore); private readonly router = inject(Router); protected async submit(): Promise { @@ -38,6 +37,7 @@ export default class CreateCharacterComponent { this.formGroup.value.age!, this.formGroup.value.nation!, this.formGroup.value.religion!, + this.formGroup.value.group!, ); await this.router.navigate(['/characters', character.id, 'dashboard']); diff --git a/src/app/components/settings/backup-restore/backup-restore.component.ts b/src/app/components/settings/backup-restore/backup-restore.component.ts index e6a528c..4574fd5 100644 --- a/src/app/components/settings/backup-restore/backup-restore.component.ts +++ b/src/app/components/settings/backup-restore/backup-restore.component.ts @@ -6,7 +6,6 @@ import { TauriService } from '../../../services/tauri.service'; import { DateTime } from 'luxon'; import { H2Component } from '../../headings/h2/h2.component'; import { H3Component } from '../../headings/h3/h3.component'; -import { Router } from '@angular/router'; import { FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { FileDirective } from '../../../directives/file.directive'; @@ -22,7 +21,6 @@ export default class BackupRestoreComponent { protected readonly fileControl = new FormControl(null, { validators: [Validators.required] }); private readonly databaseService = inject(DatabaseService); private readonly tauriService = inject(TauriService); - private readonly router = inject(Router); @ViewChild('fileInput') private readonly fileInputElement!: ElementRef; @@ -64,7 +62,7 @@ export default class BackupRestoreComponent { return; } - await this.router.navigateByUrl('/'); + window.location.reload(); } async reset(): Promise { @@ -75,6 +73,6 @@ export default class BackupRestoreComponent { } await this.databaseService.clear(); - await this.router.navigateByUrl('/'); + window.location.reload(); } } diff --git a/src/app/components/sidebar/sidebar.component.html b/src/app/components/sidebar/sidebar.component.html index 6b67fb2..b4c7e8a 100644 --- a/src/app/components/sidebar/sidebar.component.html +++ b/src/app/components/sidebar/sidebar.component.html @@ -6,7 +6,9 @@ - Dashboard + Dashboard @@ -16,11 +18,19 @@ - Backup + Backup - + + @if (charactersStore.count() && currentCharacter()) { + + + } + @@ -34,7 +44,7 @@ } - + With by Boundfox Studios diff --git a/src/app/components/sidebar/sidebar.component.ts b/src/app/components/sidebar/sidebar.component.ts index 2a4dc1b..a1bb329 100644 --- a/src/app/components/sidebar/sidebar.component.ts +++ b/src/app/components/sidebar/sidebar.component.ts @@ -1,28 +1,22 @@ -import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; -import { - faArrowCircleLeft, - faArrowRotateBackward, - faDashboard, - faDiceD6, - faDragon, - faGears, - faHeart, - faRecycle, - faTools, -} from '@fortawesome/free-solid-svg-icons'; +import { faArrowRotateBackward, faDashboard, faDiceD6, faDragon, faGears, faHeart, faTools } from '@fortawesome/free-solid-svg-icons'; import { SidebarItemComponent } from './sidebar-item/sidebar-item.component'; import { faGithub } from '@fortawesome/free-brands-svg-icons'; import { VersionService } from '../../services/version.service'; import { toSignal } from '@angular/core/rxjs-interop'; -import { AsyncPipe } from '@angular/common'; +import { AsyncPipe, JsonPipe } from '@angular/common'; import { SidebarItemGroupComponent } from './sidebar-item-group/sidebar-item-group.component'; -import { RouterLinkActive } from '@angular/router'; +import { ActivatedRoute, NavigationEnd, Router, RouterLinkActive } from '@angular/router'; +import { CharacterSwitcherComponent } from '../character-switcher/character-switcher.component'; +import { CharactersStore } from '../../stores/characters.store'; +import { filter, map } from 'rxjs'; +import { CharacterEntity } from '../../models/character/character.entity'; @Component({ selector: 'pap-sidebar', standalone: true, - imports: [FaIconComponent, SidebarItemComponent, AsyncPipe, SidebarItemGroupComponent, RouterLinkActive], + imports: [FaIconComponent, SidebarItemComponent, AsyncPipe, SidebarItemGroupComponent, RouterLinkActive, CharacterSwitcherComponent, JsonPipe], templateUrl: './sidebar.component.html', styleUrl: './sidebar.component.css', changeDetection: ChangeDetectionStrategy.OnPush, @@ -35,9 +29,47 @@ export class SidebarComponent { protected readonly faDashboard = faDashboard; protected readonly faTools = faTools; protected readonly faGears = faGears; - protected readonly faRecycle = faRecycle; - protected readonly faArrowCircleLeft = faArrowCircleLeft; protected readonly faArrowRotateBackward = faArrowRotateBackward; + protected readonly charactersStore = inject(CharactersStore); + protected readonly currentCharacter = computed(() => { + const id = this.characterId(); + + if (!id) { + return null; + } + + const entities = this.charactersStore.entityMap(); + return entities[id]; + }); + protected readonly currentCharacterIdOrDefault = computed(() => { + const currentCharacter = this.currentCharacter(); + + return currentCharacter?.id ?? 1; + }); private readonly versionService = inject(VersionService); protected readonly versionSignal = toSignal(this.versionService.load()); + private readonly router = inject(Router); + private readonly route = inject(ActivatedRoute); + readonly characterId = toSignal( + this.router.events.pipe( + filter(event => event instanceof NavigationEnd), + map(() => this.getParams(this.route)), + map(params => params.characterId ?? null), + ), + ); + + protected switchCharacter(character: CharacterEntity) { + void this.router.navigate(['/characters', character.id, 'dashboard']); + } + + private getParams(route: ActivatedRoute): { characterId?: string } { + let params = route.snapshot.params; + params = { ...route.snapshot.queryParams, ...params }; + if (route.children) { + for (const r of route.children) { + params = { ...this.getParams(r), ...params }; + } + } + return params; + } } diff --git a/src/app/models/character/character.entity.ts b/src/app/models/character/character.entity.ts index e8ea2f8..69f5234 100644 --- a/src/app/models/character/character.entity.ts +++ b/src/app/models/character/character.entity.ts @@ -8,6 +8,12 @@ export interface CharacterEntity { nation: string; age: number; religion: string; + + /** + * Group is the "Spielrunde" where the character is played. + */ + group: string; + main: MainStatistics; value: ValueStatistics; } diff --git a/src/app/services/characters.service.ts b/src/app/services/characters.service.ts index 3151259..21c81b0 100644 --- a/src/app/services/characters.service.ts +++ b/src/app/services/characters.service.ts @@ -12,13 +12,14 @@ export class CharactersService { await this.charactersStore.update(character); } - async add(name: string, gender: string, age: number, nation: string, religion: string): Promise { + async add(name: string, gender: string, age: number, nation: string, religion: string, group: string): Promise { return this.charactersStore.add({ name, gender, age, nation, religion, + group, main: { agility: 0, magic: 0, diff --git a/src/app/services/tables/characters.table.ts b/src/app/services/tables/characters.table.ts index 0a7df5c..7c60616 100644 --- a/src/app/services/tables/characters.table.ts +++ b/src/app/services/tables/characters.table.ts @@ -13,8 +13,7 @@ export class CharactersTable { async add(character: Omit) { const id = await this.databaseService.characters.add({ ...character, - id: 1, // prevent creating more than one character - }); + } as CharacterEntity); const newCharacter = await this.databaseService.characters.get(id); return newCharacter!;
- Du hast bereits einen Charakter erstellt, gehe daher bitte zu seinem - Dashboard. -