transpose & history
This commit is contained in:
@@ -51,7 +51,7 @@ export class MonitorComponent implements OnInit {
|
|||||||
switchMap(_ => this.songService.read$(_.presentationSongId))
|
switchMap(_ => this.songService.read$(_.presentationSongId))
|
||||||
).subscribe((_: Song) => {
|
).subscribe((_: Song) => {
|
||||||
this.song = _;
|
this.song = _;
|
||||||
this.sections = this.textRenderingService.parse(_.text);
|
this.sections = this.textRenderingService.parse(_.text, null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export class RemoteComponent {
|
|||||||
.map(song => ({
|
.map(song => ({
|
||||||
id: song.id,
|
id: song.id,
|
||||||
title: song.title,
|
title: song.title,
|
||||||
sections: this.textRenderingService.parse(song.text)
|
sections: this.textRenderingService.parse(song.text, null)
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
await delay(500);
|
await delay(500);
|
||||||
|
|||||||
@@ -180,7 +180,10 @@ export class DocxService {
|
|||||||
const showSongs = await this.showSongService.list(showId);
|
const showSongs = await this.showSongService.list(showId);
|
||||||
const songsAsync = await showSongs.map(async showSong => {
|
const songsAsync = await showSongs.map(async showSong => {
|
||||||
const song = await this.songService.read(showSong.songId);
|
const song = await this.songService.read(showSong.songId);
|
||||||
const sections = this.textRenderingService.parse(song.text);
|
const sections = this.textRenderingService.parse(song.text, {
|
||||||
|
baseKey: showSong.keyOriginal,
|
||||||
|
targetKey: showSong.key
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
showSong,
|
showSong,
|
||||||
song,
|
song,
|
||||||
|
|||||||
@@ -16,6 +16,6 @@
|
|||||||
<app-menu-button (click)="onDelete()" [icon]="faDelete" class="btn-delete btn-icon"></app-menu-button>
|
<app-menu-button (click)="onDelete()" [icon]="faDelete" class="btn-delete btn-icon"></app-menu-button>
|
||||||
</div>
|
</div>
|
||||||
<app-song-text (chordModeChanged)="onChordModeChanged($event)" *ngIf="showText || show.published"
|
<app-song-text (chordModeChanged)="onChordModeChanged($event)" *ngIf="showText || show.published"
|
||||||
[chordMode]="showSong.chordMode"
|
[chordMode]="showSong.chordMode" [transpose]="{baseKey: showSong.keyOriginal, targetKey: showSong.key}"
|
||||||
[showSwitch]="!show.published" [text]="_song.text"></app-song-text>
|
[showSwitch]="!show.published" [text]="_song.text"></app-song-text>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ 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',
|
||||||
'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_REGEX = new RegExp('(' + KEYS.reduce((a, b) => a + '|' + b) + ')', 'gm');
|
|
||||||
export const KEYS_MAJOR_FLAT = [
|
export const KEYS_MAJOR_FLAT = [
|
||||||
'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'H',
|
'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'H',
|
||||||
];
|
];
|
||||||
@@ -16,6 +15,45 @@ export const KEYS_MINOR_B = [
|
|||||||
'c', 'db', 'd', 'eb', 'e', 'f', 'gb', 'g', 'ab', 'a', 'b', 'h',
|
'c', 'db', 'd', 'eb', 'e', 'f', 'gb', 'g', 'ab', 'a', 'b', 'h',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export type scale = 'b' | 'flat'
|
||||||
|
|
||||||
|
const scaleTypeAssignment: { [key: string]: string[][] } = {
|
||||||
|
'C': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
|
||||||
|
'C#': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
|
||||||
|
'Db': [KEYS_MAJOR_B, KEYS_MINOR_B],
|
||||||
|
'D': [KEYS_MAJOR_B, KEYS_MINOR_B],
|
||||||
|
'D#': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
|
||||||
|
'Eb': [KEYS_MAJOR_B, KEYS_MINOR_B],
|
||||||
|
'E': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
|
||||||
|
'F': [KEYS_MAJOR_B, KEYS_MINOR_B],
|
||||||
|
'F#': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
|
||||||
|
'Gb': [KEYS_MAJOR_B, KEYS_MINOR_B],
|
||||||
|
'G': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
|
||||||
|
'G#': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
|
||||||
|
'Ab': [KEYS_MAJOR_B, KEYS_MINOR_B],
|
||||||
|
'A': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
|
||||||
|
'A#': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
|
||||||
|
'B': [KEYS_MAJOR_B, KEYS_MINOR_B],
|
||||||
|
'H': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
|
||||||
|
'c': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
|
||||||
|
'c#': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
|
||||||
|
'db': [KEYS_MINOR_B, KEYS_MAJOR_B],
|
||||||
|
'd': [KEYS_MINOR_B, KEYS_MAJOR_B],
|
||||||
|
'd#': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
|
||||||
|
'eb': [KEYS_MINOR_B, KEYS_MAJOR_B],
|
||||||
|
'e': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
|
||||||
|
'f': [KEYS_MINOR_B, KEYS_MAJOR_B],
|
||||||
|
'f#': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
|
||||||
|
'gb': [KEYS_MINOR_B, KEYS_MAJOR_B],
|
||||||
|
'g': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
|
||||||
|
'g#': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
|
||||||
|
'ab': [KEYS_MINOR_B, KEYS_MAJOR_B],
|
||||||
|
'a': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
|
||||||
|
'a#': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
|
||||||
|
'b': [KEYS_MINOR_B, KEYS_MAJOR_B],
|
||||||
|
'h': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
|
||||||
|
}
|
||||||
|
|
||||||
const scaleAssignment = {
|
const scaleAssignment = {
|
||||||
'C': KEYS_MAJOR_FLAT,
|
'C': KEYS_MAJOR_FLAT,
|
||||||
'C#': KEYS_MAJOR_FLAT,
|
'C#': KEYS_MAJOR_FLAT,
|
||||||
@@ -90,7 +128,6 @@ export const scaleMapping = {
|
|||||||
'h': 'h',
|
'h': 'h',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getScale = (key: string): string[] => {
|
export const getScale = (key: string): string[] => scaleAssignment[key];
|
||||||
const scaleAssignmentElement = scaleAssignment[key];
|
export const getScaleType = (key: string): string[][] => scaleTypeAssignment[key];
|
||||||
return scaleAssignmentElement;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import {Observable} from 'rxjs';
|
|||||||
import {Song} from './song';
|
import {Song} from './song';
|
||||||
import {SongDataService} from './song-data.service';
|
import {SongDataService} from './song-data.service';
|
||||||
import {first, tap} from 'rxjs/operators';
|
import {first, tap} from 'rxjs/operators';
|
||||||
|
import {UserService} from '../../../services/user/user.service';
|
||||||
|
import * as firebase from 'firebase';
|
||||||
|
import Timestamp = firebase.firestore.Timestamp;
|
||||||
|
|
||||||
declare var importCCLI: any;
|
declare var importCCLI: any;
|
||||||
|
|
||||||
@@ -19,7 +22,7 @@ export class SongService {
|
|||||||
|
|
||||||
private list: Song[];
|
private list: Song[];
|
||||||
|
|
||||||
constructor(private songDataService: SongDataService) {
|
constructor(private songDataService: SongDataService, private userService: UserService) {
|
||||||
importCCLI = (songs: Song[]) => this.updateFromCLI(songs);
|
importCCLI = (songs: Song[]) => this.updateFromCLI(songs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +31,11 @@ export class SongService {
|
|||||||
public read = (songId: string): Promise<Song | undefined> => this.read$(songId).pipe(first()).toPromise();
|
public read = (songId: string): Promise<Song | undefined> => this.read$(songId).pipe(first()).toPromise();
|
||||||
|
|
||||||
public async update$(songId: string, data: Partial<Song>): Promise<void> {
|
public async update$(songId: string, data: Partial<Song>): Promise<void> {
|
||||||
await this.songDataService.update$(songId, data);
|
const song = await this.read(songId);
|
||||||
|
const edits = song.edits ?? [];
|
||||||
|
const user = await this.userService.currentUser();
|
||||||
|
edits.push({username: user.name, timestamp: Timestamp.now()})
|
||||||
|
await this.songDataService.update$(songId, {...data, edits});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async new(number: number, title: string): Promise<string> {
|
public async new(number: number, title: string): Promise<string> {
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import Timestamp = firebase.firestore.Timestamp;
|
||||||
|
import * as firebase from 'firebase';
|
||||||
|
|
||||||
export interface Song {
|
export interface Song {
|
||||||
id: string;
|
id: string;
|
||||||
comment: string;
|
comment: string;
|
||||||
@@ -19,4 +22,11 @@ export interface Song {
|
|||||||
label: string;
|
label: string;
|
||||||
termsOfUse: string;
|
termsOfUse: string;
|
||||||
origin: string;
|
origin: string;
|
||||||
|
|
||||||
|
edits: Edit[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Edit {
|
||||||
|
username: string;
|
||||||
|
timestamp: Timestamp;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Cool bridge without any chords
|
|||||||
|
|
||||||
it('should parse section types', () => {
|
it('should parse section types', () => {
|
||||||
const service: TextRenderingService = TestBed.get(TextRenderingService);
|
const service: TextRenderingService = TestBed.get(TextRenderingService);
|
||||||
const sections = service.parse(testText);
|
const sections = service.parse(testText, null);
|
||||||
expect(sections[0].type).toBe(SectionType.Verse);
|
expect(sections[0].type).toBe(SectionType.Verse);
|
||||||
expect(sections[0].number).toBe(0);
|
expect(sections[0].number).toBe(0);
|
||||||
expect(sections[1].type).toBe(SectionType.Verse);
|
expect(sections[1].type).toBe(SectionType.Verse);
|
||||||
@@ -46,7 +46,7 @@ Cool bridge without any chords
|
|||||||
|
|
||||||
it('should parse text lines', () => {
|
it('should parse text lines', () => {
|
||||||
const service: TextRenderingService = TestBed.get(TextRenderingService);
|
const service: TextRenderingService = TestBed.get(TextRenderingService);
|
||||||
const sections = service.parse(testText);
|
const sections = service.parse(testText, null);
|
||||||
expect(sections[0].lines[1].type).toBe(LineType.text);
|
expect(sections[0].lines[1].type).toBe(LineType.text);
|
||||||
expect(sections[0].lines[1].text).toBe('Text Line 1-1');
|
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].type).toBe(LineType.text);
|
||||||
@@ -63,7 +63,7 @@ Cool bridge without any chords
|
|||||||
|
|
||||||
it('should parse chord lines', () => {
|
it('should parse chord lines', () => {
|
||||||
const service: TextRenderingService = TestBed.inject(TextRenderingService);
|
const service: TextRenderingService = TestBed.inject(TextRenderingService);
|
||||||
const sections = service.parse(testText);
|
const sections = service.parse(testText, null);
|
||||||
expect(sections[0].lines[0].type).toBe(LineType.chord);
|
expect(sections[0].lines[0].type).toBe(LineType.chord);
|
||||||
expect(sections[0].lines[0].text).toBe('C D E F G A H');
|
expect(sections[0].lines[0].text).toBe('C D E F G A H');
|
||||||
expect(sections[0].lines[2].type).toBe(LineType.chord);
|
expect(sections[0].lines[2].type).toBe(LineType.chord);
|
||||||
@@ -91,7 +91,7 @@ Cool bridge without any chords
|
|||||||
const text = `Strophe
|
const text = `Strophe
|
||||||
g# F# E g# F# E
|
g# F# E g# F# E
|
||||||
text`
|
text`
|
||||||
const sections = service.parse(text);
|
const sections = service.parse(text, null);
|
||||||
expect(sections[0].lines[0].type).toBe(LineType.chord);
|
expect(sections[0].lines[0].type).toBe(LineType.chord);
|
||||||
expect(sections[0].lines[0].text).toBe('g# F# E g# F# E');
|
expect(sections[0].lines[0].text).toBe('g# F# E g# F# E');
|
||||||
expect(sections[0].lines[1].type).toBe(LineType.text);
|
expect(sections[0].lines[1].type).toBe(LineType.text);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
|
import {TransposeMode, TransposeService} from './transpose.service';
|
||||||
|
|
||||||
export enum SectionType {
|
export enum SectionType {
|
||||||
Verse,
|
Verse,
|
||||||
@@ -22,7 +23,7 @@ export interface Chord {
|
|||||||
export interface Line {
|
export interface Line {
|
||||||
type: LineType;
|
type: LineType;
|
||||||
text: string;
|
text: string;
|
||||||
chords?: Chord[]
|
chords?: Chord[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Section {
|
export interface Section {
|
||||||
@@ -37,10 +38,10 @@ export interface Section {
|
|||||||
export class TextRenderingService {
|
export class TextRenderingService {
|
||||||
private regexSection = /(Strophe|Refrain|Bridge)/;
|
private regexSection = /(Strophe|Refrain|Bridge)/;
|
||||||
|
|
||||||
constructor() {
|
constructor(private transposeService: TransposeService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public parse(text: string): Section[] {
|
public parse(text: string, transpose: TransposeMode): Section[] {
|
||||||
if (!text) return [];
|
if (!text) return [];
|
||||||
const arrayOfLines = text.split(/\r?\n/).filter(_ => _);
|
const arrayOfLines = text.split(/\r?\n/).filter(_ => _);
|
||||||
const indices = {
|
const indices = {
|
||||||
@@ -55,18 +56,19 @@ export class TextRenderingService {
|
|||||||
number: indices[type]++,
|
number: indices[type]++,
|
||||||
lines: []
|
lines: []
|
||||||
}];
|
}];
|
||||||
array[array.length - 1].lines.push(this.getLineOfLineText(line));
|
array[array.length - 1].lines.push(this.getLineOfLineText(line, transpose));
|
||||||
return array;
|
return array;
|
||||||
}, [] as Section[]);
|
}, [] as Section[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getLineOfLineText(text: string): Line {
|
private getLineOfLineText(text: string, transpose: TransposeMode): Line {
|
||||||
if (!text) return null;
|
if (!text) return null;
|
||||||
const cords = this.readChords(text);
|
const cords = this.readChords(text);
|
||||||
const hasMatches = cords.length > 0;
|
const hasMatches = cords.length > 0;
|
||||||
const type = hasMatches ? LineType.chord : LineType.text;
|
const type = hasMatches ? LineType.chord : LineType.text;
|
||||||
|
|
||||||
return {type, text, chords: hasMatches ? cords : undefined}
|
const line = {type, text, chords: hasMatches ? cords : undefined};
|
||||||
|
return transpose ? this.transposeService.transpose(line, transpose.baseKey, transpose.targetKey) : line;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSectionTypeOfLine(line: string): SectionType {
|
private getSectionTypeOfLine(line: string): SectionType {
|
||||||
|
|||||||
20
src/app/modules/songs/services/transpose.service.spec.ts
Normal file
20
src/app/modules/songs/services/transpose.service.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import {TestBed} from '@angular/core/testing';
|
||||||
|
|
||||||
|
import {TransposeService} from './transpose.service';
|
||||||
|
|
||||||
|
describe('TransposeService', () => {
|
||||||
|
let service: TransposeService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(TransposeService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create map', () => {
|
||||||
|
const distance = service.getDistance('D', 'G');
|
||||||
|
const map = service.getMap('D', distance);
|
||||||
|
|
||||||
|
console.log(map);
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
80
src/app/modules/songs/services/transpose.service.ts
Normal file
80
src/app/modules/songs/services/transpose.service.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {Chord, Line, LineType} from './text-rendering.service';
|
||||||
|
import {getScaleType, scaleMapping} from './key.helper';
|
||||||
|
|
||||||
|
export interface TransposeMode {
|
||||||
|
baseKey: string;
|
||||||
|
targetKey: string
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class TransposeService {
|
||||||
|
|
||||||
|
public transpose(line: Line, baseKey: string, targetKey: string): Line {
|
||||||
|
if (line.type !== LineType.chord) return line;
|
||||||
|
const difference = this.getDistance(baseKey, targetKey);
|
||||||
|
const map = this.getMap(baseKey, difference);
|
||||||
|
|
||||||
|
const chords = line.chords.map(chord => this.transposeChord(chord, map));
|
||||||
|
const renderedLine = this.renderLine(chords);
|
||||||
|
|
||||||
|
return {...line, text: renderedLine, chords};
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDistance(baseKey: string, targetKey: string): number {
|
||||||
|
const scale = getScaleType(baseKey);
|
||||||
|
return (
|
||||||
|
(scale[0].indexOf(targetKey) - scale[0].indexOf(baseKey)) ??
|
||||||
|
(scale[1].indexOf(targetKey) - scale[1].indexOf(baseKey))
|
||||||
|
) % 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getMap(baseKey: string, difference: number) {
|
||||||
|
const scale = getScaleType(baseKey);
|
||||||
|
const map = {};
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
const source = scale[0][i];
|
||||||
|
const mappedIndex = (i + difference) % 12;
|
||||||
|
map[source] = scale[0][mappedIndex];
|
||||||
|
}
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
const source = scale[1][i];
|
||||||
|
const mappedIndex = (i + difference) % 12;
|
||||||
|
map[source] = scale[1][mappedIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private transposeChord(chord: Chord, map: {}): Chord {
|
||||||
|
const translatedChord = map[chord.chord];
|
||||||
|
const translatedSlashChord = chord.slashChord ? map[chord.slashChord] : null;
|
||||||
|
return {...chord, chord: translatedChord, slashChord: translatedSlashChord};
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderLine(chords: Chord[]): string {
|
||||||
|
let template = ' ';
|
||||||
|
|
||||||
|
chords.forEach(chord => {
|
||||||
|
const pos = chord.position;
|
||||||
|
const renderedChord = this.renderChord(chord);
|
||||||
|
const newLength = renderedChord.length;
|
||||||
|
|
||||||
|
const pre = template.substr(0, pos);
|
||||||
|
const post = template.substr(pos + newLength);
|
||||||
|
|
||||||
|
template = pre + renderedChord + post;
|
||||||
|
})
|
||||||
|
|
||||||
|
return template.trimRight();
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderChord(chord: Chord) {
|
||||||
|
return (
|
||||||
|
scaleMapping[chord.chord] +
|
||||||
|
(chord.add ? chord.add : '') +
|
||||||
|
(chord.slashChord ? '/' + scaleMapping[chord.slashChord] : ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<div>
|
<div>
|
||||||
<app-edit-song></app-edit-song>
|
<app-edit-song></app-edit-song>
|
||||||
<app-edit-file></app-edit-file>
|
<app-edit-file></app-edit-file>
|
||||||
|
<app-history></app-history>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,10 +24,11 @@ import {ButtonModule} from '../../../../widget-modules/components/button/button.
|
|||||||
import {MatTooltipModule} from '@angular/material/tooltip';
|
import {MatTooltipModule} from '@angular/material/tooltip';
|
||||||
import {SaveDialogComponent} from './edit-song/save-dialog/save-dialog.component';
|
import {SaveDialogComponent} from './edit-song/save-dialog/save-dialog.component';
|
||||||
import {MatDialogModule} from '@angular/material/dialog';
|
import {MatDialogModule} from '@angular/material/dialog';
|
||||||
|
import {HistoryComponent} from './history/history.component';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [EditComponent, EditSongComponent, EditFileComponent, FileComponent, SaveDialogComponent],
|
declarations: [EditComponent, EditSongComponent, EditFileComponent, FileComponent, SaveDialogComponent, HistoryComponent],
|
||||||
exports: [EditComponent],
|
exports: [EditComponent],
|
||||||
bootstrap: [SaveDialogComponent],
|
bootstrap: [SaveDialogComponent],
|
||||||
imports: [
|
imports: [
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<app-card *ngIf="song && song.edits" heading="letzte Änderungen">
|
||||||
|
<div *ngFor="let edit of song.edits" class="list">
|
||||||
|
<div>{{edit.username}}</div>
|
||||||
|
<div>{{edit.timestamp.toDate()|date:'dd.MM.yyyy'}}</div>
|
||||||
|
</div>
|
||||||
|
</app-card>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
.list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
|
||||||
|
import {HistoryComponent} from './history.component';
|
||||||
|
|
||||||
|
describe('HistoryComponent', () => {
|
||||||
|
let component: HistoryComponent;
|
||||||
|
let fixture: ComponentFixture<HistoryComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [HistoryComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(HistoryComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
31
src/app/modules/songs/song/edit/history/history.component.ts
Normal file
31
src/app/modules/songs/song/edit/history/history.component.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {first, map, switchMap} from 'rxjs/operators';
|
||||||
|
import {ActivatedRoute} from '@angular/router';
|
||||||
|
import {SongService} from '../../../services/song.service';
|
||||||
|
import {Song} from '../../../services/song';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-history',
|
||||||
|
templateUrl: './history.component.html',
|
||||||
|
styleUrls: ['./history.component.less']
|
||||||
|
})
|
||||||
|
export class HistoryComponent implements OnInit {
|
||||||
|
public song: Song;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private activatedRoute: ActivatedRoute,
|
||||||
|
private songService: SongService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ngOnInit(): void {
|
||||||
|
this.activatedRoute.params.pipe(
|
||||||
|
map(param => param.songId),
|
||||||
|
switchMap(songId => this.songService.read$(songId)),
|
||||||
|
first()
|
||||||
|
).subscribe(song => {
|
||||||
|
this.song = song;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,10 @@ export class UserService {
|
|||||||
return this._user$.pipe(filter(_ => !!_));
|
return this._user$.pipe(filter(_ => !!_));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async currentUser(): Promise<User> {
|
||||||
|
return this.user$.pipe(first()).toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
public getUserbyId(userId: string): Promise<User> {
|
public getUserbyId(userId: string): Promise<User> {
|
||||||
return this.getUserbyId$(userId).pipe(first()).toPromise();
|
return this.getUserbyId$(userId).pipe(first()).toPromise();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
} from '../../../modules/songs/services/text-rendering.service';
|
} from '../../../modules/songs/services/text-rendering.service';
|
||||||
import {faGripLines} from '@fortawesome/free-solid-svg-icons/faGripLines';
|
import {faGripLines} from '@fortawesome/free-solid-svg-icons/faGripLines';
|
||||||
import {songSwitch} from './animation';
|
import {songSwitch} from './animation';
|
||||||
|
import {TransposeMode} from '../../../modules/songs/services/transpose.service';
|
||||||
|
|
||||||
export type ChordMode = 'show' | 'hide' | 'onlyFirst'
|
export type ChordMode = 'show' | 'hide' | 'onlyFirst'
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ export class SongTextComponent implements OnInit {
|
|||||||
@Input() public index = -1;
|
@Input() public index = -1;
|
||||||
@Input() public fullscreen = false;
|
@Input() public fullscreen = false;
|
||||||
@Input() public showSwitch = false;
|
@Input() public showSwitch = false;
|
||||||
|
@Input() public transpose: TransposeMode = null;
|
||||||
@Output() public chordModeChanged = new EventEmitter<ChordMode>();
|
@Output() public chordModeChanged = new EventEmitter<ChordMode>();
|
||||||
@ViewChildren('section') viewSections: QueryList<ElementRef>;
|
@ViewChildren('section') viewSections: QueryList<ElementRef>;
|
||||||
public faLines = faGripLines;
|
public faLines = faGripLines;
|
||||||
@@ -42,7 +44,7 @@ export class SongTextComponent implements OnInit {
|
|||||||
this.sections = null;
|
this.sections = null;
|
||||||
this.offset = 0;
|
this.offset = 0;
|
||||||
setTimeout(() =>
|
setTimeout(() =>
|
||||||
this.sections = this.textRenderingService.parse(value).sort((a, b) => a.type - b.type), 100);
|
this.sections = this.textRenderingService.parse(value, this.transpose).sort((a, b) => a.type - b.type), 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user