validate chords

This commit is contained in:
2026-03-11 16:18:36 +01:00
parent 03fb395605
commit ae4459f5ce
12 changed files with 538 additions and 13 deletions

View File

@@ -54,10 +54,26 @@
matInput
></textarea>
</mat-form-field>
@if (chordValidationIssues.length > 0) {
<div class="song-text-validation">
<h3>Akkordschreibweise korrigieren</h3>
@for (issue of chordValidationIssues; track issue.lineNumber + '-' + issue.token) {
<div class="issue">
<strong>Zeile {{ issue.lineNumber }}:</strong>
<span>{{ issue.message }}</span>
<code>{{ issue.token }}</code>
@if (issue.suggestion) {
<span>-></span>
<code>{{ issue.suggestion }}</code>
}
</div>
}
</div>
}
@if (songtextFocus) {
<div class="song-text-help">
<h3>Vorschau</h3>
<app-song-text [text]="form.value.text" chordMode="show"></app-song-text>
<app-song-text [invalidChordIssues]="chordValidationIssues" [text]="form.value.text" chordMode="show"></app-song-text>
<h3>Hinweise zur Bearbeitung</h3>
<h4>Aufbau</h4>
Der Liedtext wird hintereinander weg geschrieben. Dabei werden Strophen,
@@ -180,7 +196,7 @@
</div>
</form>
<app-button-row>
<app-button (click)="onSave()" [icon]="faSave">Speichern</app-button>
<app-button (click)="onSave()" [disabled]="form.invalid" [icon]="faSave">Speichern</app-button>
</app-button-row>
</app-card>
}

View File

@@ -43,3 +43,26 @@ h4 {
.song-text-help {
font-size: 11px;
}
.song-text-validation {
margin: -8px 0 12px;
padding: 12px 14px;
border-radius: 6px;
background: rgba(166, 32, 32, 0.08);
border: 1px solid rgba(166, 32, 32, 0.22);
color: #7d1919;
.issue {
display: flex;
gap: 8px;
align-items: baseline;
flex-wrap: wrap;
margin-top: 6px;
}
code {
padding: 1px 4px;
border-radius: 4px;
background: rgba(125, 25, 25, 0.08);
}
}

View File

@@ -5,12 +5,15 @@ import {ActivatedRoute, Router, RouterStateSnapshot} from '@angular/router';
import {SongService} from '../../../services/song.service';
import {EditService} from '../edit.service';
import {first, map, switchMap} from 'rxjs/operators';
import {startWith} from 'rxjs';
import {KEYS} from '../../../services/key.helper';
import {COMMA, ENTER} from '@angular/cdk/keycodes';
import {MatChipGrid, MatChipInput, MatChipInputEvent, MatChipRow} from '@angular/material/chips';
import {faExternalLinkAlt, faSave, faTimesCircle} from '@fortawesome/free-solid-svg-icons';
import {MatDialog} from '@angular/material/dialog';
import {SaveDialogComponent} from './save-dialog/save-dialog.component';
import {TextRenderingService} from '../../../services/text-rendering.service';
import {ChordValidationIssue} from '../../../services/chord';
import {CardComponent} from '../../../../../widget-modules/components/card/card.component';
import {MatFormField, MatLabel, MatSuffix} from '@angular/material/form-field';
@@ -63,6 +66,7 @@ export class EditSongComponent implements OnInit {
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;
@@ -78,6 +82,7 @@ export class EditSongComponent implements OnInit {
public faSave = faSave;
public faLink = faExternalLinkAlt;
public songtextFocus = false;
public chordValidationIssues: ChordValidationIssue[] = [];
public ngOnInit(): void {
this.activatedRoute.params
@@ -92,12 +97,15 @@ export class EditSongComponent implements OnInit {
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.onFlagsChanged(this.form.controls.flags.value as string);
});
}
public async onSave(): Promise<void> {
if (!this.song) return;
if (!this.song || this.form.invalid) return;
const data = this.form.value as Partial<Song>;
await this.songService.update$(this.song.id, data);
this.form.markAsPristine();
@@ -149,8 +157,23 @@ export class EditSongComponent implements OnInit {
this.flags = flagArray.split(';').filter(_ => !!_);
}
private updateChordValidation(text: string): void {
const control = this.form.controls.text;
this.chordValidationIssues = this.textRenderingService.validateChordNotation(text ?? '');
const errors = {...(control.errors ?? {})};
if (this.chordValidationIssues.length > 0) {
errors.chordNotation = this.chordValidationIssues;
control.setErrors(errors);
return;
}
delete errors.chordNotation;
control.setErrors(Object.keys(errors).length > 0 ? errors : null);
}
private async onSaveDialogAfterClosed(save: boolean, url: string) {
if (save && this.song) {
if (save && this.song && !this.form.invalid) {
const data = this.form.value as Partial<Song>;
await this.songService.update$(this.song.id, data);
}