From 7d58dd9bdd29decab5342a809f7968f7e20358a0 Mon Sep 17 00:00:00 2001 From: smuddyx Date: Mon, 16 Mar 2020 21:07:11 +0100 Subject: [PATCH] transform keys and text service --- package.json | 2 +- .../shows/services/show-song.service.ts | 10 +- src/app/modules/shows/services/showSong.ts | 2 + .../modules/shows/show/show.component.html | 2 +- .../shows/show/song/song.component.html | 22 +++-- .../shows/show/song/song.component.less | 44 ++++++++- .../modules/shows/show/song/song.component.ts | 29 +++++- src/app/modules/songs/services/key.helper.ts | 95 +++++++++++++++++++ .../modules/songs/services/song.service.ts | 11 +-- .../services/text-rendering.service.spec.ts | 64 ++++++++++++- .../songs/services/text-rendering.service.ts | 36 ++++++- .../edit/edit-song/edit-song.component.html | 2 +- .../edit/edit-song/edit-song.component.ts | 9 +- .../modules/songs/song/edit/edit.module.ts | 2 + .../key-translator/key-translator.module.ts | 16 ++++ .../pipes/key-translator/key.pipe.spec.ts | 8 ++ .../pipes/key-translator/key.pipe.ts | 13 +++ src/styles/styles.less | 4 + 18 files changed, 335 insertions(+), 36 deletions(-) create mode 100644 src/app/modules/songs/services/key.helper.ts create mode 100644 src/app/widget-modules/pipes/key-translator/key-translator.module.ts create mode 100644 src/app/widget-modules/pipes/key-translator/key.pipe.spec.ts create mode 100644 src/app/widget-modules/pipes/key-translator/key.pipe.ts diff --git a/package.json b/package.json index a47cb76..24a72a7 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "scripts": { "ng": "ng", - "start": "ng serve", + "start": "npm i && ng serve", "build": "ng build", "deploy": "ng build --prod && firebase deploy", "test": "ng test", diff --git a/src/app/modules/shows/services/show-song.service.ts b/src/app/modules/shows/services/show-song.service.ts index ed3091d..2ff102c 100644 --- a/src/app/modules/shows/services/show-song.service.ts +++ b/src/app/modules/shows/services/show-song.service.ts @@ -2,17 +2,23 @@ import {Injectable} from '@angular/core'; import {ShowSongDataService} from './show-song-data.service'; import {Observable} from 'rxjs'; import {ShowSong} from './showSong'; +import {SongDataService} from '../../songs/services/song-data.service'; +import {take} from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class ShowSongService { - constructor(private showSongDataService: ShowSongDataService) { + constructor( + private showSongDataService: ShowSongDataService, + private songDataService: SongDataService + ) { } public async new$(showId: string, songId: string, order: number): Promise { - const data = {songId, order}; + const song = await this.songDataService.read$(songId).pipe(take(1)).toPromise(); + const data = {songId, order, key: song.key, keyOriginal: song.key}; return await this.showSongDataService.add(showId, data); } diff --git a/src/app/modules/shows/services/showSong.ts b/src/app/modules/shows/services/showSong.ts index a75dd3c..6075fdc 100644 --- a/src/app/modules/shows/services/showSong.ts +++ b/src/app/modules/shows/services/showSong.ts @@ -1,5 +1,7 @@ export interface ShowSong { id: string; songId: string; + key: string; + keyOriginal: string; order: number; } diff --git a/src/app/modules/shows/show/show.component.html b/src/app/modules/shows/show/show.component.html index 6a6943c..d245437 100644 --- a/src/app/modules/shows/show/show.component.html +++ b/src/app/modules/shows/show/show.component.html @@ -5,7 +5,7 @@
diff --git a/src/app/modules/shows/show/song/song.component.html b/src/app/modules/shows/show/song/song.component.html index 30e06d3..39522ce 100644 --- a/src/app/modules/shows/show/song/song.component.html +++ b/src/app/modules/shows/show/song/song.component.html @@ -1,10 +1,14 @@ -
-

- - - {{song.title}}

- +
+ + + {{_song.title}} + + {{showSong.keyOriginal}} →  + + + {{key}} + + + +
diff --git a/src/app/modules/shows/show/song/song.component.less b/src/app/modules/shows/show/song/song.component.less index a5149d7..b8fa9b9 100644 --- a/src/app/modules/shows/show/song/song.component.less +++ b/src/app/modules/shows/show/song/song.component.less @@ -1,13 +1,49 @@ .song { border-bottom: 1px solid #ccc; - display: flex; - align-items: center; - justify-content: space-between; + display: grid; + grid-template-columns: 20px 20px auto 70px 25px; + @media screen and (max-width: 860px) { + grid-template-columns: 40px 40px auto 70px 45px; + } + grid-template-areas: "up down title keys delete"; + + & > * { + display: flex; + align-items: center; + } + + overflow: hidden; +} + +mat-form-field { + width: 40px; + margin: -24px 0 -20px 0; +} + +.btnUp { + grid-area: up; +} + +.btnDown { + grid-area: down; +} + +.title { + grid-area: title; +} + +.keys { + grid-area: keys; + justify-content: flex-end; +} + +.btnDelete { + grid-area: delete; + justify-content: flex-end; } .menu { display: flex; - } button { diff --git a/src/app/modules/shows/show/song/song.component.ts b/src/app/modules/shows/show/song/song.component.ts index 5e7c79a..189f6f3 100644 --- a/src/app/modules/shows/show/song/song.component.ts +++ b/src/app/modules/shows/show/song/song.component.ts @@ -1,32 +1,51 @@ -import {Component, Input} from '@angular/core'; +import {Component, Input, OnInit} from '@angular/core'; import {Song} from '../../../songs/services/song'; import {faTrash} from '@fortawesome/free-solid-svg-icons/faTrash'; import {faCaretUp} from '@fortawesome/free-solid-svg-icons/faCaretUp'; import {faCaretDown} from '@fortawesome/free-solid-svg-icons/faCaretDown'; import {ShowSongService} from '../../services/show-song.service'; import {ShowSong} from '../../services/showSong'; +import {getScale} from '../../../songs/services/key.helper'; +import {FormControl} from '@angular/forms'; @Component({ selector: 'app-song', templateUrl: './song.component.html', styleUrls: ['./song.component.less'] }) -export class SongComponent { - @Input() public song: Song; +export class SongComponent implements OnInit { + @Input() public showSong: ShowSong; + @Input() public showId: string; - @Input() public showSongId: string; + public keys: string[]; @Input() public showSongs: ShowSong[]; public faDelete = faTrash; public faUp = faCaretUp; public faDown = faCaretDown; + public keyFormControl: FormControl; + + public _song: Song; + + @Input() + public set song(song: Song) { + this._song = song; + this.keys = !!song ? getScale(song.key) : []; + }; constructor( private showSongService: ShowSongService, ) { } + public ngOnInit(): void { + this.keyFormControl = new FormControl(this.showSong.key); + this.keyFormControl.valueChanges.subscribe(async value => { + await this.showSongService.update$(this.showId, this.showSong.id, {key: value}); + }) + } + public async onDelete(): Promise { - await this.showSongService.delete$(this.showId, this.showSongId); + await this.showSongService.delete$(this.showId, this.showSong.id); } diff --git a/src/app/modules/songs/services/key.helper.ts b/src/app/modules/songs/services/key.helper.ts new file mode 100644 index 0000000..18708ee --- /dev/null +++ b/src/app/modules/songs/services/key.helper.ts @@ -0,0 +1,95 @@ +export const KEYS = [ + 'C', 'C#', 'Db', 'D', 'D#', 'Eb', 'E', 'F', 'F#', 'Gb', 'G', 'G#', 'Ab', 'A', 'A#', 'B', 'H', + 'c', 'c#', 'db', 'd', 'd#', 'eb', 'e', 'f', 'f#', 'gb', 'g', 'g#', 'ab', 'a', 'a#', 'b', 'h' +]; +export const KEYS_MAJOR_FLAT = [ + 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'H', +]; +export const KEYS_MAJOR_B = [ + 'C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'B', 'H', +]; +export const KEYS_MINOR_FLAT = [ + 'c', 'c#', 'd', 'd#', 'e', 'f', 'f#', 'g', 'g#', 'a', 'a#', 'h', +]; +export const KEYS_MINOR_B = [ + 'c', 'db', 'd', 'eb', 'e', 'f', 'gb', 'g', 'ab', 'a', 'b', 'h', +]; + +const scaleAssignment = { + 'C': KEYS_MAJOR_FLAT, + 'C#': KEYS_MAJOR_FLAT, + 'Db': KEYS_MAJOR_B, + 'D': KEYS_MAJOR_B, + 'D#': KEYS_MAJOR_FLAT, + 'Eb': KEYS_MAJOR_B, + 'E': KEYS_MAJOR_FLAT, + 'F': KEYS_MAJOR_B, + 'F#': KEYS_MAJOR_FLAT, + 'Gb': KEYS_MAJOR_B, + 'G': KEYS_MAJOR_FLAT, + 'G#': KEYS_MAJOR_FLAT, + 'Ab': KEYS_MAJOR_B, + 'A': KEYS_MAJOR_FLAT, + 'A#': KEYS_MAJOR_FLAT, + 'B': KEYS_MAJOR_B, + 'H': KEYS_MAJOR_FLAT, + 'c': KEYS_MINOR_FLAT, + 'c#': KEYS_MINOR_FLAT, + 'db': KEYS_MINOR_B, + 'd': KEYS_MINOR_B, + 'd#': KEYS_MINOR_FLAT, + 'eb': KEYS_MINOR_B, + 'e': KEYS_MINOR_FLAT, + 'f': KEYS_MINOR_B, + 'f#': KEYS_MINOR_FLAT, + 'gb': KEYS_MINOR_B, + 'g': KEYS_MINOR_FLAT, + 'g#': KEYS_MINOR_FLAT, + 'ab': KEYS_MINOR_B, + 'a': KEYS_MINOR_FLAT, + 'a#': KEYS_MINOR_FLAT, + 'b': KEYS_MINOR_B, + 'h': KEYS_MINOR_FLAT, +}; + +export const scaleMapping = { + 'C': 'C', + 'C#': 'C♯', + 'Db': 'D♭', + 'D': 'D', + 'D#': 'D♯', + 'Eb': 'E♭', + 'E': 'E', + 'F': 'F', + 'F#': 'F♯', + 'Gb': 'D♭', + 'G': 'G', + 'G#': 'G♯', + 'Ab': 'A♭', + 'A': 'A', + 'A#': 'A♯', + 'B': 'B', + 'H': 'H', + 'c': 'c', + 'c#': 'c♯', + 'db': 'd♭', + 'd': 'd', + 'd#': 'd♯', + 'eb': 'e♭', + 'e': 'e', + 'f': 'f', + 'f#': 'f♯', + 'gb': 'g♭', + 'g': 'g', + 'g#': 'g♯', + 'ab': 'a♭', + 'a': 'a', + 'a#': 'a♯', + 'b': 'b', + 'h': 'h', +}; + +export const getScale = (key: string): string[] => { + const scaleAssignmentElement = scaleAssignment[key]; + return scaleAssignmentElement; +}; diff --git a/src/app/modules/songs/services/song.service.ts b/src/app/modules/songs/services/song.service.ts index 2c1ab5b..ff9c1e1 100644 --- a/src/app/modules/songs/services/song.service.ts +++ b/src/app/modules/songs/services/song.service.ts @@ -11,15 +11,12 @@ declare var importCCLI: any; }) export class SongService { - public TYPES = ['Praise', 'Worship']; + public static TYPES = ['Praise', 'Worship']; + + public static LEGAL_OWNER = ['CCLI', 'other']; + public static LEGAL_TYPE = ['open', 'allowed']; - public LEGAL_OWNER = ['CCLI', 'other']; - public LEGAL_TYPE = ['open', 'allowed']; - public KEYS = [ - 'C', 'C#', 'Db', 'D', 'D#', 'Eb', 'E', 'F', 'F#', 'Gb', 'G', 'G#', 'Ab', 'A', 'A#', 'B', 'H', - 'c', 'c#', 'db', 'd', 'd#', 'eb', 'e', 'f', 'f#', 'gb', 'g', 'g#', 'ab', 'a', 'a#', 'b', 'h' - ]; private list: Song[]; constructor(private songDataService: SongDataService) { diff --git a/src/app/modules/songs/services/text-rendering.service.spec.ts b/src/app/modules/songs/services/text-rendering.service.spec.ts index 88080fc..754eda0 100644 --- a/src/app/modules/songs/services/text-rendering.service.spec.ts +++ b/src/app/modules/songs/services/text-rendering.service.spec.ts @@ -1,12 +1,74 @@ import {TestBed} from '@angular/core/testing'; -import {TextRenderingService} from './text-rendering.service'; +import {LineType, SectionType, TextRenderingService} from './text-rendering.service'; describe('TextRenderingService', () => { + const testText = `Strophe +C D E F G A H +Text Line 1-1 + a d e f g a h c b +Text Line 2-1 + +Strophe +C D E F G A H +Text Line 1-2 + a d e f g a h c b +Text Line 2-2 + +Refrain +c# cb c7 cmaj7 c/e +and the chorus + +Bridge +Cool bridge without any chords +`; + + beforeEach(() => TestBed.configureTestingModule({})); it('should be created', () => { const service: TextRenderingService = TestBed.get(TextRenderingService); expect(service).toBeTruthy(); }); + + it('should parse section types', () => { + const service: TextRenderingService = TestBed.get(TextRenderingService); + const sections = service.parse(testText); + expect(sections[0].type).toBe(SectionType.Verse); + expect(sections[1].type).toBe(SectionType.Verse); + expect(sections[2].type).toBe(SectionType.Chorus); + expect(sections[3].type).toBe(SectionType.Bridge); + }); + + it('should parse text lines', () => { + const service: TextRenderingService = TestBed.get(TextRenderingService); + const sections = service.parse(testText); + expect(sections[0].lines[1].type).toBe(LineType.text); + expect(sections[0].lines[1].text).toBe('Text Line 1-1'); + expect(sections[0].lines[3].type).toBe(LineType.text); + expect(sections[0].lines[3].text).toBe('Text Line 2-1'); + expect(sections[1].lines[1].type).toBe(LineType.text); + expect(sections[1].lines[1].text).toBe('Text Line 1-2'); + expect(sections[1].lines[3].type).toBe(LineType.text); + expect(sections[1].lines[3].text).toBe('Text Line 2-2'); + expect(sections[2].lines[1].type).toBe(LineType.text); + expect(sections[2].lines[1].text).toBe('and the chorus'); + expect(sections[3].lines[0].type).toBe(LineType.text); + expect(sections[3].lines[0].text).toBe('Cool bridge without any chords'); + }); + + it('should parse chord lines', () => { + const service: TextRenderingService = TestBed.get(TextRenderingService); + const sections = service.parse(testText); + expect(sections[0].lines[0].type).toBe(LineType.chrod); + expect(sections[0].lines[0].text).toBe('C D E F G A H'); + expect(sections[0].lines[2].type).toBe(LineType.chrod); + expect(sections[0].lines[2].text).toBe(' a d e f g a h c b'); + expect(sections[1].lines[0].type).toBe(LineType.chrod); + expect(sections[1].lines[0].text).toBe('C D E F G A H'); + expect(sections[1].lines[2].type).toBe(LineType.chrod); + expect(sections[1].lines[2].text).toBe(' a d e f g a h c b'); + expect(sections[2].lines[0].type).toBe(LineType.chrod); + expect(sections[2].lines[0].text).toBe('c# cb c7 cmaj7 c/e'); + }); }); diff --git a/src/app/modules/songs/services/text-rendering.service.ts b/src/app/modules/songs/services/text-rendering.service.ts index 9cb23cd..d017904 100644 --- a/src/app/modules/songs/services/text-rendering.service.ts +++ b/src/app/modules/songs/services/text-rendering.service.ts @@ -17,7 +17,6 @@ export interface Line { text: string; } - export interface Section { type: SectionType; number: number; @@ -32,5 +31,40 @@ export class TextRenderingService { constructor() { } + private regexSection = /(Strophe|Refrain|Bridge)/; + private regexChords = /\b([CDEFGAHBcdefgahb](#|##|b|bb|sus|maj|maj7|min|aug|\d+|\/[CDEFGAHBcdefgahb])?\b)/; + + public parse(text: string): Section[] { + const arrayOfLines = text.split(/\r?\n/).filter(_ => _); + const sections = arrayOfLines.reduce((array, line) => { + if (line.match(this.regexSection)) return [...array, { + type: this.getSectionTypeOfLine(line), + number: -1, + lines: [] + }]; + array[array.length - 1].lines.push(this.getLineOfLineText(line)); + return array; + }, [] as Section[]); + + return sections; + } + + private getLineOfLineText(text: string): Line { + const matches = !!text.match(this.regexChords); + const type = matches ? LineType.chrod : LineType.text; + return {type, text} + } + + private getSectionTypeOfLine(line: string): SectionType { + const typeString = line.match(this.regexSection)[1]; + switch (typeString) { + case "Strophe": + return SectionType.Verse; + case "Refrain": + return SectionType.Chorus; + case "Bridge": + return SectionType.Bridge; + } + } } diff --git a/src/app/modules/songs/song/edit/edit-song/edit-song.component.html b/src/app/modules/songs/song/edit/edit-song/edit-song.component.html index 046d1c6..1515a78 100644 --- a/src/app/modules/songs/song/edit/edit-song/edit-song.component.html +++ b/src/app/modules/songs/song/edit/edit-song/edit-song.component.html @@ -16,7 +16,7 @@ Tonart - {{key}} + {{key|key}} diff --git a/src/app/modules/songs/song/edit/edit-song/edit-song.component.ts b/src/app/modules/songs/song/edit/edit-song/edit-song.component.ts index 1a301bf..7823125 100644 --- a/src/app/modules/songs/song/edit/edit-song/edit-song.component.ts +++ b/src/app/modules/songs/song/edit/edit-song/edit-song.component.ts @@ -5,6 +5,7 @@ import {ActivatedRoute, Router} from '@angular/router'; import {SongService} from '../../../services/song.service'; import {EditService} from '../edit.service'; import {first, map, switchMap} from 'rxjs/operators'; +import {KEYS} from '../../../services/key.helper'; @Component({ selector: 'app-edit-song', @@ -14,10 +15,10 @@ import {first, map, switchMap} from 'rxjs/operators'; export class EditSongComponent implements OnInit { public song: Song; public form: FormGroup; - public keys = this.songService.KEYS; - public types = this.songService.TYPES; - public legalOwner = this.songService.LEGAL_OWNER; - public legalType = this.songService.LEGAL_TYPE; + public keys = KEYS; + public types = SongService.TYPES; + public legalOwner = SongService.LEGAL_OWNER; + public legalType = SongService.LEGAL_TYPE; constructor( private activatedRoute: ActivatedRoute, diff --git a/src/app/modules/songs/song/edit/edit.module.ts b/src/app/modules/songs/song/edit/edit.module.ts index 0edb696..0a6c465 100644 --- a/src/app/modules/songs/song/edit/edit.module.ts +++ b/src/app/modules/songs/song/edit/edit.module.ts @@ -16,6 +16,7 @@ import {MatIconModule} from '@angular/material/icon'; import {FileComponent} from './edit-file/file/file.component'; import {LegalOwnerTranslatorModule} from '../../../../widget-modules/pipes/legal-owner-translator/legal-owner-translator.module'; import {LegalTypeTranslatorModule} from '../../../../widget-modules/pipes/legal-type-translator/legal-type-translator.module'; +import {KeyTranslatorModule} from '../../../../widget-modules/pipes/key-translator/key-translator.module'; @NgModule({ @@ -37,6 +38,7 @@ import {LegalTypeTranslatorModule} from '../../../../widget-modules/pipes/legal- MatIconModule, LegalOwnerTranslatorModule, LegalTypeTranslatorModule, + KeyTranslatorModule, ] }) diff --git a/src/app/widget-modules/pipes/key-translator/key-translator.module.ts b/src/app/widget-modules/pipes/key-translator/key-translator.module.ts new file mode 100644 index 0000000..48574c0 --- /dev/null +++ b/src/app/widget-modules/pipes/key-translator/key-translator.module.ts @@ -0,0 +1,16 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {KeyPipe} from './key.pipe'; + + +@NgModule({ + declarations: [KeyPipe], + exports: [ + KeyPipe + ], + imports: [ + CommonModule + ] +}) +export class KeyTranslatorModule { +} diff --git a/src/app/widget-modules/pipes/key-translator/key.pipe.spec.ts b/src/app/widget-modules/pipes/key-translator/key.pipe.spec.ts new file mode 100644 index 0000000..3363244 --- /dev/null +++ b/src/app/widget-modules/pipes/key-translator/key.pipe.spec.ts @@ -0,0 +1,8 @@ +import {KeyPipe} from './key.pipe'; + +describe('KeyPipe', () => { + it('create an instance', () => { + const pipe = new KeyPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/src/app/widget-modules/pipes/key-translator/key.pipe.ts b/src/app/widget-modules/pipes/key-translator/key.pipe.ts new file mode 100644 index 0000000..491ca3f --- /dev/null +++ b/src/app/widget-modules/pipes/key-translator/key.pipe.ts @@ -0,0 +1,13 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {scaleMapping} from '../../../modules/songs/services/key.helper'; + +@Pipe({ + name: 'key' +}) +export class KeyPipe implements PipeTransform { + + transform(key: string): string { + return scaleMapping[key]; + } + +} diff --git a/src/styles/styles.less b/src/styles/styles.less index d2471f0..66b04fc 100644 --- a/src/styles/styles.less +++ b/src/styles/styles.less @@ -34,6 +34,10 @@ h1, h2, h3, h4 { width: unset; } +.mat-form-field.mat-form-field-appearance-standard .mat-form-field-underline { + height: 0; +} + .content > *:not(router-outlet) { margin-top: 60px;