This commit is contained in:
2026-03-15 12:50:33 +01:00
parent dd68a6b21d
commit d907c89eb6
36 changed files with 309 additions and 286 deletions

View File

@@ -96,10 +96,11 @@ export class TextRenderingService {
return [];
}
const indices = {
const indices: Record<SectionType, number> = {
[SectionType.Bridge]: 0,
[SectionType.Chorus]: 0,
[SectionType.Verse]: 0,
[SectionType.Comment]: 0,
};
const sections: Section[] = [];

View File

@@ -1,5 +1,6 @@
import {Component, Input, inject} from '@angular/core';
import {ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup} from '@angular/forms';
import {Component, DestroyRef, Input, inject} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {FormBuilder, FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
import {SongService} from '../../services/song.service';
import {FilterValues} from './filter-values';
import {Song} from '../../services/song';
@@ -22,17 +23,24 @@ import {SongTypePipe} from '../../../../widget-modules/pipes/song-type-translate
})
export class FilterComponent {
private filterStore = inject(FilterStoreService);
private destroyRef = inject(DestroyRef);
public filterFormGroup: UntypedFormGroup;
public filterFormGroup: FormGroup<{
q: FormControl<string>;
type: FormControl<string>;
key: FormControl<string>;
legalType: FormControl<string>;
flag: FormControl<string>;
}>;
@Input() public songs: Song[] = [];
public types = SongService.TYPES;
public legalType = SongService.LEGAL_TYPE;
public keys = KEYS;
public constructor() {
const fb = inject(UntypedFormBuilder);
const fb = inject(FormBuilder);
this.filterFormGroup = fb.group({
this.filterFormGroup = fb.nonNullable.group({
q: '',
type: '',
key: '',
@@ -40,15 +48,15 @@ export class FilterComponent {
flag: '',
});
this.filterStore.songFilter$.subscribe(filterValues => {
this.filterStore.songFilter$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(filterValues => {
this.filterFormGroup.patchValue(filterValues, {emitEvent: false});
});
this.filterFormGroup.controls.q.valueChanges.subscribe(_ => this.filterValueChanged('q', (_ as string) ?? ''));
this.filterFormGroup.controls.key.valueChanges.subscribe(_ => this.filterValueChanged('key', (_ as string) ?? ''));
this.filterFormGroup.controls.type.valueChanges.subscribe(_ => this.filterValueChanged('type', (_ as string) ?? ''));
this.filterFormGroup.controls.legalType.valueChanges.subscribe(_ => this.filterValueChanged('legalType', (_ as string) ?? ''));
this.filterFormGroup.controls.flag.valueChanges.subscribe(_ => this.filterValueChanged('flag', (_ as string) ?? ''));
this.filterFormGroup.controls.q.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('q', value));
this.filterFormGroup.controls.key.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('key', value));
this.filterFormGroup.controls.type.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('type', value));
this.filterFormGroup.controls.legalType.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('legalType', value));
this.filterFormGroup.controls.flag.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('flag', value));
}
public getFlags(): string[] {

View File

@@ -1,4 +1,5 @@
import {Component, inject} from '@angular/core';
import {Component, DestroyRef, inject} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {Upload} from '../../../services/upload';
import {UploadService} from '../../../services/upload.service';
import {ActivatedRoute} from '@angular/router';
@@ -22,6 +23,7 @@ export class EditFileComponent {
private activatedRoute = inject(ActivatedRoute);
private uploadService = inject(UploadService);
private fileService = inject(FileDataService);
private destroyRef = inject(DestroyRef);
public selectedFiles: FileList | null = null;
public currentUpload: Upload | null = null;
@@ -32,7 +34,8 @@ export class EditFileComponent {
this.activatedRoute.params
.pipe(
map(param => param as {songId: string}),
map(param => param.songId)
map(param => param.songId),
takeUntilDestroyed(this.destroyRef)
)
.subscribe(songId => {
this.songId = songId;

View File

@@ -1,9 +1,10 @@
import {Component, OnInit, inject} from '@angular/core';
import {Component, DestroyRef, inject, OnInit} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {Song} from '../../../services/song';
import {ReactiveFormsModule, UntypedFormGroup} from '@angular/forms';
import {ReactiveFormsModule} from '@angular/forms';
import {ActivatedRoute, Router, RouterStateSnapshot} from '@angular/router';
import {SongService} from '../../../services/song.service';
import {EditService} from '../edit.service';
import {EditService, SongFormGroup} from '../edit.service';
import {first, map, switchMap} from 'rxjs/operators';
import {startWith} from 'rxjs';
import {KEYS} from '../../../services/key.helper';
@@ -62,15 +63,9 @@ 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);
private textRenderingService = inject(TextRenderingService);
public dialog = inject(MatDialog);
public song: Song | null = null;
public form: UntypedFormGroup = new UntypedFormGroup({});
public form = {} as SongFormGroup;
public keys = KEYS;
public types = SongService.TYPES;
public status = SongService.STATUS;
@@ -83,6 +78,12 @@ export class EditSongComponent implements OnInit {
public faLink = faExternalLinkAlt;
public songtextFocus = false;
public chordValidationIssues: ChordValidationIssue[] = [];
private activatedRoute = inject(ActivatedRoute);
private songService = inject(SongService);
private editService = inject(EditService);
private router = inject(Router);
private textRenderingService = inject(TextRenderingService);
private destroyRef = inject(DestroyRef);
public ngOnInit(): void {
this.activatedRoute.params
@@ -90,23 +91,24 @@ export class EditSongComponent implements OnInit {
map(param => param as {songId: string}),
map(param => param.songId),
switchMap(songId => this.songService.read$(songId)),
first()
first(),
takeUntilDestroyed(this.destroyRef)
)
.subscribe(song => {
this.song = song;
if (!song) return;
this.form = this.editService.createSongForm(song);
this.form.controls.flags.valueChanges.subscribe(_ => this.onFlagsChanged(_ as string));
this.form.controls.text.valueChanges.pipe(startWith(this.form.controls.text.value)).subscribe(text => {
this.updateChordValidation(text as string);
this.form.controls.flags.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.onFlagsChanged(value));
this.form.controls.text.valueChanges.pipe(startWith(this.form.controls.text.value), takeUntilDestroyed(this.destroyRef)).subscribe(text => {
this.updateChordValidation(text);
});
this.onFlagsChanged(this.form.controls.flags.value as string);
this.onFlagsChanged(this.form.controls.flags.value);
});
}
public async onSave(): Promise<void> {
if (!this.song || this.form.invalid) return;
const data = this.form.value as Partial<Song>;
const data = this.form.getRawValue() as Partial<Song>;
await this.songService.update$(this.song.id, data);
this.form.markAsPristine();
await this.router.navigateByUrl('songs/' + this.song.id);
@@ -121,7 +123,6 @@ export class EditSongComponent implements OnInit {
const input = event.input;
const value = event.value;
// Add our fruit
if ((value || '').trim()) {
const flags = [...this.flags, value.trim()];
this.form.controls.flags.setValue(flags.join(';'));
@@ -174,7 +175,7 @@ export class EditSongComponent implements OnInit {
private async onSaveDialogAfterClosed(save: boolean, url: string) {
if (save && this.song && !this.form.invalid) {
const data = this.form.value as Partial<Song>;
const data = this.form.getRawValue() as Partial<Song>;
await this.songService.update$(this.song.id, data);
}

View File

@@ -1,30 +1,48 @@
import {Injectable} from '@angular/core';
import {Song} from '../../services/song';
import {UntypedFormControl, UntypedFormGroup} from '@angular/forms';
import {FormControl, FormGroup} from '@angular/forms';
export type SongFormGroup = FormGroup<{
text: FormControl<string>;
title: FormControl<string>;
comment: FormControl<string>;
flags: FormControl<string>;
key: FormControl<string>;
tempo: FormControl<number>;
type: FormControl<Song['type']>;
status: FormControl<Song['status']>;
legalType: FormControl<Song['legalType']>;
legalOwner: FormControl<Song['legalOwner']>;
legalOwnerId: FormControl<string>;
artist: FormControl<string>;
label: FormControl<string>;
termsOfUse: FormControl<string>;
origin: FormControl<string>;
}>;
@Injectable({
providedIn: 'root',
})
export class EditService {
public createSongForm(song: Song): UntypedFormGroup {
return new UntypedFormGroup({
text: new UntypedFormControl(song.text),
title: new UntypedFormControl(song.title),
comment: new UntypedFormControl(song.comment),
flags: new UntypedFormControl(song.flags),
key: new UntypedFormControl(song.key),
tempo: new UntypedFormControl(song.tempo),
type: new UntypedFormControl(song.type),
status: new UntypedFormControl(song.status ?? 'draft'),
public createSongForm(song: Song): SongFormGroup {
return new FormGroup({
text: new FormControl(song.text, {nonNullable: true}),
title: new FormControl(song.title, {nonNullable: true}),
comment: new FormControl(song.comment, {nonNullable: true}),
flags: new FormControl(song.flags, {nonNullable: true}),
key: new FormControl(song.key, {nonNullable: true}),
tempo: new FormControl(song.tempo, {nonNullable: true}),
type: new FormControl(song.type, {nonNullable: true}),
status: new FormControl(song.status ?? 'draft', {nonNullable: true}),
legalType: new UntypedFormControl(song.legalType),
legalOwner: new UntypedFormControl(song.legalOwner),
legalOwnerId: new UntypedFormControl(song.legalOwnerId),
legalType: new FormControl(song.legalType, {nonNullable: true}),
legalOwner: new FormControl(song.legalOwner, {nonNullable: true}),
legalOwnerId: new FormControl(song.legalOwnerId, {nonNullable: true}),
artist: new UntypedFormControl(song.artist),
label: new UntypedFormControl(song.label),
termsOfUse: new UntypedFormControl(song.termsOfUse),
origin: new UntypedFormControl(song.origin),
artist: new FormControl(song.artist, {nonNullable: true}),
label: new FormControl(song.label, {nonNullable: true}),
termsOfUse: new FormControl(song.termsOfUse, {nonNullable: true}),
origin: new FormControl(song.origin, {nonNullable: true}),
});
}
}

View File

@@ -1,10 +1,11 @@
import {Component, OnDestroy, OnInit, inject} from '@angular/core';
import {Component, OnInit, inject} from '@angular/core';
import {faSave} from '@fortawesome/free-solid-svg-icons';
import {ReactiveFormsModule, UntypedFormControl, UntypedFormGroup, Validators} from '@angular/forms';
import {DestroyRef} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {SongService} from '../../services/song.service';
import {Song} from '../../services/song';
import {Router} from '@angular/router';
import {Subscription} from 'rxjs';
import {take} from 'rxjs/operators';
import {CardComponent} from '../../../../widget-modules/components/card/card.component';
import {MatFormField, MatLabel} from '@angular/material/form-field';
@@ -18,39 +19,34 @@ import {ButtonComponent} from '../../../../widget-modules/components/button/butt
styleUrls: ['./new.component.less'],
imports: [CardComponent, ReactiveFormsModule, MatFormField, MatLabel, MatInput, ButtonRowComponent, ButtonComponent],
})
export class NewComponent implements OnInit, OnDestroy {
export class NewComponent implements OnInit {
private songService = inject(SongService);
private router = inject(Router);
private destroyRef = inject(DestroyRef);
public faSave = faSave;
public form: UntypedFormGroup = new UntypedFormGroup({
number: new UntypedFormControl(null, Validators.required),
title: new UntypedFormControl(null, Validators.required),
public form = new FormGroup({
number: new FormControl<number | null>(null, Validators.required),
title: new FormControl<string>('', {nonNullable: true, validators: [Validators.required]}),
});
private subs: Subscription[] = [];
public ngOnInit(): void {
this.form.reset();
this.subs.push(
this.songService
.list$()
.pipe(take(1))
.subscribe(songs => {
const freeSongnumber = this.getFreeSongNumber(songs);
this.form.controls.number.setValue(freeSongnumber);
})
);
}
public ngOnDestroy(): void {
this.subs.forEach(_ => _.unsubscribe());
this.songService
.list$()
.pipe(take(1), takeUntilDestroyed(this.destroyRef))
.subscribe(songs => {
const freeSongnumber = this.getFreeSongNumber(songs);
this.form.controls.number.setValue(freeSongnumber);
});
}
public async onSave(): Promise<void> {
const value = this.form.value as {number: number; title: string};
const songNumber = value.number;
const title = value.title;
const {number: songNumber, title} = this.form.getRawValue();
if (songNumber == null) {
return;
}
const newSongId = await this.songService.new(songNumber, title);
await this.router.navigateByUrl('/songs/' + newSongId + '/edit');
}

View File

@@ -75,11 +75,12 @@ export class SongComponent implements OnInit {
}
public ngOnInit(): void {
this.song$ = this.activatedRoute.params.pipe(
const song$ = this.activatedRoute.params.pipe(
map(param => param as {songId: string}),
map(param => param.songId),
switchMap(songId => this.songService.read$(songId))
);
this.song$ = song$;
this.files$ = this.activatedRoute.params.pipe(
map(param => param as {songId: string}),
@@ -87,7 +88,7 @@ export class SongComponent implements OnInit {
switchMap(songId => this.fileService.read$(songId))
);
this.songCount$ = combineLatest([this.user$, this.song$]).pipe(
this.songCount$ = combineLatest([this.userService.user$, song$]).pipe(
map(([user, song]) => {
if (!song) {
return 0;
@@ -111,10 +112,9 @@ export class SongComponent implements OnInit {
await this.router.navigateByUrl('/songs');
}
public async addSongToShow(show: Show, song: Song) {
if (!show) return;
const newId = await this.showSongService.new$(show?.id, song.id, false);
await this.showService.update$(show?.id, {order: [...show.order, newId ?? '']});
public async addSongToShow(show: Show, song: Song): Promise<void> {
const newId = await this.showSongService.new$(show.id, song.id, false);
await this.showService.update$(show.id, {order: [...show.order, newId ?? '']});
await this.router.navigateByUrl('/shows/' + show.id);
}
}