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,12 +1,7 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. # Angular 21-supported baseline to avoid unsupported-browser build warnings.
# For additional information regarding the format and rule options, please see: last 2 Chrome major versions
# https://github.com/browserslist/browserslist#queries last 2 Edge major versions
last 2 Firefox major versions
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 0.5%
last 2 versions
Firefox ESR Firefox ESR
not dead last 2 Safari major versions
not IE 9-11 # For IE 9-11 support, remove 'not'. last 2 iOS major versions

804
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@
"@angular/common": "^21.2.2", "@angular/common": "^21.2.2",
"@angular/compiler": "^21.2.2", "@angular/compiler": "^21.2.2",
"@angular/core": "^21.2.2", "@angular/core": "^21.2.2",
"@angular/fire": "^21.0.0-rc.0", "@angular/fire": "21.0.0-rc.0",
"@angular/forms": "^21.2.2", "@angular/forms": "^21.2.2",
"@angular/material": "^21.2.1", "@angular/material": "^21.2.1",
"@angular/platform-browser": "^21.2.2", "@angular/platform-browser": "^21.2.2",
@@ -28,7 +28,6 @@
"@fortawesome/free-solid-svg-icons": "^7.2.0", "@fortawesome/free-solid-svg-icons": "^7.2.0",
"docx": "^9.6.0", "docx": "^9.6.0",
"firebase": "^12.10.0", "firebase": "^12.10.0",
"lodash": "^4.17.21",
"ngx-mat-select-search": "^8.0.4", "ngx-mat-select-search": "^8.0.4",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"rxjs": "~7.8.1", "rxjs": "~7.8.1",
@@ -49,13 +48,12 @@
"@angular/language-service": "^21.2.2", "@angular/language-service": "^21.2.2",
"@types/jasmine": "~6.0.0", "@types/jasmine": "~6.0.0",
"@types/jasminewd2": "~2.0.13", "@types/jasminewd2": "~2.0.13",
"@types/lodash": "^4.17.24",
"@typescript-eslint/eslint-plugin": "^8.57.0", "@typescript-eslint/eslint-plugin": "^8.57.0",
"@typescript-eslint/parser": "^8.57.0", "@typescript-eslint/parser": "^8.57.0",
"eslint": "^9.39.4", "eslint": "^9.39.4",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.5", "eslint-plugin-prettier": "^5.5.5",
"firebase-tools": "^14.27.0", "firebase-tools": "^15.9.1",
"jasmine-core": "~6.1.0", "jasmine-core": "~6.1.0",
"jasmine-spec-reporter": "~7.0.0", "jasmine-spec-reporter": "~7.0.0",
"karma": "~6.4.4", "karma": "~6.4.4",

View File

@@ -1,4 +1,4 @@
import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core'; import {ChangeDetectionStrategy, Component, OnInit, inject} from '@angular/core';
import {fader} from './animations'; import {fader} from './animations';
import {ScrollService} from './services/scroll.service'; import {ScrollService} from './services/scroll.service';
import {register} from 'swiper/element/bundle'; import {register} from 'swiper/element/bundle';
@@ -14,7 +14,9 @@ import {NavigationComponent} from './widget-modules/components/application-frame
imports: [RouterOutlet, NavigationComponent], imports: [RouterOutlet, NavigationComponent],
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
public constructor(private scrollService: ScrollService) { private scrollService = inject(ScrollService);
public constructor() {
register(); register();
} }

View File

@@ -1,4 +1,4 @@
import {Component} from '@angular/core'; import {Component, inject} from '@angular/core';
import {UserService} from '../../../services/user/user.service'; import {UserService} from '../../../services/user/user.service';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
import {User} from '../../../services/user/user'; import {User} from '../../../services/user/user';
@@ -12,9 +12,13 @@ import {AsyncPipe} from '@angular/common';
imports: [BrandComponent, AsyncPipe], imports: [BrandComponent, AsyncPipe],
}) })
export class NewUserComponent { export class NewUserComponent {
private userService = inject(UserService);
public user$: Observable<User | null> | null = null; public user$: Observable<User | null> | null = null;
public constructor(private userService: UserService) { public constructor() {
const userService = this.userService;
this.user$ = userService.user$; this.user$ = userService.user$;
} }
} }

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core'; import {Injectable, inject} from '@angular/core';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
import {shareReplay} from 'rxjs/operators'; import {shareReplay} from 'rxjs/operators';
import {DbService} from 'src/app/services/db.service'; import {DbService} from 'src/app/services/db.service';
@@ -8,6 +8,8 @@ import {GuestShow} from './guest-show';
providedIn: 'root', providedIn: 'root',
}) })
export class GuestShowDataService { export class GuestShowDataService {
private dbService = inject(DbService);
private collection = 'guest'; private collection = 'guest';
public list$: Observable<GuestShow[]> = this.dbService.col$<GuestShow>(this.collection).pipe( public list$: Observable<GuestShow[]> = this.dbService.col$<GuestShow>(this.collection).pipe(
shareReplay({ shareReplay({
@@ -16,8 +18,6 @@ export class GuestShowDataService {
}) })
); );
public constructor(private dbService: DbService) {}
public read$: (id: string) => Observable<GuestShow | null> = (id: string): Observable<GuestShow | null> => this.dbService.doc$(`${this.collection}/${id}`); public read$: (id: string) => Observable<GuestShow | null> = (id: string): Observable<GuestShow | null> => this.dbService.doc$(`${this.collection}/${id}`);
public update$: (id: string, data: Partial<GuestShow>) => Promise<void> = async (id: string, data: Partial<GuestShow>): Promise<void> => public update$: (id: string, data: Partial<GuestShow>) => Promise<void> = async (id: string, data: Partial<GuestShow>): Promise<void> =>
await this.dbService.doc(this.collection + '/' + id).update(data); await this.dbService.doc(this.collection + '/' + id).update(data);

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core'; import {Injectable, inject} from '@angular/core';
import {Show} from '../shows/services/show'; import {Show} from '../shows/services/show';
import {Song} from '../songs/services/song'; import {Song} from '../songs/services/song';
import {GuestShowDataService} from './guest-show-data.service'; import {GuestShowDataService} from './guest-show-data.service';
@@ -8,10 +8,8 @@ import {ShowService} from '../shows/services/show.service';
providedIn: 'root', providedIn: 'root',
}) })
export class GuestShowService { export class GuestShowService {
public constructor( private showService = inject(ShowService);
private showService: ShowService, private guestShowDataService = inject(GuestShowDataService);
private guestShowDataService: GuestShowDataService
) {}
public async share(show: Show, songs: Song[]): Promise<string> { public async share(show: Show, songs: Song[]): Promise<string> {
const data = { const data = {

View File

@@ -1,4 +1,4 @@
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; import {Component, CUSTOM_ELEMENTS_SCHEMA, inject} from '@angular/core';
import {GuestShowDataService} from './guest-show-data.service'; import {GuestShowDataService} from './guest-show-data.service';
import {ActivatedRoute} from '@angular/router'; import {ActivatedRoute} from '@angular/router';
import {map, switchMap} from 'rxjs/operators'; import {map, switchMap} from 'rxjs/operators';
@@ -16,17 +16,15 @@ import {ShowTypePipe} from '../../widget-modules/pipes/show-type-translater/show
imports: [SongTextComponent, AsyncPipe, DatePipe, ShowTypePipe], imports: [SongTextComponent, AsyncPipe, DatePipe, ShowTypePipe],
}) })
export class GuestComponent { export class GuestComponent {
private currentRoute = inject(ActivatedRoute);
private service = inject(GuestShowDataService);
private configService = inject(ConfigService);
public show$ = this.currentRoute.params.pipe( public show$ = this.currentRoute.params.pipe(
map(param => param.id as string), map(param => param.id as string),
switchMap(id => this.service.read$(id)) switchMap(id => this.service.read$(id))
); );
public config$ = this.configService.get$(); public config$ = this.configService.get$();
public constructor(
private currentRoute: ActivatedRoute,
private service: GuestShowDataService,
private configService: ConfigService
) {}
public trackBy = (index: number, show: Song) => show.id; public trackBy = (index: number, show: Song) => show.id;
} }

View File

@@ -1,4 +1,4 @@
import {ChangeDetectorRef, Component, OnDestroy, OnInit} from '@angular/core'; import {ChangeDetectorRef, Component, OnDestroy, OnInit, inject} from '@angular/core';
import {distinctUntilChanged, filter, map, shareReplay, switchMap, takeUntil, tap} from 'rxjs/operators'; import {distinctUntilChanged, filter, map, shareReplay, switchMap, takeUntil, tap} from 'rxjs/operators';
import {ShowService} from '../../shows/services/show.service'; import {ShowService} from '../../shows/services/show.service';
import {Song} from '../../songs/services/song'; import {Song} from '../../songs/services/song';
@@ -25,6 +25,12 @@ import {ShowTypePipe} from '../../../widget-modules/pipes/show-type-translater/s
imports: [LogoComponent, SongTextComponent, LegalComponent, AsyncPipe, DatePipe, ShowTypePipe], imports: [LogoComponent, SongTextComponent, LegalComponent, AsyncPipe, DatePipe, ShowTypePipe],
}) })
export class MonitorComponent implements OnInit, OnDestroy { export class MonitorComponent implements OnInit, OnDestroy {
private showService = inject(ShowService);
private showSongService = inject(ShowSongService);
private globalSettingsService = inject(GlobalSettingsService);
private configService = inject(ConfigService);
private cRef = inject(ChangeDetectorRef);
public song: Song | null = null; public song: Song | null = null;
public zoom = 10; public zoom = 10;
public currentShowId: string | null = null; public currentShowId: string | null = null;
@@ -38,13 +44,9 @@ export class MonitorComponent implements OnInit, OnDestroy {
public presentationBackground: PresentationBackground = 'none'; public presentationBackground: PresentationBackground = 'none';
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
public constructor( public constructor() {
private showService: ShowService, const configService = this.configService;
private showSongService: ShowSongService,
private globalSettingsService: GlobalSettingsService,
private configService: ConfigService,
private cRef: ChangeDetectorRef
) {
this.config$ = configService.get$(); this.config$ = configService.get$();
} }

View File

@@ -1,4 +1,4 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy} from '@angular/core'; import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, inject} from '@angular/core';
import {combineLatest, Subject} from 'rxjs'; import {combineLatest, Subject} from 'rxjs';
import {PresentationBackground, Show} from '../../shows/services/show'; import {PresentationBackground, Show} from '../../shows/services/show';
import {ShowSongService} from '../../shows/services/show-song.service'; import {ShowSongService} from '../../shows/services/show-song.service';
@@ -61,6 +61,12 @@ export interface PresentationSong {
], ],
}) })
export class RemoteComponent implements OnDestroy { export class RemoteComponent implements OnDestroy {
private showService = inject(ShowService);
private showSongService = inject(ShowSongService);
private songService = inject(SongService);
private textRenderingService = inject(TextRenderingService);
private cRef = inject(ChangeDetectorRef);
public show: Show | null = null; public show: Show | null = null;
public showSongs: ShowSong[] = []; public showSongs: ShowSong[] = [];
public songs$ = this.songService.list$(); public songs$ = this.songService.list$();
@@ -73,14 +79,9 @@ export class RemoteComponent implements OnDestroy {
public presentationDynamicTextChanged$ = new Subject<{presentationDynamicText: string; showId: string}>(); public presentationDynamicTextChanged$ = new Subject<{presentationDynamicText: string; showId: string}>();
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
public constructor( public constructor() {
private showService: ShowService, const globalSettingsService = inject(GlobalSettingsService);
private showSongService: ShowSongService,
private songService: SongService,
private textRenderingService: TextRenderingService,
globalSettingsService: GlobalSettingsService,
private cRef: ChangeDetectorRef
) {
const currentShowId$ = globalSettingsService.get$.pipe( const currentShowId$ = globalSettingsService.get$.pipe(
filter((settings): settings is NonNullable<typeof settings> => !!settings), filter((settings): settings is NonNullable<typeof settings> => !!settings),
map(_ => _.currentShow), map(_ => _.currentShow),

View File

@@ -1,4 +1,4 @@
import {Component, OnInit} from '@angular/core'; import {Component, OnInit, inject} from '@angular/core';
import {map} from 'rxjs/operators'; import {map} from 'rxjs/operators';
import {ShowService} from '../../shows/services/show.service'; import {ShowService} from '../../shows/services/show.service';
import {Show} from '../../shows/services/show'; import {Show} from '../../shows/services/show';
@@ -19,17 +19,15 @@ import {ShowTypePipe} from '../../../widget-modules/pipes/show-type-translater/s
imports: [CardComponent, MatButton, UserNameComponent, AsyncPipe, DatePipe, ShowTypePipe], imports: [CardComponent, MatButton, UserNameComponent, AsyncPipe, DatePipe, ShowTypePipe],
}) })
export class SelectComponent implements OnInit { export class SelectComponent implements OnInit {
private showService = inject(ShowService);
private globalSettingsService = inject(GlobalSettingsService);
private router = inject(Router);
public visible = false; public visible = false;
public shows$ = this.showService public shows$ = this.showService
.list$(true) .list$(true)
.pipe(map(_ => _.filter(_ => _.date.toDate() > new Date(new Date().setMonth(new Date().getMonth() - 1))).sort((a, b) => (b.date < a.date ? -1 : b.date > a.date ? 1 : 0)))); .pipe(map(_ => _.filter(_ => _.date.toDate() > new Date(new Date().setMonth(new Date().getMonth() - 1))).sort((a, b) => (b.date < a.date ? -1 : b.date > a.date ? 1 : 0))));
public constructor(
private showService: ShowService,
private globalSettingsService: GlobalSettingsService,
private router: Router
) {}
public async selectShow(show: Show) { public async selectShow(show: Show) {
this.visible = false; this.visible = false;

View File

@@ -1,4 +1,4 @@
import {Component, Inject} from '@angular/core'; import {Component, inject} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogActions, MatDialogClose, MatDialogContent} from '@angular/material/dialog'; import {MAT_DIALOG_DATA, MatDialogActions, MatDialogClose, MatDialogContent} from '@angular/material/dialog';
import {MatButton} from '@angular/material/button'; import {MatButton} from '@angular/material/button';
import QRCode from 'qrcode'; import QRCode from 'qrcode';
@@ -17,9 +17,13 @@ export interface ShareDialogData {
styleUrl: './share-dialog.component.less', styleUrl: './share-dialog.component.less',
}) })
export class ShareDialogComponent { export class ShareDialogComponent {
public data = inject<ShareDialogData>(MAT_DIALOG_DATA);
public qrCode: string; public qrCode: string;
public constructor(@Inject(MAT_DIALOG_DATA) public data: ShareDialogData) { public constructor() {
const data = this.data;
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
QRCode.toDataURL(data.url, { QRCode.toDataURL(data.url, {
type: 'image/jpeg', type: 'image/jpeg',

View File

@@ -1,4 +1,4 @@
import {Component, OnInit} from '@angular/core'; import {Component, OnInit, inject} from '@angular/core';
import {ShowDataService} from '../services/show-data.service'; import {ShowDataService} from '../services/show-data.service';
import {Observable, take} from 'rxjs'; import {Observable, take} from 'rxjs';
import {Show} from '../services/show'; import {Show} from '../services/show';
@@ -42,6 +42,10 @@ import {ShowTypePipe} from '../../../widget-modules/pipes/show-type-translater/s
], ],
}) })
export class EditComponent implements OnInit { export class EditComponent implements OnInit {
private showService = inject(ShowService);
private router = inject(Router);
private activatedRoute = inject(ActivatedRoute);
public shows$: Observable<Show[]>; public shows$: Observable<Show[]>;
public showTypePublic = ShowService.SHOW_TYPE_PUBLIC; public showTypePublic = ShowService.SHOW_TYPE_PUBLIC;
public showTypePrivate = ShowService.SHOW_TYPE_PRIVATE; public showTypePrivate = ShowService.SHOW_TYPE_PRIVATE;
@@ -52,12 +56,9 @@ export class EditComponent implements OnInit {
}); });
public faSave = faSave; public faSave = faSave;
public constructor( public constructor() {
private showService: ShowService, const showDataService = inject(ShowDataService);
showDataService: ShowDataService,
private router: Router,
private activatedRoute: ActivatedRoute
) {
this.shows$ = showDataService.list$; this.shows$ = showDataService.list$;
} }

View File

@@ -1,4 +1,4 @@
import {Component, Input} from '@angular/core'; import {Component, Input, inject} from '@angular/core';
import {KeyValue} from '@angular/common'; import {KeyValue} from '@angular/common';
import {ActivatedRoute, Router} from '@angular/router'; import {ActivatedRoute, Router} from '@angular/router';
import {ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup} from '@angular/forms'; import {ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup} from '@angular/forms';
@@ -9,7 +9,6 @@ import {distinctUntilChanged, map, switchMap} from 'rxjs/operators';
import {combineLatest, Observable, of} from 'rxjs'; import {combineLatest, Observable, of} from 'rxjs';
import {dynamicSort, onlyUnique} from '../../../../services/filter.helper'; import {dynamicSort, onlyUnique} from '../../../../services/filter.helper';
import {UserService} from '../../../../services/user/user.service'; import {UserService} from '../../../../services/user/user.service';
import {isEqual} from 'lodash';
import {MatFormField, MatLabel} from '@angular/material/form-field'; import {MatFormField, MatLabel} from '@angular/material/form-field';
import {MatSelect} from '@angular/material/select'; import {MatSelect} from '@angular/material/select';
import {MatOptgroup, MatOption} from '@angular/material/core'; import {MatOptgroup, MatOption} from '@angular/material/core';
@@ -22,6 +21,10 @@ import {ShowTypePipe} from '../../../../widget-modules/pipes/show-type-translate
imports: [ReactiveFormsModule, MatFormField, MatLabel, MatSelect, MatOption, MatOptgroup, ShowTypePipe], imports: [ReactiveFormsModule, MatFormField, MatLabel, MatSelect, MatOption, MatOptgroup, ShowTypePipe],
}) })
export class FilterComponent { export class FilterComponent {
private router = inject(Router);
private showService = inject(ShowService);
private userService = inject(UserService);
@Input() public route = '/shows/'; @Input() public route = '/shows/';
@Input() public shows: Show[] = []; @Input() public shows: Show[] = [];
@@ -38,13 +41,10 @@ export class FilterComponent {
public owners: {key: string; value: string}[] = []; public owners: {key: string; value: string}[] = [];
public constructor( public constructor() {
private router: Router, const activatedRoute = inject(ActivatedRoute);
private showService: ShowService, const fb = inject(UntypedFormBuilder);
private userService: UserService,
activatedRoute: ActivatedRoute,
fb: UntypedFormBuilder
) {
this.filterFormGroup = fb.group({ this.filterFormGroup = fb.group({
time: 1, time: 1,
owner: null, owner: null,
@@ -92,7 +92,7 @@ export class FilterComponent {
map(owners => { map(owners => {
return owners.sort(dynamicSort('value')); return owners.sort(dynamicSort('value'));
}), }),
distinctUntilChanged(isEqual), distinctUntilChanged((left, right) => this.sameOwners(left, right)),
map(_ => _ as {key: string; value: string}[]) map(_ => _ as {key: string; value: string}[])
); );
}; };
@@ -104,4 +104,12 @@ export class FilterComponent {
}); });
await this.router.navigateByUrl(route); await this.router.navigateByUrl(route);
} }
private sameOwners(left: {key: string; value: string}[], right: {key: string; value: string}[]): boolean {
if (left.length !== right.length) {
return false;
}
return left.every((owner, index) => owner.key === right[index]?.key && owner.value === right[index]?.value);
}
} }

View File

@@ -1,4 +1,4 @@
import {Component} from '@angular/core'; import {Component, inject} from '@angular/core';
import {combineLatest} from 'rxjs'; import {combineLatest} from 'rxjs';
import {Show} from '../services/show'; import {Show} from '../services/show';
import {fade} from '../../../animations'; import {fade} from '../../../animations';
@@ -22,8 +22,9 @@ import {SortByPipe} from '../../../widget-modules/pipes/sort-by/sort-by.pipe';
imports: [RoleDirective, ListHeaderComponent, FilterComponent, CardComponent, ListItemComponent, RouterLink, AsyncPipe, SortByPipe], imports: [RoleDirective, ListHeaderComponent, FilterComponent, CardComponent, ListItemComponent, RouterLink, AsyncPipe, SortByPipe],
}) })
export class ListComponent { export class ListComponent {
public shows$ = this.showService.list$(); private showService = inject(ShowService);
public privateShows$ = this.showService.list$().pipe(map(show => show.filter(_ => !_.published))); private activatedRoute = inject(ActivatedRoute);
public lastMonths$ = this.activatedRoute.queryParams.pipe( public lastMonths$ = this.activatedRoute.queryParams.pipe(
map(params => { map(params => {
const filterValues = params as FilterValues; const filterValues = params as FilterValues;
@@ -43,8 +44,9 @@ export class ListComponent {
return filterValues?.showType; return filterValues?.showType;
}) })
); );
public shows$ = this.showService.list$();
public privateShows$ = this.showService.list$().pipe(map(show => show.filter(_ => !_.published)));
public queriedPublicShows$ = this.lastMonths$.pipe(switchMap(lastMonths => this.showService.listPublicSince$(lastMonths))); public queriedPublicShows$ = this.lastMonths$.pipe(switchMap(lastMonths => this.showService.listPublicSince$(lastMonths)));
public fallbackPublicShows$ = combineLatest([this.shows$, this.lastMonths$]).pipe( public fallbackPublicShows$ = combineLatest([this.shows$, this.lastMonths$]).pipe(
map(([shows, lastMonths]) => { map(([shows, lastMonths]) => {
const startDate = new Date(); const startDate = new Date();
@@ -54,7 +56,6 @@ export class ListComponent {
return shows.filter(show => show.published && !show.archived && show.date.toDate() >= startDate); return shows.filter(show => show.published && !show.archived && show.date.toDate() >= startDate);
}) })
); );
public publicShows$ = combineLatest([this.queriedPublicShows$, this.fallbackPublicShows$, this.owner$, this.showType$]).pipe( public publicShows$ = combineLatest([this.queriedPublicShows$, this.fallbackPublicShows$, this.owner$, this.showType$]).pipe(
map(([queriedShows, fallbackShows, owner, showType]) => { map(([queriedShows, fallbackShows, owner, showType]) => {
const shows = queriedShows.length > 0 || fallbackShows.length === 0 ? queriedShows : fallbackShows; const shows = queriedShows.length > 0 || fallbackShows.length === 0 ? queriedShows : fallbackShows;
@@ -63,10 +64,5 @@ export class ListComponent {
}) })
); );
public constructor(
private showService: ShowService,
private activatedRoute: ActivatedRoute
) {}
public trackBy = (index: number, show: unknown) => (show as Show).id; public trackBy = (index: number, show: unknown) => (show as Show).id;
} }

View File

@@ -1,4 +1,4 @@
import {Component, OnInit} from '@angular/core'; import {Component, OnInit, inject} from '@angular/core';
import {ShowDataService} from '../services/show-data.service'; import {ShowDataService} from '../services/show-data.service';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
import {Show} from '../services/show'; import {Show} from '../services/show';
@@ -40,6 +40,9 @@ import {ShowTypePipe} from '../../../widget-modules/pipes/show-type-translater/s
], ],
}) })
export class NewComponent implements OnInit { export class NewComponent implements OnInit {
private showService = inject(ShowService);
private router = inject(Router);
public shows$: Observable<Show[]>; public shows$: Observable<Show[]>;
public showTypePublic = ShowService.SHOW_TYPE_PUBLIC; public showTypePublic = ShowService.SHOW_TYPE_PUBLIC;
public showTypePrivate = ShowService.SHOW_TYPE_PRIVATE; public showTypePrivate = ShowService.SHOW_TYPE_PRIVATE;
@@ -49,11 +52,9 @@ export class NewComponent implements OnInit {
}); });
public faSave = faSave; public faSave = faSave;
public constructor( public constructor() {
private showService: ShowService, const showDataService = inject(ShowDataService);
showDataService: ShowDataService,
private router: Router
) {
this.shows$ = showDataService.list$; this.shows$ = showDataService.list$;
} }

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core'; import {Injectable, inject} from '@angular/core';
import {Document, HeadingLevel, ISectionOptions, Packer, Paragraph} from 'docx'; import {Document, HeadingLevel, ISectionOptions, Packer, Paragraph} from 'docx';
import {ShowService} from './show.service'; import {ShowService} from './show.service';
import {ShowTypePipe} from '../../../widget-modules/pipes/show-type-translater/show-type.pipe'; import {ShowTypePipe} from '../../../widget-modules/pipes/show-type-translater/show-type.pipe';
@@ -26,13 +26,11 @@ export interface DownloadOptions {
providedIn: 'root', providedIn: 'root',
}) })
export class DocxService { export class DocxService {
public constructor( private showService = inject(ShowService);
private showService: ShowService, private showSongService = inject(ShowSongService);
private showSongService: ShowSongService, private textRenderingService = inject(TextRenderingService);
private textRenderingService: TextRenderingService, private userService = inject(UserService);
private userService: UserService, private configService = inject(ConfigService);
private configService: ConfigService
) {}
public async create(showId: string, options: DownloadOptions = {}): Promise<void> { public async create(showId: string, options: DownloadOptions = {}): Promise<void> {
const data = await this.prepareData(showId); const data = await this.prepareData(showId);
@@ -194,7 +192,8 @@ export class DocxService {
if (!config) return null; if (!config) return null;
const showSongs = await this.showSongService.list(showId); const showSongs = await this.showSongService.list(showId);
const songsAsync = showSongs.map(showSong => { const songsLoaded = showSongs
.map(showSong => {
const sections = this.textRenderingService.parse(showSong.text, { const sections = this.textRenderingService.parse(showSong.text, {
baseKey: showSong.keyOriginal, baseKey: showSong.keyOriginal,
targetKey: showSong.key, targetKey: showSong.key,
@@ -203,8 +202,7 @@ export class DocxService {
showSong, showSong,
sections, sections,
}; };
}); })
const songsLoaded = (await Promise.all(songsAsync))
.filter(_ => !!_) .filter(_ => !!_)
.map( .map(
_ => _ =>

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core'; import {Injectable, inject} from '@angular/core';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
import {DbService} from '../../../services/db.service'; import {DbService} from '../../../services/db.service';
import {Show} from './show'; import {Show} from './show';
@@ -9,6 +9,8 @@ import {orderBy, QueryConstraint, Timestamp, where} from '@angular/fire/firestor
providedIn: 'root', providedIn: 'root',
}) })
export class ShowDataService { export class ShowDataService {
private dbService = inject(DbService);
private collection = 'shows'; private collection = 'shows';
public list$: Observable<Show[]> = this.dbService.col$<Show>(this.collection).pipe( public list$: Observable<Show[]> = this.dbService.col$<Show>(this.collection).pipe(
// server-side ordering cuts client work and keeps stable order across subscribers // server-side ordering cuts client work and keeps stable order across subscribers
@@ -19,8 +21,6 @@ export class ShowDataService {
}) })
); );
public constructor(private dbService: DbService) {}
public listRaw$ = () => this.dbService.col$<Show>(this.collection); public listRaw$ = () => this.dbService.col$<Show>(this.collection);
public listPublicSince$(lastMonths: number): Observable<Show[]> { public listPublicSince$(lastMonths: number): Observable<Show[]> {

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core'; import {Injectable, inject} from '@angular/core';
import {DbService} from '../../../services/db.service'; import {DbService} from '../../../services/db.service';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
import {ShowSong} from './show-song'; import {ShowSong} from './show-song';
@@ -9,12 +9,12 @@ import {shareReplay} from 'rxjs/operators';
providedIn: 'root', providedIn: 'root',
}) })
export class ShowSongDataService { export class ShowSongDataService {
private dbService = inject(DbService);
private collection = 'shows'; private collection = 'shows';
private subCollection = 'songs'; private subCollection = 'songs';
private listCache = new Map<string, Observable<ShowSong[]>>(); private listCache = new Map<string, Observable<ShowSong[]>>();
public constructor(private dbService: DbService) {}
public list$ = (showId: string, queryConstraints?: QueryConstraint[]): Observable<ShowSong[]> => { public list$ = (showId: string, queryConstraints?: QueryConstraint[]): Observable<ShowSong[]> => {
if (queryConstraints && queryConstraints.length > 0) { if (queryConstraints && queryConstraints.length > 0) {
return this.dbService.col$(`${this.collection}/${showId}/${this.subCollection}`, queryConstraints); return this.dbService.col$(`${this.collection}/${showId}/${this.subCollection}`, queryConstraints);

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core'; import {Injectable, inject} from '@angular/core';
import {ShowSongDataService} from './show-song-data.service'; import {ShowSongDataService} from './show-song-data.service';
import {firstValueFrom, Observable} from 'rxjs'; import {firstValueFrom, Observable} from 'rxjs';
import {ShowSong} from './show-song'; import {ShowSong} from './show-song';
@@ -10,12 +10,10 @@ import {ShowService} from './show.service';
providedIn: 'root', providedIn: 'root',
}) })
export class ShowSongService { export class ShowSongService {
public constructor( private showSongDataService = inject(ShowSongDataService);
private showSongDataService: ShowSongDataService, private songDataService = inject(SongDataService);
private songDataService: SongDataService, private userService = inject(UserService);
private userService: UserService, private showService = inject(ShowService);
private showService: ShowService
) {}
public async new$(showId: string, songId: string, addedLive = false): Promise<string | null> { public async new$(showId: string, songId: string, addedLive = false): Promise<string | null> {
const song = await firstValueFrom(this.songDataService.read$(songId)); const song = await firstValueFrom(this.songDataService.read$(songId));

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core'; import {Injectable, inject} from '@angular/core';
import {ShowDataService} from './show-data.service'; import {ShowDataService} from './show-data.service';
import {Show} from './show'; import {Show} from './show';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
@@ -10,15 +10,17 @@ import {User} from '../../../services/user/user';
providedIn: 'root', providedIn: 'root',
}) })
export class ShowService { export class ShowService {
private showDataService = inject(ShowDataService);
private userService = inject(UserService);
public static SHOW_TYPE = ['service-worship', 'service-praise', 'home-group-big', 'home-group', 'prayer-group', 'teens-group', 'kids-group', 'misc-public', 'misc-private']; public static SHOW_TYPE = ['service-worship', 'service-praise', 'home-group-big', 'home-group', 'prayer-group', 'teens-group', 'kids-group', 'misc-public', 'misc-private'];
public static SHOW_TYPE_PUBLIC = ['service-worship', 'service-praise', 'home-group-big', 'teens-group', 'kids-group', 'misc-public']; public static SHOW_TYPE_PUBLIC = ['service-worship', 'service-praise', 'home-group-big', 'teens-group', 'kids-group', 'misc-public'];
public static SHOW_TYPE_PRIVATE = ['home-group', 'prayer-group', 'misc-private']; public static SHOW_TYPE_PRIVATE = ['home-group', 'prayer-group', 'misc-private'];
private user: User | null = null; private user: User | null = null;
public constructor( public constructor() {
private showDataService: ShowDataService, const userService = this.userService;
private userService: UserService
) {
userService.user$.subscribe(_ => (this.user = _)); userService.user$.subscribe(_ => (this.user = _));
} }

View File

@@ -1,4 +1,4 @@
import {ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, HostListener, OnDestroy, OnInit} from '@angular/core'; import {ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, HostListener, OnDestroy, OnInit, inject} from '@angular/core';
import {filter, map, shareReplay, switchMap, tap} from 'rxjs/operators'; import {filter, map, shareReplay, switchMap, tap} from 'rxjs/operators';
import {ActivatedRoute, Router} from '@angular/router'; import {ActivatedRoute, Router} from '@angular/router';
import {ShowService} from '../services/show.service'; import {ShowService} from '../services/show.service';
@@ -80,6 +80,16 @@ import {ShowTypePipe} from '../../../widget-modules/pipes/show-type-translater/s
], ],
}) })
export class ShowComponent implements OnInit, OnDestroy { export class ShowComponent implements OnInit, OnDestroy {
private activatedRoute = inject(ActivatedRoute);
private showService = inject(ShowService);
private songService = inject(SongService);
private showSongService = inject(ShowSongService);
private docxService = inject(DocxService);
private router = inject(Router);
private cRef = inject(ChangeDetectorRef);
public dialog = inject(MatDialog);
private guestShowService = inject(GuestShowService);
public show$: Observable<Show | null> | null = null; public show$: Observable<Show | null> | null = null;
public songs$: Observable<Song[] | null> | null = null; public songs$: Observable<Song[] | null> | null = null;
public showSongs: ShowSong[] | null = null; public showSongs: ShowSong[] | null = null;
@@ -105,18 +115,6 @@ export class ShowComponent implements OnInit, OnDestroy {
public currentTime: Date; public currentTime: Date;
private subs: Subscription[] = []; private subs: Subscription[] = [];
public constructor(
private activatedRoute: ActivatedRoute,
private showService: ShowService,
private songService: SongService,
private showSongService: ShowSongService,
private docxService: DocxService,
private router: Router,
private cRef: ChangeDetectorRef,
public dialog: MatDialog,
private guestShowService: GuestShowService
) {}
public ngOnInit(): void { public ngOnInit(): void {
this.currentTime = new Date(); this.currentTime = new Date();
setInterval(() => { setInterval(() => {

View File

@@ -1,4 +1,4 @@
import {Component, Input, OnInit, ViewChild} from '@angular/core'; import {Component, Input, OnInit, ViewChild, inject} from '@angular/core';
import {ShowSongService} from '../../services/show-song.service'; import {ShowSongService} from '../../services/show-song.service';
import {ShowSong} from '../../services/show-song'; import {ShowSong} from '../../services/show-song';
import {getScale} from '../../../songs/services/key.helper'; import {getScale} from '../../../songs/services/key.helper';
@@ -39,6 +39,8 @@ import {ButtonComponent} from '../../../../widget-modules/components/button/butt
], ],
}) })
export class SongComponent implements OnInit { export class SongComponent implements OnInit {
private showSongService = inject(ShowSongService);
@Input() public show: Show | null = null; @Input() public show: Show | null = null;
@Input() public showId: string | null = null; @Input() public showId: string | null = null;
@Input() public showText: boolean | null = null; @Input() public showText: boolean | null = null;
@@ -55,8 +57,6 @@ export class SongComponent implements OnInit {
public editSongControl = new UntypedFormControl(); public editSongControl = new UntypedFormControl();
@ViewChild('option') private keyOptions: MatSelect; @ViewChild('option') private keyOptions: MatSelect;
public constructor(private showSongService: ShowSongService) {}
@Input() @Input()
public set showSong(song: ShowSong) { public set showSong(song: ShowSong) {
this.iSong = song; this.iSong = song;

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core'; import {Injectable, inject} from '@angular/core';
import {File} from './file'; import {File} from './file';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
import {FileServer} from './fileServer'; import {FileServer} from './fileServer';
@@ -8,7 +8,7 @@ import {DbService} from '../../../services/db.service';
providedIn: 'root', providedIn: 'root',
}) })
export class FileDataService { export class FileDataService {
public constructor(private db: DbService) {} private db = inject(DbService);
public async set(songId: string, file: FileServer): Promise<string> { public async set(songId: string, file: FileServer): Promise<string> {
const songRef = this.db.doc('songs/' + songId); 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 {deleteObject, getDownloadURL, ref, Storage} from '@angular/fire/storage';
import {from, Observable} from 'rxjs'; import {from, Observable} from 'rxjs';
import {FileDataService} from './file-data.service'; import {FileDataService} from './file-data.service';
@@ -7,10 +7,8 @@ import {FileDataService} from './file-data.service';
providedIn: 'root', providedIn: 'root',
}) })
export class FileService { export class FileService {
public constructor( private storage = inject(Storage);
private storage: Storage, private fileDataService = inject(FileDataService);
private fileDataService: FileDataService
) {}
public getDownloadUrl(path: string): Observable<string> { public getDownloadUrl(path: string): Observable<string> {
return from(getDownloadURL(ref(this.storage, path))); return from(getDownloadURL(ref(this.storage, path)));

View File

@@ -2,14 +2,14 @@ import {getScale, scaleMapping} from './key.helper';
describe('key.helper', () => { describe('key.helper', () => {
it('should render Gb correctly', () => { 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', () => { 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', () => { 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 {Song} from './song';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
import {DbService} from '../../../services/db.service'; import {DbService} from '../../../services/db.service';
@@ -8,6 +8,8 @@ import {shareReplay, startWith} from 'rxjs/operators';
providedIn: 'root', providedIn: 'root',
}) })
export class SongDataService { export class SongDataService {
private dbService = inject(DbService);
private collection = 'songs'; private collection = 'songs';
public list$: Observable<Song[]> = this.dbService.col$<Song>(this.collection).pipe( public list$: Observable<Song[]> = this.dbService.col$<Song>(this.collection).pipe(
startWith([] as Song[]), // immediate empty emit keeps UI responsive while first snapshot arrives 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 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 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; 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 {Observable} from 'rxjs';
import {SongService} from './song.service'; import {SongService} from './song.service';
@@ -9,7 +9,7 @@ import {take} from 'rxjs/operators';
providedIn: 'root', providedIn: 'root',
}) })
export class SongListResolver { export class SongListResolver {
public constructor(private songService: SongService) {} private songService = inject(SongService);
public resolve(): Observable<Song[]> { public resolve(): Observable<Song[]> {
return this.songService.list$().pipe(take(1)); 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 {firstValueFrom, Observable} from 'rxjs';
import {Song} from './song'; import {Song} from './song';
import {SongDataService} from './song-data.service'; import {SongDataService} from './song-data.service';
@@ -16,21 +16,15 @@ export type SongLegalType = 'open' | 'allowed';
providedIn: 'root', providedIn: 'root',
}) })
export class SongService { export class SongService {
private songDataService = inject(SongDataService);
private userService = inject(UserService);
public static TYPES: SongType[] = ['Praise', 'Worship', 'Misc']; public static TYPES: SongType[] = ['Praise', 'Worship', 'Misc'];
public static STATUS: SongStatus[] = ['draft', 'set', 'final']; public static STATUS: SongStatus[] = ['draft', 'set', 'final'];
public static LEGAL_OWNER: SongLegalOwner[] = ['CCLI', 'other']; public static LEGAL_OWNER: SongLegalOwner[] = ['CCLI', 'other'];
public static LEGAL_TYPE: SongLegalType[] = ['open', 'allowed']; 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 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): Observable<Song | null> => this.songDataService.read$(songId);
public read = (songId: string): Promise<Song | null> => firstValueFrom(this.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 {TransposeService} from './transpose.service';
import {TransposeMode} from './transpose-mode'; import {TransposeMode} from './transpose-mode';
import {SectionType} from './section-type'; import {SectionType} from './section-type';
@@ -11,9 +11,9 @@ import {Line} from './line';
providedIn: 'root', providedIn: 'root',
}) })
export class TextRenderingService { 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[] { public parse(text: string, transpose: TransposeMode | null, withComments = true): Section[] {
if (!text) { if (!text) {

View File

@@ -34,18 +34,18 @@ describe('TransposeService', () => {
const distance = service.getDistance('C', 'Db'); const distance = service.getDistance('C', 'Db');
const map = service.getMap('C', 'Db', distance); const map = service.getMap('C', 'Db', distance);
expect(distance).toBe(1); void expect(distance).toBe(1);
expect(map?.['C']).toBe('Db'); void expect(map?.['C']).toBe('Db');
expect(map?.['G']).toBe('Ab'); void expect(map?.['G']).toBe('Ab');
}); });
it('should keep german B/H notation consistent', () => { it('should keep german B/H notation consistent', () => {
const distance = service.getDistance('H', 'C'); const distance = service.getDistance('H', 'C');
const map = service.getMap('H', 'C', distance); const map = service.getMap('H', 'C', distance);
expect(distance).toBe(1); void expect(distance).toBe(1);
expect(map?.['H']).toBe('C'); void expect(map?.['H']).toBe('C');
expect(map?.['B']).toBe('C#'); void expect(map?.['B']).toBe('C#');
}); });
it('should render unknown chords as X', () => { it('should render unknown chords as X', () => {
@@ -57,7 +57,7 @@ describe('TransposeService', () => {
const rendered = service.renderChords(line); const rendered = service.renderChords(line);
expect(rendered.text).toBe('Xsus4'); void expect(rendered.text).toBe('Xsus4');
}); });
it('should render unknown slash chords as X', () => { it('should render unknown slash chords as X', () => {
@@ -69,7 +69,7 @@ describe('TransposeService', () => {
const rendered = service.renderChords(line); 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', () => { it('should transpose lines with long chord positions without truncating', () => {
@@ -81,7 +81,7 @@ describe('TransposeService', () => {
const rendered = service.renderChords(line); const rendered = service.renderChords(line);
expect(rendered.text.length).toBe(121); void expect(rendered.text.length).toBe(121);
expect(rendered.text.endsWith('C')).toBeTrue(); 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 {Upload} from './upload';
import {FileDataService} from './file-data.service'; import {FileDataService} from './file-data.service';
import {ref, Storage, uploadBytesResumable} from '@angular/fire/storage'; import {ref, Storage, uploadBytesResumable} from '@angular/fire/storage';
@@ -9,12 +9,8 @@ import {FileServer} from './fileServer';
providedIn: 'root', providedIn: 'root',
}) })
export class UploadService extends FileBase { export class UploadService extends FileBase {
public constructor( private fileDataService = inject(FileDataService);
private fileDataService: FileDataService, private storage = inject(Storage);
private storage: Storage
) {
super();
}
public pushUpload(songId: string, upload: Upload): void { public pushUpload(songId: string, upload: Upload): void {
const directory = this.directory(songId); 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 {ActivatedRoute, Router} from '@angular/router';
import {ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup} from '@angular/forms'; import {ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup} from '@angular/forms';
import {SongService} from '../../services/song.service'; 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], imports: [ReactiveFormsModule, MatFormField, MatLabel, MatInput, MatSelect, MatOption, LegalTypePipe, KeyPipe, SongTypePipe],
}) })
export class FilterComponent { export class FilterComponent {
private router = inject(Router);
public filterFormGroup: UntypedFormGroup; public filterFormGroup: UntypedFormGroup;
@Input() public route = '/'; @Input() public route = '/';
@Input() public songs: Song[] = []; @Input() public songs: Song[] = [];
@@ -28,11 +30,10 @@ export class FilterComponent {
public legalType = SongService.LEGAL_TYPE; public legalType = SongService.LEGAL_TYPE;
public keys = KEYS; public keys = KEYS;
public constructor( public constructor() {
private router: Router, const activatedRoute = inject(ActivatedRoute);
activatedRoute: ActivatedRoute, const fb = inject(UntypedFormBuilder);
fb: UntypedFormBuilder
) {
this.filterFormGroup = fb.group({ this.filterFormGroup = fb.group({
q: '', q: '',
type: '', 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 {SongService} from '../services/song.service';
import {Song} from '../services/song'; import {Song} from '../services/song';
import {map} from 'rxjs/operators'; import {map} from 'rxjs/operators';
@@ -25,6 +25,10 @@ import {FaIconComponent} from '@fortawesome/angular-fontawesome';
imports: [ListHeaderComponent, FilterComponent, CardComponent, RouterLink, RoleDirective, FaIconComponent, AsyncPipe], imports: [ListHeaderComponent, FilterComponent, CardComponent, RouterLink, RoleDirective, FaIconComponent, AsyncPipe],
}) })
export class SongListComponent implements OnInit, OnDestroy { export class SongListComponent implements OnInit, OnDestroy {
private songService = inject(SongService);
private activatedRoute = inject(ActivatedRoute);
private scrollService = inject(ScrollService);
public anyFilterActive = false; public anyFilterActive = false;
public songs$: Observable<Song[]> = combineLatest([ public songs$: Observable<Song[]> = combineLatest([
this.activatedRoute.queryParams.pipe(map(_ => _ as FilterValues)), this.activatedRoute.queryParams.pipe(map(_ => _ as FilterValues)),
@@ -39,12 +43,6 @@ export class SongListComponent implements OnInit, OnDestroy {
public faDraft = faPencilRuler; public faDraft = faPencilRuler;
public faFinal = faCheck; public faFinal = faCheck;
public constructor(
private songService: SongService,
private activatedRoute: ActivatedRoute,
private scrollService: ScrollService
) {}
public ngOnInit(): void { public ngOnInit(): void {
setTimeout(() => this.scrollService.restoreScrollPositionFor('songlist'), 100); setTimeout(() => this.scrollService.restoreScrollPositionFor('songlist'), 100);
setTimeout(() => this.scrollService.restoreScrollPositionFor('songlist'), 300); 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 {Upload} from '../../../services/upload';
import {UploadService} from '../../../services/upload.service'; import {UploadService} from '../../../services/upload.service';
import {ActivatedRoute} from '@angular/router'; import {ActivatedRoute} from '@angular/router';
@@ -19,16 +19,16 @@ import {FileComponent} from './file/file.component';
imports: [CardComponent, NgStyle, MatIconButton, MatIcon, FileComponent, AsyncPipe], imports: [CardComponent, NgStyle, MatIconButton, MatIcon, FileComponent, AsyncPipe],
}) })
export class EditFileComponent { export class EditFileComponent {
private activatedRoute = inject(ActivatedRoute);
private uploadService = inject(UploadService);
private fileService = inject(FileDataService);
public selectedFiles: FileList | null = null; public selectedFiles: FileList | null = null;
public currentUpload: Upload | null = null; public currentUpload: Upload | null = null;
public songId: string | null = null; public songId: string | null = null;
public files$: Observable<File[]>; public files$: Observable<File[]>;
public constructor( public constructor() {
private activatedRoute: ActivatedRoute,
private uploadService: UploadService,
private fileService: FileDataService
) {
this.activatedRoute.params this.activatedRoute.params
.pipe( .pipe(
map(param => param as {songId: string}), 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 {Observable} from 'rxjs';
import {File} from '../../../../services/file'; import {File} from '../../../../services/file';
import {faTrashAlt} from '@fortawesome/free-solid-svg-icons'; import {faTrashAlt} from '@fortawesome/free-solid-svg-icons';
@@ -14,6 +14,8 @@ import {AsyncPipe} from '@angular/common';
imports: [MatIconButton, FaIconComponent, AsyncPipe], imports: [MatIconButton, FaIconComponent, AsyncPipe],
}) })
export class FileComponent { export class FileComponent {
private fileService = inject(FileService);
public url$: Observable<string> | null = null; public url$: Observable<string> | null = null;
public name = ''; public name = '';
public faTrash = faTrashAlt; public faTrash = faTrashAlt;
@@ -21,8 +23,6 @@ export class FileComponent {
private fileId: string | null = null; private fileId: string | null = null;
private path: string | null = null; private path: string | null = null;
public constructor(private fileService: FileService) {}
@Input() @Input()
public set file(file: File) { public set file(file: File) {
this.url$ = this.fileService.getDownloadUrl(file.path + '/' + file.name); 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 {Song} from '../../../services/song';
import {ReactiveFormsModule, UntypedFormGroup} from '@angular/forms'; import {ReactiveFormsModule, UntypedFormGroup} from '@angular/forms';
import {ActivatedRoute, Router, RouterStateSnapshot} from '@angular/router'; import {ActivatedRoute, Router, RouterStateSnapshot} from '@angular/router';
@@ -59,6 +59,12 @@ import {StatusPipe} from '../../../../../widget-modules/pipes/status-translater/
], ],
}) })
export class EditSongComponent implements OnInit { 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 song: Song | null = null;
public form: UntypedFormGroup = new UntypedFormGroup({}); public form: UntypedFormGroup = new UntypedFormGroup({});
public keys = KEYS; public keys = KEYS;
@@ -73,14 +79,6 @@ export class EditSongComponent implements OnInit {
public faLink = faExternalLinkAlt; public faLink = faExternalLinkAlt;
public songtextFocus = false; public songtextFocus = false;
public constructor(
private activatedRoute: ActivatedRoute,
private songService: SongService,
private editService: EditService,
private router: Router,
public dialog: MatDialog
) {}
public ngOnInit(): void { public ngOnInit(): void {
this.activatedRoute.params this.activatedRoute.params
.pipe( .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 {first, map, switchMap} from 'rxjs/operators';
import {ActivatedRoute} from '@angular/router'; import {ActivatedRoute} from '@angular/router';
import {SongService} from '../../../services/song.service'; import {SongService} from '../../../services/song.service';
@@ -13,12 +13,10 @@ import {CardComponent} from '../../../../../widget-modules/components/card/card.
imports: [CardComponent, DatePipe], imports: [CardComponent, DatePipe],
}) })
export class HistoryComponent implements OnInit { export class HistoryComponent implements OnInit {
public song: Song | null = null; private activatedRoute = inject(ActivatedRoute);
private songService = inject(SongService);
public constructor( public song: Song | null = null;
private activatedRoute: ActivatedRoute,
private songService: SongService
) {}
public ngOnInit(): void { public ngOnInit(): void {
this.activatedRoute.params 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 {File} from '../../services/file';
import {getDownloadURL, ref, Storage} from '@angular/fire/storage'; import {getDownloadURL, ref, Storage} from '@angular/fire/storage';
import {from, Observable} from 'rxjs'; import {from, Observable} from 'rxjs';
@@ -11,11 +11,11 @@ import {AsyncPipe} from '@angular/common';
imports: [AsyncPipe], imports: [AsyncPipe],
}) })
export class FileComponent { export class FileComponent {
private storage = inject(Storage);
public url$: Observable<string> | null = null; public url$: Observable<string> | null = null;
public name = ''; public name = '';
public constructor(private storage: Storage) {}
@Input() @Input()
public set file(file: File) { public set file(file: File) {
this.url$ = from(getDownloadURL(ref(this.storage, file.path + '/' + file.name))); 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 {faSave} from '@fortawesome/free-solid-svg-icons';
import {ReactiveFormsModule, UntypedFormControl, UntypedFormGroup, Validators} from '@angular/forms'; import {ReactiveFormsModule, UntypedFormControl, UntypedFormGroup, Validators} from '@angular/forms';
import {SongService} from '../../services/song.service'; 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], imports: [CardComponent, ReactiveFormsModule, MatFormField, MatLabel, MatInput, ButtonRowComponent, ButtonComponent],
}) })
export class NewComponent implements OnInit, OnDestroy { export class NewComponent implements OnInit, OnDestroy {
private songService = inject(SongService);
private router = inject(Router);
public faSave = faSave; public faSave = faSave;
public form: UntypedFormGroup = new UntypedFormGroup({ public form: UntypedFormGroup = new UntypedFormGroup({
number: new UntypedFormControl(null, Validators.required), number: new UntypedFormControl(null, Validators.required),
@@ -26,11 +29,6 @@ export class NewComponent implements OnInit, OnDestroy {
}); });
private subs: Subscription[] = []; private subs: Subscription[] = [];
public constructor(
private songService: SongService,
private router: Router
) {}
public ngOnInit(): void { public ngOnInit(): void {
this.form.reset(); 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 {ActivatedRoute, Router, RouterLink} from '@angular/router';
import {SongService} from '../services/song.service'; import {SongService} from '../services/song.service';
import {distinctUntilChanged, map, switchMap} from 'rxjs/operators'; 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 { 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 song$: Observable<Song | null> | null = null;
public files$: Observable<File[] | null> | null = null; public files$: Observable<File[] | null> | null = null;
public user$: Observable<User | null> | null = null; public user$: Observable<User | null> | null = null;
@@ -60,15 +68,9 @@ export class SongComponent implements OnInit {
public faFileCirclePlus = faFileCirclePlus; public faFileCirclePlus = faFileCirclePlus;
public privateShows$ = this.showService.list$().pipe(map(show => show.filter(_ => !_.published).sort((a, b) => b.date.toMillis() - a.date.toMillis()))); public privateShows$ = this.showService.list$().pipe(map(show => show.filter(_ => !_.published).sort((a, b) => b.date.toMillis() - a.date.toMillis())));
public constructor( public constructor() {
private activatedRoute: ActivatedRoute, const userService = this.userService;
private songService: SongService,
private fileService: FileDataService,
private userService: UserService,
private router: Router,
private showService: ShowService,
private showSongService: ShowSongService
) {
this.user$ = userService.user$; this.user$ = userService.user$;
} }

View File

@@ -1,4 +1,4 @@
import {Component, OnInit} from '@angular/core'; import {Component, OnInit, inject} from '@angular/core';
import {UserService} from '../../../services/user/user.service'; import {UserService} from '../../../services/user/user.service';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
import {User} from '../../../services/user/user'; import {User} from '../../../services/user/user';
@@ -40,11 +40,11 @@ import {UsersComponent} from './users/users.component';
], ],
}) })
export class InfoComponent implements OnInit { export class InfoComponent implements OnInit {
private userService = inject(UserService);
public user$: Observable<User | null> | null = null; public user$: Observable<User | null> | null = null;
public faSignOut = faSignOutAlt; public faSignOut = faSignOutAlt;
public constructor(private userService: UserService) {}
public ngOnInit(): void { public ngOnInit(): void {
this.user$ = this.userService.user$; this.user$ = this.userService.user$;
} }

View File

@@ -1,4 +1,4 @@
import {Component, Input} from '@angular/core'; import {Component, Input, inject} from '@angular/core';
import {User} from '../../../../../services/user/user'; import {User} from '../../../../../services/user/user';
import {UserService} from '../../../../../services/user/user.service'; import {UserService} from '../../../../../services/user/user.service';
import {ROLE_TYPES} from '../../../../../services/user/roles'; import {ROLE_TYPES} from '../../../../../services/user/roles';
@@ -20,6 +20,8 @@ import {RolePipe} from '../../role.pipe';
imports: [MatFormField, MatLabel, MatInput, ReactiveFormsModule, FormsModule, MatSelect, MatOption, MatIconButton, FaIconComponent, RolePipe], imports: [MatFormField, MatLabel, MatInput, ReactiveFormsModule, FormsModule, MatSelect, MatOption, MatIconButton, FaIconComponent, RolePipe],
}) })
export class UserComponent { export class UserComponent {
private userService = inject(UserService);
public id = ''; public id = '';
public name = ''; public name = '';
public roles: string[] = []; public roles: string[] = [];
@@ -27,8 +29,6 @@ export class UserComponent {
public edit = false; public edit = false;
public faClose = faTimes; public faClose = faTimes;
public constructor(private userService: UserService) {}
@Input() @Input()
public set user(value: User) { public set user(value: User) {
this.id = value.id; this.id = value.id;

View File

@@ -1,4 +1,4 @@
import {Component} from '@angular/core'; import {Component, inject} from '@angular/core';
import {UserService} from '../../../../services/user/user.service'; import {UserService} from '../../../../services/user/user.service';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
import {User} from '../../../../services/user/user'; import {User} from '../../../../services/user/user';
@@ -14,9 +14,13 @@ import {SortByPipe} from '../../../../widget-modules/pipes/sort-by/sort-by.pipe'
imports: [CardComponent, UserComponent, AsyncPipe, SortByPipe], imports: [CardComponent, UserComponent, AsyncPipe, SortByPipe],
}) })
export class UsersComponent { export class UsersComponent {
private userService = inject(UserService);
public users$: Observable<User[]>; public users$: Observable<User[]>;
public constructor(private userService: UserService) { public constructor() {
const userService = this.userService;
this.users$ = userService.list$(); this.users$ = userService.list$();
} }
} }

View File

@@ -1,4 +1,4 @@
import {Component, OnInit} from '@angular/core'; import {Component, OnInit, inject} from '@angular/core';
import {ReactiveFormsModule, UntypedFormControl, UntypedFormGroup, Validators} from '@angular/forms'; import {ReactiveFormsModule, UntypedFormControl, UntypedFormGroup, Validators} from '@angular/forms';
import {Router, RouterLink} from '@angular/router'; import {Router, RouterLink} from '@angular/router';
import {UserService} from '../../../services/user/user.service'; import {UserService} from '../../../services/user/user.service';
@@ -17,6 +17,9 @@ import {AuthMessagePipe} from './auth-message.pipe';
imports: [LogoComponent, ReactiveFormsModule, MatFormField, MatLabel, MatInput, MatButton, RouterLink, AuthMessagePipe], imports: [LogoComponent, ReactiveFormsModule, MatFormField, MatLabel, MatInput, MatButton, RouterLink, AuthMessagePipe],
}) })
export class LoginComponent implements OnInit { export class LoginComponent implements OnInit {
private userService = inject(UserService);
private router = inject(Router);
public form: UntypedFormGroup = new UntypedFormGroup({ public form: UntypedFormGroup = new UntypedFormGroup({
user: new UntypedFormControl(null, [Validators.required, Validators.email]), user: new UntypedFormControl(null, [Validators.required, Validators.email]),
pass: new UntypedFormControl(null, [Validators.required]), pass: new UntypedFormControl(null, [Validators.required]),
@@ -25,13 +28,8 @@ export class LoginComponent implements OnInit {
public faSignIn = faSignInAlt; public faSignIn = faSignInAlt;
public faNewUser = faUserPlus; public faNewUser = faUserPlus;
public constructor(
private userService: UserService,
private router: Router
) {}
public ngOnInit(): void { public ngOnInit(): void {
this.form.reset; this.form.reset();
} }
public async onLogin(): Promise<void> { public async onLogin(): Promise<void> {

View File

@@ -1,4 +1,4 @@
import {AfterViewInit, Component} from '@angular/core'; import {AfterViewInit, Component, inject} from '@angular/core';
import {Router} from '@angular/router'; import {Router} from '@angular/router';
import {UserService} from '../../../services/user/user.service'; import {UserService} from '../../../services/user/user.service';
@@ -8,13 +8,13 @@ import {UserService} from '../../../services/user/user.service';
styleUrls: ['./logout.component.less'], styleUrls: ['./logout.component.less'],
}) })
export class LogoutComponent implements AfterViewInit { export class LogoutComponent implements AfterViewInit {
public constructor( private userService = inject(UserService);
private userService: UserService, private router = inject(Router);
private router: Router
) {}
public async ngAfterViewInit(): Promise<void> { public ngAfterViewInit(): void {
void (async () => {
await this.userService.logout(); await this.userService.logout();
await this.router.navigateByUrl('/'); await this.router.navigateByUrl('/');
})();
} }
} }

View File

@@ -1,4 +1,4 @@
import {Component, OnInit} from '@angular/core'; import {Component, OnInit, inject} from '@angular/core';
import {ReactiveFormsModule, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators} from '@angular/forms'; import {ReactiveFormsModule, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators} from '@angular/forms';
import {UserService} from '../../../services/user/user.service'; import {UserService} from '../../../services/user/user.service';
import {faUserPlus} from '@fortawesome/free-solid-svg-icons'; import {faUserPlus} from '@fortawesome/free-solid-svg-icons';
@@ -15,6 +15,9 @@ import {ButtonComponent} from '../../../widget-modules/components/button/button.
imports: [CardComponent, ReactiveFormsModule, MatFormField, MatLabel, MatInput, ButtonRowComponent, ButtonComponent], imports: [CardComponent, ReactiveFormsModule, MatFormField, MatLabel, MatInput, ButtonRowComponent, ButtonComponent],
}) })
export class NewComponent implements OnInit { export class NewComponent implements OnInit {
private fb = inject(UntypedFormBuilder);
private userService = inject(UserService);
public form: UntypedFormGroup = this.fb.group({ public form: UntypedFormGroup = this.fb.group({
email: new UntypedFormControl(null, [Validators.required, Validators.email]), email: new UntypedFormControl(null, [Validators.required, Validators.email]),
name: new UntypedFormControl(null, [Validators.required]), name: new UntypedFormControl(null, [Validators.required]),
@@ -22,11 +25,6 @@ export class NewComponent implements OnInit {
}); });
public faNewUser = faUserPlus; public faNewUser = faUserPlus;
public constructor(
private fb: UntypedFormBuilder,
private userService: UserService
) {}
public ngOnInit(): void { public ngOnInit(): void {
this.form.reset(); this.form.reset();
} }

View File

@@ -1,4 +1,4 @@
import {Component, OnInit} from '@angular/core'; import {Component, OnInit, inject} from '@angular/core';
import {ReactiveFormsModule, UntypedFormControl, UntypedFormGroup, Validators} from '@angular/forms'; import {ReactiveFormsModule, UntypedFormControl, UntypedFormGroup, Validators} from '@angular/forms';
import {Router} from '@angular/router'; import {Router} from '@angular/router';
import {UserService} from '../../../services/user/user.service'; import {UserService} from '../../../services/user/user.service';
@@ -18,6 +18,9 @@ import {AuthMessagePipe} from '../login/auth-message.pipe';
imports: [CardComponent, ReactiveFormsModule, MatFormField, MatLabel, MatInput, ButtonRowComponent, ButtonComponent, AuthMessagePipe], imports: [CardComponent, ReactiveFormsModule, MatFormField, MatLabel, MatInput, ButtonRowComponent, ButtonComponent, AuthMessagePipe],
}) })
export class PasswordComponent implements OnInit { export class PasswordComponent implements OnInit {
public userService = inject(UserService);
private router = inject(Router);
public form: UntypedFormGroup = new UntypedFormGroup({ public form: UntypedFormGroup = new UntypedFormGroup({
user: new UntypedFormControl(null, [Validators.required, Validators.email]), user: new UntypedFormControl(null, [Validators.required, Validators.email]),
}); });
@@ -25,11 +28,6 @@ export class PasswordComponent implements OnInit {
public errorMessage = ''; public errorMessage = '';
public faNewPassword = faWindowRestore; public faNewPassword = faWindowRestore;
public constructor(
public userService: UserService,
private router: Router
) {}
public ngOnInit(): void { public ngOnInit(): void {
this.form.reset(); this.form.reset();
} }

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core'; import {Injectable, inject} from '@angular/core';
import {DbService} from './db.service'; import {DbService} from './db.service';
import {firstValueFrom, Observable} from 'rxjs'; import {firstValueFrom, Observable} from 'rxjs';
import {Config} from './config'; import {Config} from './config';
@@ -8,6 +8,8 @@ import {shareReplay} from 'rxjs/operators';
providedIn: 'root', providedIn: 'root',
}) })
export class ConfigService { export class ConfigService {
private db = inject(DbService);
private readonly config$ = this.db.doc$<Config>('global/config').pipe( private readonly config$ = this.db.doc$<Config>('global/config').pipe(
shareReplay({ shareReplay({
bufferSize: 1, bufferSize: 1,
@@ -15,8 +17,6 @@ export class ConfigService {
}) })
); );
public constructor(private db: DbService) {}
public get$ = (): Observable<Config | null> => this.config$; public get$ = (): Observable<Config | null> => this.config$;
public get = (): Promise<Config | null> => firstValueFrom(this.get$()); public get = (): Promise<Config | null> => firstValueFrom(this.get$());
} }

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core'; import {Injectable, inject} from '@angular/core';
import { import {
addDoc, addDoc,
collection, collection,
@@ -76,7 +76,7 @@ export class DbDocument<T> {
providedIn: 'root', providedIn: 'root',
}) })
export class DbService { export class DbService {
public constructor(private fs: Firestore) {} private fs = inject(Firestore);
public col<T>(ref: CollectionPredicate<T>): DbCollection<T> { public col<T>(ref: CollectionPredicate<T>): DbCollection<T> {
return typeof ref === 'string' ? new DbCollection<T>(this.fs, ref) : ref; return typeof ref === 'string' ? new DbCollection<T>(this.fs, ref) : ref;

View File

@@ -6,12 +6,9 @@ export const openFullscreen = () => {
} }
try { try {
const promise = elem.requestFullscreen(); void elem.requestFullscreen().catch(() => {
if (promise && typeof promise.catch === 'function') {
void promise.catch(() => {
// Browser may reject when no user gesture is present. Keep app usable. // Browser may reject when no user gesture is present. Keep app usable.
}); });
}
} catch { } catch {
// Some browsers may throw synchronously if fullscreen is not allowed. // Some browsers may throw synchronously if fullscreen is not allowed.
} }
@@ -19,11 +16,8 @@ export const openFullscreen = () => {
export const closeFullscreen = () => { export const closeFullscreen = () => {
if (document.exitFullscreen) { if (document.exitFullscreen) {
const promise = document.exitFullscreen(); void document.exitFullscreen().catch(() => {
if (promise && typeof promise.catch === 'function') {
void promise.catch(() => {
// Ignore; leaving fullscreen is a best-effort action. // Ignore; leaving fullscreen is a best-effort action.
}); });
} }
}
}; };

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core'; import {Injectable, inject} from '@angular/core';
import {DbService} from './db.service'; import {DbService} from './db.service';
import {GlobalSettings} from './global-settings'; import {GlobalSettings} from './global-settings';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
@@ -8,6 +8,8 @@ import {shareReplay} from 'rxjs/operators';
providedIn: 'root', providedIn: 'root',
}) })
export class GlobalSettingsService { export class GlobalSettingsService {
private db = inject(DbService);
private readonly settings$ = this.db.doc$<GlobalSettings>('global/static').pipe( private readonly settings$ = this.db.doc$<GlobalSettings>('global/static').pipe(
shareReplay({ shareReplay({
bufferSize: 1, bufferSize: 1,
@@ -15,8 +17,6 @@ export class GlobalSettingsService {
}) })
); );
public constructor(private db: DbService) {}
public get get$(): Observable<GlobalSettings | null> { public get get$(): Observable<GlobalSettings | null> {
return this.settings$; return this.settings$;
} }

View File

@@ -1,18 +1,16 @@
import {Directive, ElementRef, Input, OnInit, TemplateRef, ViewContainerRef} from '@angular/core'; import {Directive, ElementRef, Input, OnInit, TemplateRef, ViewContainerRef, inject} from '@angular/core';
import {UserService} from './user.service'; import {UserService} from './user.service';
@Directive({selector: '[appOwner]'}) @Directive({selector: '[appOwner]'})
export class OwnerDirective implements OnInit { export class OwnerDirective implements OnInit {
private element = inject(ElementRef);
private templateRef = inject<TemplateRef<unknown>>(TemplateRef);
private viewContainer = inject(ViewContainerRef);
private userService = inject(UserService);
private currentUserId: string | null = null; private currentUserId: string | null = null;
private iAppOwner: string | null = null; private iAppOwner: string | null = null;
public constructor(
private element: ElementRef,
private templateRef: TemplateRef<unknown>,
private viewContainer: ViewContainerRef,
private userService: UserService
) {}
@Input() @Input()
public set appOwner(value: string) { public set appOwner(value: string) {
this.iAppOwner = value; this.iAppOwner = value;

View File

@@ -1,4 +1,4 @@
import {ChangeDetectorRef, Directive, ElementRef, Input, OnInit, TemplateRef, ViewContainerRef} from '@angular/core'; import {ChangeDetectorRef, Directive, ElementRef, Input, OnInit, TemplateRef, ViewContainerRef, inject} from '@angular/core';
import {roles} from './roles'; import {roles} from './roles';
import {UserService} from './user.service'; import {UserService} from './user.service';
import {User} from './user'; import {User} from './user';
@@ -6,19 +6,17 @@ import {combineLatest} from 'rxjs';
@Directive({selector: '[appRole]'}) @Directive({selector: '[appRole]'})
export class RoleDirective implements OnInit { export class RoleDirective implements OnInit {
private element = inject(ElementRef);
private templateRef = inject<TemplateRef<unknown>>(TemplateRef);
private viewContainer = inject(ViewContainerRef);
private userService = inject(UserService);
private changeDetection = inject(ChangeDetectorRef);
@Input() public appRole: roles[] = []; @Input() public appRole: roles[] = [];
private currentUser: User | null = null; private currentUser: User | null = null;
private loggedIn = false; private loggedIn = false;
private currentViewState = false; private currentViewState = false;
public constructor(
private element: ElementRef,
private templateRef: TemplateRef<unknown>,
private viewContainer: ViewContainerRef,
private userService: UserService,
private changeDetection: ChangeDetectorRef
) {}
public ngOnInit(): void { public ngOnInit(): void {
combineLatest([this.userService.user$, this.userService.loggedIn$()]).subscribe(_ => { combineLatest([this.userService.user$, this.userService.loggedIn$()]).subscribe(_ => {
this.currentUser = _[0]; this.currentUser = _[0];

View File

@@ -1,4 +1,4 @@
import {Component, Input} from '@angular/core'; import {Component, Input, inject} from '@angular/core';
import {UserService} from '../user.service'; import {UserService} from '../user.service';
import {map} from 'rxjs/operators'; import {map} from 'rxjs/operators';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
@@ -11,9 +11,9 @@ import {AsyncPipe} from '@angular/common';
imports: [AsyncPipe], imports: [AsyncPipe],
}) })
export class UserNameComponent { export class UserNameComponent {
public name$: Observable<string | null> | null = null; private userService = inject(UserService);
public constructor(private userService: UserService) {} public name$: Observable<string | null> | null = null;
@Input() @Input()
public set userId(id: string) { public set userId(id: string) {

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core'; import {Injectable, inject} from '@angular/core';
import {Auth, authState, createUserWithEmailAndPassword, sendPasswordResetEmail, signInWithEmailAndPassword, signOut} from '@angular/fire/auth'; import {Auth, authState, createUserWithEmailAndPassword, sendPasswordResetEmail, signInWithEmailAndPassword, signOut} from '@angular/fire/auth';
import {BehaviorSubject, firstValueFrom, Observable} from 'rxjs'; import {BehaviorSubject, firstValueFrom, Observable} from 'rxjs';
import {filter, map, shareReplay, switchMap, take, tap} from 'rxjs/operators'; import {filter, map, shareReplay, switchMap, take, tap} from 'rxjs/operators';
@@ -20,18 +20,18 @@ export interface SongUsageMigrationResult {
providedIn: 'root', providedIn: 'root',
}) })
export class UserService { export class UserService {
private auth = inject(Auth);
private db = inject(DbService);
private router = inject(Router);
private showDataService = inject(ShowDataService);
private showSongDataService = inject(ShowSongDataService);
public users$ = this.db.col$<User>('users').pipe(shareReplay({bufferSize: 1, refCount: true})); public users$ = this.db.col$<User>('users').pipe(shareReplay({bufferSize: 1, refCount: true}));
private iUserId$ = new BehaviorSubject<string | null>(null); private iUserId$ = new BehaviorSubject<string | null>(null);
private iUser$ = new BehaviorSubject<User | null>(null); private iUser$ = new BehaviorSubject<User | null>(null);
private userByIdCache = new Map<string, Observable<User | null>>(); private userByIdCache = new Map<string, Observable<User | null>>();
public constructor( public constructor() {
private auth: Auth,
private db: DbService,
private router: Router,
private showDataService: ShowDataService,
private showSongDataService: ShowSongDataService
) {
authState(this.auth) authState(this.auth)
.pipe( .pipe(
filter(auth => !!auth), filter(auth => !!auth),

View File

@@ -1,4 +1,4 @@
import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; import {ChangeDetectionStrategy, Component, Input, inject} from '@angular/core';
import {ReactiveFormsModule, UntypedFormControl} from '@angular/forms'; import {ReactiveFormsModule, UntypedFormControl} from '@angular/forms';
import {filterSong} from '../../../services/filter.helper'; import {filterSong} from '../../../services/filter.helper';
import {MatFormField, MatLabel, MatOption, MatSelect, MatSelectChange} from '@angular/material/select'; import {MatFormField, MatLabel, MatOption, MatSelect, MatSelectChange} from '@angular/material/select';
@@ -18,17 +18,15 @@ import {NgxMatSelectSearchModule} from 'ngx-mat-select-search';
imports: [MatFormField, MatSelect, MatOption, MatLabel, NgxMatSelectSearchModule, ReactiveFormsModule], imports: [MatFormField, MatSelect, MatOption, MatLabel, NgxMatSelectSearchModule, ReactiveFormsModule],
}) })
export class AddSongComponent { export class AddSongComponent {
private showSongService = inject(ShowSongService);
private showService = inject(ShowService);
@Input() public songs: Song[] | null = null; @Input() public songs: Song[] | null = null;
@Input() public showSongs: ShowSong[] | null = null; @Input() public showSongs: ShowSong[] | null = null;
@Input() public show: Show | null = null; @Input() public show: Show | null = null;
@Input() public addedLive = false; @Input() public addedLive = false;
public filteredSongsControl = new UntypedFormControl(); public filteredSongsControl = new UntypedFormControl();
public constructor(
private showSongService: ShowSongService,
private showService: ShowService
) {}
public filteredSongs(): Song[] { public filteredSongs(): Song[] {
if (!this.songs) return []; if (!this.songs) return [];
const songs = this.songs const songs = this.songs

View File

@@ -1,4 +1,4 @@
import {Component} from '@angular/core'; import {Component, inject} from '@angular/core';
import {ActivatedRoute, Params, Router} from '@angular/router'; import {ActivatedRoute, Params, Router} from '@angular/router';
import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {FormsModule, ReactiveFormsModule} from '@angular/forms';
@@ -9,12 +9,13 @@ import {FormsModule, ReactiveFormsModule} from '@angular/forms';
imports: [ReactiveFormsModule, FormsModule], imports: [ReactiveFormsModule, FormsModule],
}) })
export class FilterComponent { export class FilterComponent {
private router = inject(Router);
public value = ''; public value = '';
public constructor( public constructor() {
private router: Router, const activatedRoute = inject(ActivatedRoute);
activatedRoute: ActivatedRoute
) {
activatedRoute.queryParams.subscribe((params: Params) => { activatedRoute.queryParams.subscribe((params: Params) => {
const typedParams = params as {q: string}; const typedParams = params as {q: string};
if (typedParams.q) this.value = typedParams.q; if (typedParams.q) this.value = typedParams.q;

View File

@@ -1,4 +1,4 @@
import {ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnInit, Output, QueryList, ViewChildren} from '@angular/core'; import {ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnInit, Output, QueryList, ViewChildren, inject} from '@angular/core';
import {TextRenderingService} from '../../../modules/songs/services/text-rendering.service'; import {TextRenderingService} from '../../../modules/songs/services/text-rendering.service';
import {faGripLines} from '@fortawesome/free-solid-svg-icons'; import {faGripLines} from '@fortawesome/free-solid-svg-icons';
import {songSwitch} from './animation'; import {songSwitch} from './animation';
@@ -21,6 +21,10 @@ export type ChordMode = 'show' | 'hide' | 'onlyFirst';
imports: [MatIconButton, FaIconComponent], imports: [MatIconButton, FaIconComponent],
}) })
export class SongTextComponent implements OnInit { export class SongTextComponent implements OnInit {
private textRenderingService = inject(TextRenderingService);
private elRef = inject<ElementRef<HTMLElement>>(ElementRef);
private cRef = inject(ChangeDetectorRef);
public sections: Section[] = []; public sections: Section[] = [];
@Input() public header: string | null = null; @Input() public header: string | null = null;
@Input() public index = -1; @Input() public index = -1;
@@ -35,12 +39,6 @@ export class SongTextComponent implements OnInit {
private iText = ''; private iText = '';
private iTranspose: TransposeMode | null = null; private iTranspose: TransposeMode | null = null;
public constructor(
private textRenderingService: TextRenderingService,
private elRef: ElementRef<HTMLElement>,
private cRef: ChangeDetectorRef
) {}
@Input() @Input()
public set chordMode(value: ChordMode) { public set chordMode(value: ChordMode) {
this.iChordMode = value ?? 'hide'; this.iChordMode = value ?? 'hide';

View File

@@ -1,10 +1,9 @@
import {Directive, ElementRef, Input, OnInit} from '@angular/core'; import {Directive, ElementRef, Input, OnInit, inject} from '@angular/core';
@Directive({selector: '[appAutofocus]'}) @Directive({selector: '[appAutofocus]'})
export class AutofocusDirective implements OnInit { export class AutofocusDirective implements OnInit {
private focus = true; private focus = true;
private el = inject<ElementRef<HTMLElement>>(ElementRef);
public constructor(private el: ElementRef<HTMLElement>) {}
@Input() @Input()
public set autofocus(condition: boolean) { public set autofocus(condition: boolean) {

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core'; import {Injectable, inject} from '@angular/core';
import {CanActivate, Router, UrlTree} from '@angular/router'; import {CanActivate, Router, UrlTree} from '@angular/router';
import {Auth, authState} from '@angular/fire/auth'; import {Auth, authState} from '@angular/fire/auth';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
@@ -8,10 +8,8 @@ import {map, take} from 'rxjs/operators';
providedIn: 'root', providedIn: 'root',
}) })
export class AuthGuard implements CanActivate { export class AuthGuard implements CanActivate {
public constructor( private auth = inject(Auth);
private auth: Auth, private router = inject(Router);
private router: Router
) {}
public canActivate(): Observable<boolean | UrlTree> { public canActivate(): Observable<boolean | UrlTree> {
return authState(this.auth).pipe( return authState(this.auth).pipe(

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@angular/core'; import {Injectable, inject} from '@angular/core';
import {ActivatedRouteSnapshot, Router, UrlTree} from '@angular/router'; import {ActivatedRouteSnapshot, Router, UrlTree} from '@angular/router';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
import {UserService} from '../../services/user/user.service'; import {UserService} from '../../services/user/user.service';
@@ -8,10 +8,8 @@ import {map} from 'rxjs/operators';
providedIn: 'root', providedIn: 'root',
}) })
export class RoleGuard { export class RoleGuard {
public constructor( private userService = inject(UserService);
private userService: UserService, private router = inject(Router);
private router: Router
) {}
public canActivate(next: ActivatedRouteSnapshot): Observable<boolean | UrlTree> { public canActivate(next: ActivatedRouteSnapshot): Observable<boolean | UrlTree> {
const requiredRoles = next.data.requiredRoles as string[]; const requiredRoles = next.data.requiredRoles as string[];

View File

@@ -1,5 +1,4 @@
import {Pipe, PipeTransform} from '@angular/core'; import {Pipe, PipeTransform} from '@angular/core';
import {orderBy} from 'lodash';
@Pipe({ @Pipe({
name: 'sortBy', name: 'sortBy',
@@ -19,7 +18,29 @@ export class SortByPipe implements PipeTransform {
if (value.length <= 1) { if (value.length <= 1) {
return value; return value;
} // array with only one item } // array with only one item
// eslint-disable-next-line @typescript-eslint/no-unsafe-call return [...value].sort((left, right) => {
return orderBy(value, [column], [order]); const leftValue = this.getComparableValue(left, column);
const rightValue = this.getComparableValue(right, column);
const result = leftValue < rightValue ? -1 : leftValue > rightValue ? 1 : 0;
return order === 'asc' ? result : -result;
});
}
private getComparableValue(item: unknown, column: string): string | number {
const value = (item as Record<string, unknown>)[column];
if (value instanceof Date) {
return value.getTime();
}
switch (typeof value) {
case 'number':
return value;
case 'string':
return value;
case 'boolean':
return value ? 1 : 0;
default:
return '';
}
} }
} }