migrate angular 21 finalize

This commit is contained in:
2026-03-09 22:56:31 +01:00
parent 26c99a0dae
commit bb08e46b0c
63 changed files with 738 additions and 783 deletions

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core';
import {Injectable, inject} from '@angular/core';
import {File} from './file';
import {Observable} from 'rxjs';
import {FileServer} from './fileServer';
@@ -8,7 +8,7 @@ import {DbService} from '../../../services/db.service';
providedIn: 'root',
})
export class FileDataService {
public constructor(private db: DbService) {}
private db = inject(DbService);
public async set(songId: string, file: FileServer): Promise<string> {
const songRef = this.db.doc('songs/' + songId);

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core';
import {Injectable, inject} from '@angular/core';
import {deleteObject, getDownloadURL, ref, Storage} from '@angular/fire/storage';
import {from, Observable} from 'rxjs';
import {FileDataService} from './file-data.service';
@@ -7,10 +7,8 @@ import {FileDataService} from './file-data.service';
providedIn: 'root',
})
export class FileService {
public constructor(
private storage: Storage,
private fileDataService: FileDataService
) {}
private storage = inject(Storage);
private fileDataService = inject(FileDataService);
public getDownloadUrl(path: string): Observable<string> {
return from(getDownloadURL(ref(this.storage, path)));

View File

@@ -2,14 +2,14 @@ import {getScale, scaleMapping} from './key.helper';
describe('key.helper', () => {
it('should render Gb correctly', () => {
expect(scaleMapping['Gb']).toBe('G♭');
void expect(scaleMapping['Gb']).toBe('G♭');
});
it('should expose a sharp-based scale for D', () => {
expect(getScale('D')).toEqual(['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'H']);
void expect(getScale('D')).toEqual(['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'H']);
});
it('should keep flat-based spelling for Db', () => {
expect(getScale('Db')).toEqual(['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'B', 'H']);
void expect(getScale('Db')).toEqual(['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'B', 'H']);
});
});

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core';
import {Injectable, inject} from '@angular/core';
import {Song} from './song';
import {Observable} from 'rxjs';
import {DbService} from '../../../services/db.service';
@@ -8,6 +8,8 @@ import {shareReplay, startWith} from 'rxjs/operators';
providedIn: 'root',
})
export class SongDataService {
private dbService = inject(DbService);
private collection = 'songs';
public list$: Observable<Song[]> = this.dbService.col$<Song>(this.collection).pipe(
startWith([] as Song[]), // immediate empty emit keeps UI responsive while first snapshot arrives
@@ -17,11 +19,6 @@ export class SongDataService {
})
);
public constructor(private dbService: DbService) {
// Warm the shared stream once at startup to avoid first-navigation delay.
// this.list$.subscribe();
}
public read$ = (songId: string): Observable<Song | null> => this.dbService.doc$(this.collection + '/' + songId);
public update$ = async (songId: string, data: Partial<Song>): Promise<void> => await this.dbService.doc(this.collection + '/' + songId).update(data);
public add = async (data: Partial<Song>): Promise<string> => (await this.dbService.col(this.collection).add(data)).id;

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core';
import {Injectable, inject} from '@angular/core';
import {Observable} from 'rxjs';
import {SongService} from './song.service';
@@ -9,7 +9,7 @@ import {take} from 'rxjs/operators';
providedIn: 'root',
})
export class SongListResolver {
public constructor(private songService: SongService) {}
private songService = inject(SongService);
public resolve(): Observable<Song[]> {
return this.songService.list$().pipe(take(1));

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core';
import {Injectable, inject} from '@angular/core';
import {firstValueFrom, Observable} from 'rxjs';
import {Song} from './song';
import {SongDataService} from './song-data.service';
@@ -16,21 +16,15 @@ export type SongLegalType = 'open' | 'allowed';
providedIn: 'root',
})
export class SongService {
private songDataService = inject(SongDataService);
private userService = inject(UserService);
public static TYPES: SongType[] = ['Praise', 'Worship', 'Misc'];
public static STATUS: SongStatus[] = ['draft', 'set', 'final'];
public static LEGAL_OWNER: SongLegalOwner[] = ['CCLI', 'other'];
public static LEGAL_TYPE: SongLegalType[] = ['open', 'allowed'];
// private list: Song[];
public constructor(
private songDataService: SongDataService,
private userService: UserService
) {
// importCCLI = (songs: Song[]) => this.updateFromCLI(songs);
}
public list$ = (): Observable<Song[]> => this.songDataService.list$; //.pipe(tap(_ => (this.list = _)));
public read$ = (songId: string): Observable<Song | null> => this.songDataService.read$(songId);
public read = (songId: string): Promise<Song | null> => firstValueFrom(this.read$(songId));

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core';
import {Injectable, inject} from '@angular/core';
import {TransposeService} from './transpose.service';
import {TransposeMode} from './transpose-mode';
import {SectionType} from './section-type';
@@ -11,9 +11,9 @@ import {Line} from './line';
providedIn: 'root',
})
export class TextRenderingService {
private regexSection = /(Strophe|Refrain|Bridge)/;
private transposeService = inject(TransposeService);
public constructor(private transposeService: TransposeService) {}
private regexSection = /(Strophe|Refrain|Bridge)/;
public parse(text: string, transpose: TransposeMode | null, withComments = true): Section[] {
if (!text) {

View File

@@ -34,18 +34,18 @@ describe('TransposeService', () => {
const distance = service.getDistance('C', 'Db');
const map = service.getMap('C', 'Db', distance);
expect(distance).toBe(1);
expect(map?.['C']).toBe('Db');
expect(map?.['G']).toBe('Ab');
void expect(distance).toBe(1);
void expect(map?.['C']).toBe('Db');
void expect(map?.['G']).toBe('Ab');
});
it('should keep german B/H notation consistent', () => {
const distance = service.getDistance('H', 'C');
const map = service.getMap('H', 'C', distance);
expect(distance).toBe(1);
expect(map?.['H']).toBe('C');
expect(map?.['B']).toBe('C#');
void expect(distance).toBe(1);
void expect(map?.['H']).toBe('C');
void expect(map?.['B']).toBe('C#');
});
it('should render unknown chords as X', () => {
@@ -57,7 +57,7 @@ describe('TransposeService', () => {
const rendered = service.renderChords(line);
expect(rendered.text).toBe('Xsus4');
void expect(rendered.text).toBe('Xsus4');
});
it('should render unknown slash chords as X', () => {
@@ -69,7 +69,7 @@ describe('TransposeService', () => {
const rendered = service.renderChords(line);
expect(rendered.text).toBe('C/X');
void expect(rendered.text).toBe('C/X');
});
it('should transpose lines with long chord positions without truncating', () => {
@@ -81,7 +81,7 @@ describe('TransposeService', () => {
const rendered = service.renderChords(line);
expect(rendered.text.length).toBe(121);
expect(rendered.text.endsWith('C')).toBeTrue();
void expect(rendered.text.length).toBe(121);
void expect(rendered.text.endsWith('C')).toBeTrue();
});
});

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core';
import {Injectable, inject} from '@angular/core';
import {Upload} from './upload';
import {FileDataService} from './file-data.service';
import {ref, Storage, uploadBytesResumable} from '@angular/fire/storage';
@@ -9,12 +9,8 @@ import {FileServer} from './fileServer';
providedIn: 'root',
})
export class UploadService extends FileBase {
public constructor(
private fileDataService: FileDataService,
private storage: Storage
) {
super();
}
private fileDataService = inject(FileDataService);
private storage = inject(Storage);
public pushUpload(songId: string, upload: Upload): void {
const directory = this.directory(songId);

View File

@@ -1,4 +1,4 @@
import {Component, Input} from '@angular/core';
import {Component, Input, inject} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup} from '@angular/forms';
import {SongService} from '../../services/song.service';
@@ -21,6 +21,8 @@ import {SongTypePipe} from '../../../../widget-modules/pipes/song-type-translate
imports: [ReactiveFormsModule, MatFormField, MatLabel, MatInput, MatSelect, MatOption, LegalTypePipe, KeyPipe, SongTypePipe],
})
export class FilterComponent {
private router = inject(Router);
public filterFormGroup: UntypedFormGroup;
@Input() public route = '/';
@Input() public songs: Song[] = [];
@@ -28,11 +30,10 @@ export class FilterComponent {
public legalType = SongService.LEGAL_TYPE;
public keys = KEYS;
public constructor(
private router: Router,
activatedRoute: ActivatedRoute,
fb: UntypedFormBuilder
) {
public constructor() {
const activatedRoute = inject(ActivatedRoute);
const fb = inject(UntypedFormBuilder);
this.filterFormGroup = fb.group({
q: '',
type: '',

View File

@@ -1,4 +1,4 @@
import {ChangeDetectionStrategy, Component, OnDestroy, OnInit} from '@angular/core';
import {ChangeDetectionStrategy, Component, OnDestroy, OnInit, inject} from '@angular/core';
import {SongService} from '../services/song.service';
import {Song} from '../services/song';
import {map} from 'rxjs/operators';
@@ -25,6 +25,10 @@ import {FaIconComponent} from '@fortawesome/angular-fontawesome';
imports: [ListHeaderComponent, FilterComponent, CardComponent, RouterLink, RoleDirective, FaIconComponent, AsyncPipe],
})
export class SongListComponent implements OnInit, OnDestroy {
private songService = inject(SongService);
private activatedRoute = inject(ActivatedRoute);
private scrollService = inject(ScrollService);
public anyFilterActive = false;
public songs$: Observable<Song[]> = combineLatest([
this.activatedRoute.queryParams.pipe(map(_ => _ as FilterValues)),
@@ -39,12 +43,6 @@ export class SongListComponent implements OnInit, OnDestroy {
public faDraft = faPencilRuler;
public faFinal = faCheck;
public constructor(
private songService: SongService,
private activatedRoute: ActivatedRoute,
private scrollService: ScrollService
) {}
public ngOnInit(): void {
setTimeout(() => this.scrollService.restoreScrollPositionFor('songlist'), 100);
setTimeout(() => this.scrollService.restoreScrollPositionFor('songlist'), 300);

View File

@@ -1,4 +1,4 @@
import {Component} from '@angular/core';
import {Component, inject} from '@angular/core';
import {Upload} from '../../../services/upload';
import {UploadService} from '../../../services/upload.service';
import {ActivatedRoute} from '@angular/router';
@@ -19,16 +19,16 @@ import {FileComponent} from './file/file.component';
imports: [CardComponent, NgStyle, MatIconButton, MatIcon, FileComponent, AsyncPipe],
})
export class EditFileComponent {
private activatedRoute = inject(ActivatedRoute);
private uploadService = inject(UploadService);
private fileService = inject(FileDataService);
public selectedFiles: FileList | null = null;
public currentUpload: Upload | null = null;
public songId: string | null = null;
public files$: Observable<File[]>;
public constructor(
private activatedRoute: ActivatedRoute,
private uploadService: UploadService,
private fileService: FileDataService
) {
public constructor() {
this.activatedRoute.params
.pipe(
map(param => param as {songId: string}),

View File

@@ -1,4 +1,4 @@
import {Component, Input} from '@angular/core';
import {Component, Input, inject} from '@angular/core';
import {Observable} from 'rxjs';
import {File} from '../../../../services/file';
import {faTrashAlt} from '@fortawesome/free-solid-svg-icons';
@@ -14,6 +14,8 @@ import {AsyncPipe} from '@angular/common';
imports: [MatIconButton, FaIconComponent, AsyncPipe],
})
export class FileComponent {
private fileService = inject(FileService);
public url$: Observable<string> | null = null;
public name = '';
public faTrash = faTrashAlt;
@@ -21,8 +23,6 @@ export class FileComponent {
private fileId: string | null = null;
private path: string | null = null;
public constructor(private fileService: FileService) {}
@Input()
public set file(file: File) {
this.url$ = this.fileService.getDownloadUrl(file.path + '/' + file.name);

View File

@@ -1,4 +1,4 @@
import {Component, OnInit} from '@angular/core';
import {Component, OnInit, inject} from '@angular/core';
import {Song} from '../../../services/song';
import {ReactiveFormsModule, UntypedFormGroup} from '@angular/forms';
import {ActivatedRoute, Router, RouterStateSnapshot} from '@angular/router';
@@ -59,6 +59,12 @@ import {StatusPipe} from '../../../../../widget-modules/pipes/status-translater/
],
})
export class EditSongComponent implements OnInit {
private activatedRoute = inject(ActivatedRoute);
private songService = inject(SongService);
private editService = inject(EditService);
private router = inject(Router);
public dialog = inject(MatDialog);
public song: Song | null = null;
public form: UntypedFormGroup = new UntypedFormGroup({});
public keys = KEYS;
@@ -73,14 +79,6 @@ export class EditSongComponent implements OnInit {
public faLink = faExternalLinkAlt;
public songtextFocus = false;
public constructor(
private activatedRoute: ActivatedRoute,
private songService: SongService,
private editService: EditService,
private router: Router,
public dialog: MatDialog
) {}
public ngOnInit(): void {
this.activatedRoute.params
.pipe(

View File

@@ -1,4 +1,4 @@
import {Component, OnInit} from '@angular/core';
import {Component, OnInit, inject} from '@angular/core';
import {first, map, switchMap} from 'rxjs/operators';
import {ActivatedRoute} from '@angular/router';
import {SongService} from '../../../services/song.service';
@@ -13,12 +13,10 @@ import {CardComponent} from '../../../../../widget-modules/components/card/card.
imports: [CardComponent, DatePipe],
})
export class HistoryComponent implements OnInit {
public song: Song | null = null;
private activatedRoute = inject(ActivatedRoute);
private songService = inject(SongService);
public constructor(
private activatedRoute: ActivatedRoute,
private songService: SongService
) {}
public song: Song | null = null;
public ngOnInit(): void {
this.activatedRoute.params

View File

@@ -1,4 +1,4 @@
import {Component, Input} from '@angular/core';
import {Component, Input, inject} from '@angular/core';
import {File} from '../../services/file';
import {getDownloadURL, ref, Storage} from '@angular/fire/storage';
import {from, Observable} from 'rxjs';
@@ -11,11 +11,11 @@ import {AsyncPipe} from '@angular/common';
imports: [AsyncPipe],
})
export class FileComponent {
private storage = inject(Storage);
public url$: Observable<string> | null = null;
public name = '';
public constructor(private storage: Storage) {}
@Input()
public set file(file: File) {
this.url$ = from(getDownloadURL(ref(this.storage, file.path + '/' + file.name)));

View File

@@ -1,4 +1,4 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {Component, OnDestroy, OnInit, inject} from '@angular/core';
import {faSave} from '@fortawesome/free-solid-svg-icons';
import {ReactiveFormsModule, UntypedFormControl, UntypedFormGroup, Validators} from '@angular/forms';
import {SongService} from '../../services/song.service';
@@ -19,6 +19,9 @@ import {ButtonComponent} from '../../../../widget-modules/components/button/butt
imports: [CardComponent, ReactiveFormsModule, MatFormField, MatLabel, MatInput, ButtonRowComponent, ButtonComponent],
})
export class NewComponent implements OnInit, OnDestroy {
private songService = inject(SongService);
private router = inject(Router);
public faSave = faSave;
public form: UntypedFormGroup = new UntypedFormGroup({
number: new UntypedFormControl(null, Validators.required),
@@ -26,11 +29,6 @@ export class NewComponent implements OnInit, OnDestroy {
});
private subs: Subscription[] = [];
public constructor(
private songService: SongService,
private router: Router
) {}
public ngOnInit(): void {
this.form.reset();

View File

@@ -1,4 +1,4 @@
import {Component, OnInit} from '@angular/core';
import {Component, OnInit, inject} from '@angular/core';
import {ActivatedRoute, Router, RouterLink} from '@angular/router';
import {SongService} from '../services/song.service';
import {distinctUntilChanged, map, switchMap} from 'rxjs/operators';
@@ -51,6 +51,14 @@ import {ShowTypePipe} from '../../../widget-modules/pipes/show-type-translater/s
],
})
export class SongComponent implements OnInit {
private activatedRoute = inject(ActivatedRoute);
private songService = inject(SongService);
private fileService = inject(FileDataService);
private userService = inject(UserService);
private router = inject(Router);
private showService = inject(ShowService);
private showSongService = inject(ShowSongService);
public song$: Observable<Song | null> | null = null;
public files$: Observable<File[] | null> | null = null;
public user$: Observable<User | null> | null = null;
@@ -60,15 +68,9 @@ export class SongComponent implements OnInit {
public faFileCirclePlus = faFileCirclePlus;
public privateShows$ = this.showService.list$().pipe(map(show => show.filter(_ => !_.published).sort((a, b) => b.date.toMillis() - a.date.toMillis())));
public constructor(
private activatedRoute: ActivatedRoute,
private songService: SongService,
private fileService: FileDataService,
private userService: UserService,
private router: Router,
private showService: ShowService,
private showSongService: ShowSongService
) {
public constructor() {
const userService = this.userService;
this.user$ = userService.user$;
}