src/chord_sheet/line.ts
import ChordLyricsPair from './chord_lyrics_pair';
import Tag from './tag';
import Comment from './comment';
import Item from './item';
import Font from './font';
import { ContentType } from '../serialized_types';
import {
BRIDGE,
CHORUS,
GRID,
NONE,
TAB,
VERSE,
} from '../constants';
type MapItemFunc = (_item: Item) => Item | null;
export type LineType = 'bridge' | 'chorus' | 'none' | 'tab' | 'verse' | ContentType | 'indeterminate';
/**
* Represents a line in a chord sheet, consisting of items of type ChordLyricsPair or Tag
*/
class Line {
/**
* The items ({@link ChordLyricsPair} or {@link Tag} or {@link Comment}) of which the line consists
* @type {Array.<(ChordLyricsPair|Tag|Comment)>}
*/
items: Item[] = [];
/**
* The line type, This is set by the ChordProParser when it read tags like {start_of_chorus} or {start_of_verse}
* Values can be {@link VERSE}, {@link CHORUS} or {@link NONE}
* @type {string}
*/
type: LineType = NONE;
currentChordLyricsPair: ChordLyricsPair = new ChordLyricsPair();
key: string | null = null;
transposeKey: string | null = null;
lineNumber: number | null = null;
/**
* The text font that applies to this line. Is derived from the directives:
* `textfont`, `textsize` and `textcolour`
* See: https://www.chordpro.org/chordpro/directives-props_text_legacy/
* @type {Font}
*/
textFont: Font = new Font();
/**
* The chord font that applies to this line. Is derived from the directives:
* `chordfont`, `chordsize` and `chordcolour`
* See: https://www.chordpro.org/chordpro/directives-props_chord_legacy/
* @type {Font}
*/
chordFont: Font = new Font();
constructor({ type, items }: { type: LineType, items: Item[] } = { type: NONE, items: [] }) {
this.type = type;
this.items = items;
}
/**
* Indicates whether the line contains any items
* @returns {boolean}
*/
isEmpty(): boolean {
return this.items.length === 0;
}
isNotEmpty(): boolean {
return !this.isEmpty();
}
/**
* Adds an item ({@link ChordLyricsPair} or {@link Tag}) to the line
* @param {ChordLyricsPair|Tag} item The item to be added
*/
addItem(item: Item): void {
if (item instanceof Tag) {
this.addTag(item);
} else if (item instanceof ChordLyricsPair) {
this.addChordLyricsPair(item);
} else if (item instanceof Comment) {
this.addComment(item);
} else {
this.items.push(item);
}
}
/**
* Indicates whether the line contains items that are renderable
* @returns {boolean}
*/
hasRenderableItems(): boolean {
return this.items.some((item) => item.isRenderable());
}
/**
* Returns a deep copy of the line and all of its items
* @returns {Line}
*/
clone(): Line {
return this.mapItems(null);
}
mapItems(func: MapItemFunc | null): Line {
const clonedLine = new Line();
clonedLine.items = this.items
.map((item) => {
const clonedItem = item.clone();
return func ? func(clonedItem) : clonedItem;
})
.filter((item) => item !== null) as Item[];
clonedLine.type = this.type;
return clonedLine;
}
/**
* Indicates whether the line type is {@link BRIDGE}
* @returns {boolean}
*/
isBridge(): boolean {
return this.type === BRIDGE;
}
/**
* Indicates whether the line type is {@link CHORUS}
* @returns {boolean}
*/
isChorus(): boolean {
return this.type === CHORUS;
}
/**
* Indicates whether the line type is {@link GRID}
* @returns {boolean}
*/
isGrid(): boolean {
return this.type === GRID;
}
/**
* Indicates whether the line type is {@link TAB}
* @returns {boolean}
*/
isTab(): boolean {
return this.type === TAB;
}
/**
* Indicates whether the line type is {@link VERSE}
* @returns {boolean}
*/
isVerse(): boolean {
return this.type === VERSE;
}
/**
* Indicates whether the line contains items that are renderable. Please use {@link hasRenderableItems}
* @deprecated
* @returns {boolean}
*/
hasContent(): boolean {
return this.hasRenderableItems();
}
addChordLyricsPair(chords: ChordLyricsPair | string | null = null, lyrics = null): ChordLyricsPair {
if (chords instanceof ChordLyricsPair) {
this.currentChordLyricsPair = chords;
} else {
this.currentChordLyricsPair = new ChordLyricsPair(chords || '', lyrics || '');
}
this.items.push(this.currentChordLyricsPair);
return this.currentChordLyricsPair;
}
ensureChordLyricsPair(): void {
if (!this.currentChordLyricsPair) {
this.addChordLyricsPair();
}
}
chords(chr: string): void {
this.ensureChordLyricsPair();
this.currentChordLyricsPair.chords += chr;
}
lyrics(chr: string): void {
this.ensureChordLyricsPair();
this.currentChordLyricsPair.lyrics += chr;
}
addTag(nameOrTag: Tag | string, value: string | null = null): Tag {
const tag = (nameOrTag instanceof Tag) ? nameOrTag : new Tag(nameOrTag, value);
this.items.push(tag);
return tag;
}
addComment(content: Comment | string): Comment {
const comment = (content instanceof Comment) ? content : new Comment(content);
this.items.push(comment);
return comment;
}
set(properties: { type?: LineType, items?: Item[] }): Line {
return new Line(
{
type: this.type,
items: this.items,
...properties,
},
);
}
get _tag(): Tag | null {
if (this.items.length !== 1) {
return null;
}
const item = this.items[0];
if (!(item instanceof Tag)) {
return null;
}
return item;
}
isSectionStart(): boolean {
return this._tag?.isSectionStart() || false;
}
isSectionEnd(): boolean {
return this._tag?.isSectionEnd() || false;
}
}
export default Line;