validate chords
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<button mat-button>
|
||||
<button [disabled]="disabled" mat-button>
|
||||
@if (icon) {
|
||||
<span
|
||||
><fa-icon [icon]="icon"></fa-icon><span class="content"> </span></span
|
||||
|
||||
@@ -11,5 +11,6 @@ import {FaIconComponent} from '@fortawesome/angular-fontawesome';
|
||||
imports: [MatButton, FaIconComponent],
|
||||
})
|
||||
export class ButtonComponent {
|
||||
@Input() public disabled = false;
|
||||
@Input() public icon: IconProp | null = null;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,10 @@
|
||||
[class.comment]="isComment(line.text)"
|
||||
[class.disabled]="checkDisabled(i)"
|
||||
class="line"
|
||||
>{{ transform(line.text) }}
|
||||
>
|
||||
@for (segment of getDisplaySegments(line); track $index) {
|
||||
<span [class.invalid-chord-token]="segment.invalid">{{ segment.text }}</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -72,7 +75,10 @@
|
||||
[class.chord]="line.type === 0"
|
||||
[class.disabled]="checkDisabled(i)"
|
||||
class="line"
|
||||
>{{ line.text.trim() }}
|
||||
>
|
||||
@for (segment of getDisplaySegments(line, true); track $index) {
|
||||
<span [class.invalid-chord-token]="segment.invalid">{{ segment.text }}</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -34,6 +34,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.invalid-chord-token {
|
||||
color: #b42318;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.menu {
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
|
||||
@@ -7,12 +7,18 @@ import {SectionType} from '../../../modules/songs/services/section-type';
|
||||
import {LineType} from '../../../modules/songs/services/line-type';
|
||||
import {Section} from '../../../modules/songs/services/section';
|
||||
import {Line} from '../../../modules/songs/services/line';
|
||||
import {ChordValidationIssue} from '../../../modules/songs/services/chord';
|
||||
|
||||
import {MatIconButton} from '@angular/material/button';
|
||||
import {FaIconComponent} from '@fortawesome/angular-fontawesome';
|
||||
|
||||
export type ChordMode = 'show' | 'hide' | 'onlyFirst';
|
||||
|
||||
interface DisplaySegment {
|
||||
text: string;
|
||||
invalid: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-song-text',
|
||||
templateUrl: './song-text.component.html',
|
||||
@@ -36,6 +42,7 @@ export class SongTextComponent implements OnInit {
|
||||
public faLines = faGripLines;
|
||||
public offset = 0;
|
||||
public iChordMode: ChordMode = 'hide';
|
||||
private invalidChordIssuesByLine = new Map<number, ChordValidationIssue[]>();
|
||||
private iText = '';
|
||||
private iTranspose: TransposeMode | null = null;
|
||||
|
||||
@@ -56,6 +63,18 @@ export class SongTextComponent implements OnInit {
|
||||
this.render();
|
||||
}
|
||||
|
||||
@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;
|
||||
this.cRef.markForCheck();
|
||||
}
|
||||
|
||||
public ngOnInit(): void {
|
||||
setInterval(() => {
|
||||
if (!this.fullscreen || this.index === -1 || !this.viewSections?.toArray()[this.index]) {
|
||||
@@ -106,6 +125,50 @@ export class SongTextComponent implements OnInit {
|
||||
return text;
|
||||
}
|
||||
|
||||
public getDisplaySegments(line: Line, trim = false): DisplaySegment[] {
|
||||
const text = trim ? line.text.trim() : this.transform(line.text);
|
||||
const lineNumber = line.lineNumber;
|
||||
if (!lineNumber) {
|
||||
return [{text, invalid: false}];
|
||||
}
|
||||
|
||||
const issues = this.invalidChordIssuesByLine.get(lineNumber);
|
||||
if (!issues || issues.length === 0) {
|
||||
return [{text, invalid: false}];
|
||||
}
|
||||
|
||||
const ranges: Array<{start: number; end: number}> = [];
|
||||
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});
|
||||
searchStart = index + issue.token.length;
|
||||
});
|
||||
|
||||
if (ranges.length === 0) {
|
||||
return [{text, invalid: 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(range.start, range.end), invalid: true});
|
||||
cursor = range.end;
|
||||
});
|
||||
|
||||
if (cursor < text.length) {
|
||||
segments.push({text: text.slice(cursor), invalid: false});
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
public isComment(text: string) {
|
||||
return text.startsWith('#');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user