validate chords #3

This commit is contained in:
2026-03-11 17:13:17 +01:00
parent 9f47f259c0
commit 0452ec55b2
11 changed files with 624 additions and 462 deletions

View File

@@ -28,7 +28,7 @@
class="line"
>
@for (segment of getDisplaySegments(line); track $index) {
<span [class.invalid-chord-token]="segment.invalid">{{ segment.text }}</span>
<span [class.invalid-chord-token]="segment.invalid" [class.invalid-tab-token]="segment.isTab">{{ segment.text }}</span>
}
</div>
}
@@ -77,7 +77,7 @@
class="line"
>
@for (segment of getDisplaySegments(line, true); track $index) {
<span [class.invalid-chord-token]="segment.invalid">{{ segment.text }}</span>
<span [class.invalid-chord-token]="segment.invalid" [class.invalid-tab-token]="segment.isTab">{{ segment.text }}</span>
}
</div>
}

View File

@@ -39,6 +39,15 @@
text-shadow: none;
}
.invalid-tab-token {
display: inline-block;
min-width: 1.5ch;
text-align: center;
background: rgba(180, 35, 24, 0.12);
border-radius: 3px;
font-weight: bold;
}
.menu {
position: absolute;
right: -10px;

View File

@@ -17,6 +17,7 @@ export type ChordMode = 'show' | 'hide' | 'onlyFirst';
interface DisplaySegment {
text: string;
invalid: boolean;
isTab: boolean;
}
@Component({
@@ -42,6 +43,7 @@ export class SongTextComponent implements OnInit {
public faLines = faGripLines;
public offset = 0;
public iChordMode: ChordMode = 'hide';
public showValidationIssues = false;
private invalidChordIssuesByLine = new Map<number, ChordValidationIssue[]>();
private iText = '';
private iTranspose: TransposeMode | null = null;
@@ -64,14 +66,9 @@ export class SongTextComponent implements OnInit {
}
@Input()
public set invalidChordIssues(value: ChordValidationIssue[] | null) {
const issuesByLine = new Map<number, ChordValidationIssue[]>();
(value ?? []).forEach(issue => {
const lineIssues = issuesByLine.get(issue.lineNumber) ?? [];
lineIssues.push(issue);
issuesByLine.set(issue.lineNumber, lineIssues);
});
this.invalidChordIssuesByLine = issuesByLine;
public set validateChordNotation(value: boolean) {
this.showValidationIssues = value;
this.updateValidationIssues();
this.cRef.markForCheck();
}
@@ -129,41 +126,45 @@ export class SongTextComponent implements OnInit {
const text = trim ? line.text.trim() : this.transform(line.text);
const lineNumber = line.lineNumber;
if (!lineNumber) {
return [{text, invalid: false}];
return [{text, invalid: false, isTab: false}];
}
const issues = this.invalidChordIssuesByLine.get(lineNumber);
if (!issues || issues.length === 0) {
return [{text, invalid: false}];
return [{text, invalid: false, isTab: false}];
}
const ranges: Array<{start: number; end: number}> = [];
const ranges: Array<{start: number; end: number; isTab: boolean}> = [];
let searchStart = 0;
issues.forEach(issue => {
const index = text.indexOf(issue.token, searchStart);
if (index === -1) {
return;
}
ranges.push({start: index, end: index + issue.token.length});
ranges.push({start: index, end: index + issue.token.length, isTab: issue.reason === 'tab_character'});
searchStart = index + issue.token.length;
});
if (ranges.length === 0) {
return [{text, invalid: false}];
return [{text, invalid: false, isTab: false}];
}
const segments: DisplaySegment[] = [];
let cursor = 0;
ranges.forEach(range => {
if (range.start > cursor) {
segments.push({text: text.slice(cursor, range.start), invalid: false});
segments.push({text: text.slice(cursor, range.start), invalid: false, isTab: false});
}
segments.push({text: text.slice(range.start, range.end), invalid: true});
segments.push({
text: range.isTab ? '⇥' : text.slice(range.start, range.end),
invalid: true,
isTab: range.isTab,
});
cursor = range.end;
});
if (cursor < text.length) {
segments.push({text: text.slice(cursor), invalid: false});
segments.push({text: text.slice(cursor), invalid: false, isTab: false});
}
return segments;
@@ -176,6 +177,7 @@ export class SongTextComponent implements OnInit {
private render() {
this.offset = 0;
this.sections = [];
this.updateValidationIssues();
if (this.fullscreen) {
setTimeout(() => {
this.sections = this.textRenderingService.parse(this.iText, this.iTranspose, this.showComments);
@@ -187,6 +189,21 @@ export class SongTextComponent implements OnInit {
}
}
private updateValidationIssues(): void {
if (!this.showValidationIssues) {
this.invalidChordIssuesByLine = new Map<number, ChordValidationIssue[]>();
return;
}
const issuesByLine = new Map<number, ChordValidationIssue[]>();
this.textRenderingService.validateChordNotation(this.iText, this.showComments).forEach(issue => {
const lineIssues = issuesByLine.get(issue.lineNumber) ?? [];
lineIssues.push(issue);
issuesByLine.set(issue.lineNumber, lineIssues);
});
this.invalidChordIssuesByLine = issuesByLine;
}
private getNextChordMode(): ChordMode {
switch (this.iChordMode) {
case 'show':