update tslint -> eslint
This commit is contained in:
@@ -3,10 +3,10 @@ import {TestBed} from '@angular/core/testing';
|
||||
import {FileDataService} from './file-data.service';
|
||||
|
||||
describe('FileDataService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
beforeEach(() => void TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: FileDataService = TestBed.get(FileDataService);
|
||||
expect(service).toBeTruthy();
|
||||
const service: FileDataService = TestBed.inject(FileDataService);
|
||||
void expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,12 +5,10 @@ import {FileServer} from './fileServer';
|
||||
import {DbService} from '../../../services/db.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class FileDataService {
|
||||
|
||||
constructor(private db: DbService) {
|
||||
}
|
||||
public constructor(private db: DbService) {}
|
||||
|
||||
public async set(songId: string, file: FileServer): Promise<string> {
|
||||
const songRef = this.db.doc('songs/' + songId);
|
||||
@@ -27,8 +25,5 @@ export class FileDataService {
|
||||
public read$(songId: string): Observable<File[]> {
|
||||
const songRef = this.db.doc('songs/' + songId);
|
||||
return songRef.collection<File>('files').valueChanges({idField: 'id'});
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('FileService', () => {
|
||||
let service: FileService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
void TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(FileService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
void expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,22 +4,17 @@ import {Observable} from 'rxjs';
|
||||
import {FileDataService} from './file-data.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class FileService {
|
||||
|
||||
constructor(
|
||||
private storage: AngularFireStorage,
|
||||
private fileDataService: FileDataService
|
||||
) {
|
||||
}
|
||||
public constructor(private storage: AngularFireStorage, private fileDataService: FileDataService) {}
|
||||
|
||||
public getDownloadUrl(path: string): Observable<string> {
|
||||
const ref = this.storage.ref(path);
|
||||
return ref.getDownloadURL();
|
||||
return ref.getDownloadURL() as Observable<string>;
|
||||
}
|
||||
|
||||
public async delete(path: string, songId: string, fileId: string) {
|
||||
public async delete(path: string, songId: string, fileId: string): Promise<void> {
|
||||
const ref = this.storage.ref(path);
|
||||
await ref.delete().toPromise();
|
||||
await this.fileDataService.delete(songId, fileId);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export class FileBase {
|
||||
|
||||
protected basePath = '/attachments';
|
||||
protected directory = (songId: string) => `${this.basePath}/${songId}`;
|
||||
protected directory: (songId: string) => string = (songId: string) => `${this.basePath}/${songId}`;
|
||||
}
|
||||
|
||||
@@ -1,133 +1,156 @@
|
||||
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',
|
||||
export const KEYS: string[] = [
|
||||
'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: string[] = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'H'];
|
||||
export const KEYS_MAJOR_B: string[] = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'B', 'H'];
|
||||
export const KEYS_MINOR_FLAT: string[] = ['c', 'c#', 'd', 'd#', 'e', 'f', 'f#', 'g', 'g#', 'a', 'a#', 'h'];
|
||||
export const KEYS_MINOR_B: string[] = ['c', 'db', 'd', 'eb', 'e', 'f', 'gb', 'g', 'ab', 'a', 'b', 'h'];
|
||||
|
||||
export type scale = 'b' | 'flat'
|
||||
export type scale = 'b' | 'flat';
|
||||
|
||||
const scaleTypeAssignment: { [key: string]: string[][] } = {
|
||||
'C': [KEYS_MAJOR_FLAT, KEYS_MINOR_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],
|
||||
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],
|
||||
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],
|
||||
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],
|
||||
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],
|
||||
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],
|
||||
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],
|
||||
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],
|
||||
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],
|
||||
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 = {
|
||||
'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,
|
||||
b: [KEYS_MINOR_B, KEYS_MAJOR_B],
|
||||
h: [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
|
||||
};
|
||||
|
||||
export const scaleMapping = {
|
||||
'C': 'C',
|
||||
const scaleAssignment: {[key: string]: string[]} = {
|
||||
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: {[key: string]: string} = {
|
||||
C: 'C',
|
||||
'C#': 'C♯',
|
||||
'Db': 'D♭',
|
||||
'D': 'D',
|
||||
Db: 'D♭',
|
||||
D: 'D',
|
||||
'D#': 'D♯',
|
||||
'Eb': 'E♭',
|
||||
'E': 'E',
|
||||
'F': 'F',
|
||||
Eb: 'E♭',
|
||||
E: 'E',
|
||||
F: 'F',
|
||||
'F#': 'F♯',
|
||||
'Gb': 'D♭',
|
||||
'G': 'G',
|
||||
Gb: 'D♭',
|
||||
G: 'G',
|
||||
'G#': 'G♯',
|
||||
'Ab': 'A♭',
|
||||
'A': 'A',
|
||||
Ab: 'A♭',
|
||||
A: 'A',
|
||||
'A#': 'A♯',
|
||||
'B': 'B',
|
||||
'H': 'H',
|
||||
'c': 'c',
|
||||
B: 'B',
|
||||
H: 'H',
|
||||
c: 'c',
|
||||
'c#': 'c♯',
|
||||
'db': 'd♭',
|
||||
'd': 'd',
|
||||
db: 'd♭',
|
||||
d: 'd',
|
||||
'd#': 'd♯',
|
||||
'eb': 'e♭',
|
||||
'e': 'e',
|
||||
'f': 'f',
|
||||
eb: 'e♭',
|
||||
e: 'e',
|
||||
f: 'f',
|
||||
'f#': 'f♯',
|
||||
'gb': 'g♭',
|
||||
'g': 'g',
|
||||
gb: 'g♭',
|
||||
g: 'g',
|
||||
'g#': 'g♯',
|
||||
'ab': 'a♭',
|
||||
'a': 'a',
|
||||
ab: 'a♭',
|
||||
a: 'a',
|
||||
'a#': 'a♯',
|
||||
'b': 'b',
|
||||
'h': 'h',
|
||||
b: 'b',
|
||||
h: 'h',
|
||||
};
|
||||
|
||||
export const getScale = (key: string): string[] => scaleAssignment[key];
|
||||
export const getScaleType = (key: string): string[][] => scaleTypeAssignment[key];
|
||||
|
||||
|
||||
@@ -5,37 +5,35 @@ import {AngularFirestore} from '@angular/fire/firestore';
|
||||
import {of} from 'rxjs';
|
||||
|
||||
describe('SongDataService', () => {
|
||||
|
||||
const songs = [
|
||||
{title: 'title1'}
|
||||
];
|
||||
const songs = [{title: 'title1'}];
|
||||
|
||||
const angularFirestoreCollection = {
|
||||
valueChanges: () => of(songs)
|
||||
valueChanges: () => of(songs),
|
||||
};
|
||||
|
||||
const mockAngularFirestore = {
|
||||
collection: () => angularFirestoreCollection
|
||||
collection: () => angularFirestoreCollection,
|
||||
};
|
||||
|
||||
beforeEach(() => TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: AngularFirestore, useValue: mockAngularFirestore}
|
||||
]
|
||||
}));
|
||||
beforeEach(
|
||||
() =>
|
||||
void TestBed.configureTestingModule({
|
||||
providers: [{provide: AngularFirestore, useValue: mockAngularFirestore}],
|
||||
})
|
||||
);
|
||||
|
||||
it('should be created', () => {
|
||||
const service: SongDataService = TestBed.get(SongDataService);
|
||||
expect(service).toBeTruthy();
|
||||
const service: SongDataService = TestBed.inject(SongDataService);
|
||||
void expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should list songs', waitForAsync(() => {
|
||||
const service: SongDataService = TestBed.get(SongDataService);
|
||||
service.list$().subscribe(s => {
|
||||
expect(s).toEqual([
|
||||
{title: 'title1'}
|
||||
] as any);
|
||||
}
|
||||
);
|
||||
}));
|
||||
it(
|
||||
'should list songs',
|
||||
waitForAsync(() => {
|
||||
const service: SongDataService = TestBed.inject(SongDataService);
|
||||
service.list$().subscribe(s => {
|
||||
void expect(s[0].title).toEqual('title1');
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -4,19 +4,16 @@ import {Observable} from 'rxjs';
|
||||
import {DbService} from '../../../services/db.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class SongDataService {
|
||||
private collection = 'songs';
|
||||
|
||||
constructor(private dbService: DbService) {
|
||||
}
|
||||
public constructor(private dbService: DbService) {}
|
||||
|
||||
public list$ = (): Observable<Song[]> => this.dbService.col$(this.collection);
|
||||
public read$ = (songId: string): Observable<Song | undefined> => this.dbService.doc$(this.collection + '/' + songId);
|
||||
public update$ = async (songId: string, data: Partial<Song>): Promise<void> => await this.dbService.doc(this.collection + '/' + songId).update(data);
|
||||
public add = async (data: Partial<Song>): Promise<string> => (await this.dbService.col(this.collection).add(data)).id;
|
||||
public delete = async (songId: string): Promise<void> => await this.dbService.doc(this.collection + '/' + songId).delete();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -5,32 +5,31 @@ import {SongDataService} from './song-data.service';
|
||||
import {of} from 'rxjs';
|
||||
|
||||
describe('SongService', () => {
|
||||
|
||||
const songs = [
|
||||
{title: 'title1'}
|
||||
];
|
||||
const songs = [{title: 'title1'}];
|
||||
|
||||
const mockSongDataService = {
|
||||
list: () => of(songs)
|
||||
list: () => of(songs),
|
||||
};
|
||||
|
||||
beforeEach(() => TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: SongDataService, useValue: mockSongDataService}
|
||||
]
|
||||
}));
|
||||
beforeEach(
|
||||
() =>
|
||||
void TestBed.configureTestingModule({
|
||||
providers: [{provide: SongDataService, useValue: mockSongDataService}],
|
||||
})
|
||||
);
|
||||
|
||||
it('should be created', () => {
|
||||
const service: SongService = TestBed.get(SongService);
|
||||
expect(service).toBeTruthy();
|
||||
const service: SongService = TestBed.inject(SongService);
|
||||
void expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should list songs', waitForAsync(() => {
|
||||
const service: SongService = TestBed.get(SongService);
|
||||
service.list$().subscribe(s => {
|
||||
expect(s).toEqual([
|
||||
{title: 'title1'}
|
||||
] as any);
|
||||
});
|
||||
}));
|
||||
it(
|
||||
'should list songs',
|
||||
waitForAsync(() => {
|
||||
const service: SongService = TestBed.inject(SongService);
|
||||
service.list$().subscribe(s => {
|
||||
void expect(s[0].title).toEqual('title1');
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2,31 +2,30 @@ import {Injectable} from '@angular/core';
|
||||
import {Observable} from 'rxjs';
|
||||
import {Song} from './song';
|
||||
import {SongDataService} from './song-data.service';
|
||||
import {first, tap} from 'rxjs/operators';
|
||||
import {first} 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 let importCCLI: any;
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class SongService {
|
||||
|
||||
public static TYPES = ['Praise', 'Worship'];
|
||||
public static STATUS = ['draft', 'set', 'final'];
|
||||
|
||||
public static LEGAL_OWNER = ['CCLI', 'other'];
|
||||
public static LEGAL_TYPE = ['open', 'allowed'];
|
||||
|
||||
private list: Song[];
|
||||
// private list: Song[];
|
||||
|
||||
constructor(private songDataService: SongDataService, private userService: UserService) {
|
||||
importCCLI = (songs: Song[]) => this.updateFromCLI(songs);
|
||||
public constructor(private songDataService: SongDataService, private userService: UserService) {
|
||||
// importCCLI = (songs: Song[]) => this.updateFromCLI(songs);
|
||||
}
|
||||
|
||||
public list$ = (): Observable<Song[]> => this.songDataService.list$().pipe(tap(_ => this.list = _));
|
||||
public list$ = (): Observable<Song[]> => this.songDataService.list$(); //.pipe(tap(_ => (this.list = _)));
|
||||
public read$ = (songId: string): Observable<Song | undefined> => this.songDataService.read$(songId);
|
||||
public read = (songId: string): Promise<Song | undefined> => this.read$(songId).pipe(first()).toPromise();
|
||||
|
||||
@@ -38,38 +37,41 @@ export class SongService {
|
||||
await this.songDataService.update$(songId, {...data, edits});
|
||||
}
|
||||
|
||||
public async new(number: number, title: string): Promise<string> {
|
||||
return await this.songDataService.add({number, title, status: 'draft', legalType: 'open'});
|
||||
public async new(songNumber: number, title: string): Promise<string> {
|
||||
return await this.songDataService.add({
|
||||
number: songNumber,
|
||||
title,
|
||||
status: 'draft',
|
||||
legalType: 'open',
|
||||
});
|
||||
}
|
||||
|
||||
public async delete(songId: string): Promise<void> {
|
||||
await this.songDataService.delete(songId);
|
||||
}
|
||||
|
||||
// https://www.csvjson.com/csv2json
|
||||
private async updateFromCLI(songs: Song[]) {
|
||||
const mapped = songs.map(_ => ({
|
||||
number: _.number,
|
||||
legalType: _.legalType === 'ja' ? 'allowed' : 'open',
|
||||
legalOwner: _.legalOwner === 'ja' ? 'CCLI' : 'other',
|
||||
title: _.title,
|
||||
legalOwnerId: _.legalOwnerId,
|
||||
origin: _.origin,
|
||||
artist: _.artist,
|
||||
comment: _.comment
|
||||
}));
|
||||
const promises = this.list.map(async _ => {
|
||||
// tslint:disable-next-line:triple-equals
|
||||
const mappedSongs = mapped.filter(f => f.number == _.number);
|
||||
if (mappedSongs.length === 1) {
|
||||
const mappedSong = mappedSongs[0];
|
||||
const id = _.id;
|
||||
return await this.update$(id, mappedSong);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
|
||||
// https://www.csvjson.com/csv2json
|
||||
// private async updateFromCLI(songs: Song[]) {
|
||||
// const mapped = songs.map(_ => ({
|
||||
// number: _.number,
|
||||
// legalType: _.legalType === 'ja' ? 'allowed' : 'open',
|
||||
// legalOwner: _.legalOwner === 'ja' ? 'CCLI' : 'other',
|
||||
// title: _.title,
|
||||
// legalOwnerId: _.legalOwnerId,
|
||||
// origin: _.origin,
|
||||
// artist: _.artist,
|
||||
// comment: _.comment,
|
||||
// }));
|
||||
// const promises = this.list.map(async _ => {
|
||||
// // eslint-disable-next-line eqeqeq
|
||||
// const mappedSongs = mapped.filter(f => f.number == _.number);
|
||||
// if (mappedSongs.length === 1) {
|
||||
// const mappedSong = mappedSongs[0];
|
||||
// const id = _.id;
|
||||
// return await this.update$(id, mappedSong);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// await Promise.all(promises);
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
|
||||
import {LineType, SectionType, TextRenderingService} from './text-rendering.service';
|
||||
import {TextRenderingService} from './text-rendering.service';
|
||||
import {LineType} from './line-type';
|
||||
import {SectionType} from './section-type';
|
||||
|
||||
describe('TextRenderingService', () => {
|
||||
const testText = `Strophe
|
||||
@@ -23,60 +24,59 @@ Bridge
|
||||
Cool bridge without any chords
|
||||
`;
|
||||
|
||||
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
beforeEach(() => void TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: TextRenderingService = TestBed.get(TextRenderingService);
|
||||
expect(service).toBeTruthy();
|
||||
const service: TextRenderingService = TestBed.inject(TextRenderingService);
|
||||
void expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should parse section types', () => {
|
||||
const service: TextRenderingService = TestBed.get(TextRenderingService);
|
||||
const service: TextRenderingService = TestBed.inject(TextRenderingService);
|
||||
const sections = service.parse(testText, null);
|
||||
expect(sections[0].type).toBe(SectionType.Verse);
|
||||
expect(sections[0].number).toBe(0);
|
||||
expect(sections[1].type).toBe(SectionType.Verse);
|
||||
expect(sections[1].number).toBe(1);
|
||||
expect(sections[2].type).toBe(SectionType.Chorus);
|
||||
expect(sections[2].number).toBe(0);
|
||||
expect(sections[3].type).toBe(SectionType.Bridge);
|
||||
expect(sections[3].number).toBe(0);
|
||||
void expect(sections[0].type).toBe(SectionType.Verse);
|
||||
void expect(sections[0].number).toBe(0);
|
||||
void expect(sections[1].type).toBe(SectionType.Verse);
|
||||
void expect(sections[1].number).toBe(1);
|
||||
void expect(sections[2].type).toBe(SectionType.Chorus);
|
||||
void expect(sections[2].number).toBe(0);
|
||||
void expect(sections[3].type).toBe(SectionType.Bridge);
|
||||
void expect(sections[3].number).toBe(0);
|
||||
});
|
||||
|
||||
it('should parse text lines', () => {
|
||||
const service: TextRenderingService = TestBed.get(TextRenderingService);
|
||||
const service: TextRenderingService = TestBed.inject(TextRenderingService);
|
||||
const sections = service.parse(testText, null);
|
||||
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');
|
||||
void expect(sections[0].lines[1].type).toBe(LineType.text);
|
||||
void expect(sections[0].lines[1].text).toBe('Text Line 1-1');
|
||||
void expect(sections[0].lines[3].type).toBe(LineType.text);
|
||||
void expect(sections[0].lines[3].text).toBe('Text Line 2-1');
|
||||
void expect(sections[1].lines[1].type).toBe(LineType.text);
|
||||
void expect(sections[1].lines[1].text).toBe('Text Line 1-2');
|
||||
void expect(sections[1].lines[3].type).toBe(LineType.text);
|
||||
void expect(sections[1].lines[3].text).toBe('Text Line 2-2');
|
||||
void expect(sections[2].lines[1].type).toBe(LineType.text);
|
||||
void expect(sections[2].lines[1].text).toBe('and the chorus');
|
||||
void expect(sections[3].lines[0].type).toBe(LineType.text);
|
||||
void expect(sections[3].lines[0].text).toBe('Cool bridge without any chords');
|
||||
});
|
||||
|
||||
it('should parse chord lines', () => {
|
||||
const service: TextRenderingService = TestBed.inject(TextRenderingService);
|
||||
const sections = service.parse(testText, null);
|
||||
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[2].type).toBe(LineType.chord);
|
||||
expect(sections[0].lines[2].text).toBe(' a d e f g a h c b');
|
||||
expect(sections[1].lines[0].type).toBe(LineType.chord);
|
||||
expect(sections[1].lines[0].text).toBe('C D E F G A H');
|
||||
expect(sections[1].lines[2].type).toBe(LineType.chord);
|
||||
expect(sections[1].lines[2].text).toBe(' a d e f g a h c b');
|
||||
expect(sections[2].lines[0].type).toBe(LineType.chord);
|
||||
expect(sections[2].lines[0].text).toBe('c c# db c7 cmaj7 c/e');
|
||||
void expect(sections[0].lines[0].type).toBe(LineType.chord);
|
||||
void expect(sections[0].lines[0].text).toBe('C D E F G A H');
|
||||
void expect(sections[0].lines[2].type).toBe(LineType.chord);
|
||||
void expect(sections[0].lines[2].text).toBe(' a d e f g a h c b');
|
||||
void expect(sections[1].lines[0].type).toBe(LineType.chord);
|
||||
void expect(sections[1].lines[0].text).toBe('C D E F G A H');
|
||||
void expect(sections[1].lines[2].type).toBe(LineType.chord);
|
||||
void expect(sections[1].lines[2].text).toBe(' a d e f g a h c b');
|
||||
void expect(sections[2].lines[0].type).toBe(LineType.chord);
|
||||
void expect(sections[2].lines[0].text).toBe('c c# db c7 cmaj7 c/e');
|
||||
|
||||
// c c# db c7 cmaj7 c/e
|
||||
expect(sections[2].lines[0].chords).toEqual([
|
||||
void expect(sections[2].lines[0].chords).toEqual([
|
||||
{chord: 'c', length: 1, position: 0},
|
||||
{chord: 'c#', length: 2, position: 2},
|
||||
{chord: 'db', length: 2, position: 5},
|
||||
@@ -92,10 +92,9 @@ Cool bridge without any chords
|
||||
g# F# E g# F# E
|
||||
text`;
|
||||
const sections = service.parse(text, null);
|
||||
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[1].type).toBe(LineType.text);
|
||||
expect(sections[0].lines[1].text).toBe('text');
|
||||
|
||||
void expect(sections[0].lines[0].type).toBe(LineType.chord);
|
||||
void expect(sections[0].lines[0].text).toBe('g# F# E g# F# E');
|
||||
void expect(sections[0].lines[1].type).toBe(LineType.text);
|
||||
void expect(sections[0].lines[1].text).toBe('text');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,13 +8,12 @@ import {Chord} from './chord';
|
||||
import {Line} from './line';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class TextRenderingService {
|
||||
private regexSection = /(Strophe|Refrain|Bridge)/;
|
||||
|
||||
constructor(private transposeService: TransposeService) {
|
||||
}
|
||||
public constructor(private transposeService: TransposeService) {}
|
||||
|
||||
public parse(text: string, transpose: TransposeMode): Section[] {
|
||||
if (!text) {
|
||||
@@ -28,12 +27,15 @@ export class TextRenderingService {
|
||||
};
|
||||
return arrayOfLines.reduce((array, line) => {
|
||||
const type = this.getSectionTypeOfLine(line);
|
||||
if (line.match(this.regexSection)) {
|
||||
return [...array, {
|
||||
type,
|
||||
number: indices[type]++,
|
||||
lines: []
|
||||
}];
|
||||
if (this.regexSection.exec(line)) {
|
||||
return [
|
||||
...array,
|
||||
{
|
||||
type,
|
||||
number: indices[type]++,
|
||||
lines: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
array[array.length - 1].lines.push(this.getLineOfLineText(line, transpose));
|
||||
return array;
|
||||
@@ -49,36 +51,35 @@ export class TextRenderingService {
|
||||
const type = hasMatches ? LineType.chord : LineType.text;
|
||||
|
||||
const line = {type, text, chords: hasMatches ? cords : undefined};
|
||||
return transpose
|
||||
? this.transposeService.transpose(line, transpose.baseKey, transpose.targetKey)
|
||||
: this.transposeService.renderChords(line);
|
||||
return transpose ? this.transposeService.transpose(line, transpose.baseKey, transpose.targetKey) : this.transposeService.renderChords(line);
|
||||
}
|
||||
|
||||
private getSectionTypeOfLine(line: string): SectionType {
|
||||
if (!line) {
|
||||
return null;
|
||||
}
|
||||
const match = line.match(this.regexSection);
|
||||
const match = this.regexSection.exec(line);
|
||||
if (!match || match.length < 2) {
|
||||
return null;
|
||||
}
|
||||
const typeString = match[1];
|
||||
switch (typeString) {
|
||||
case 'Strophe':
|
||||
case 'Strophe':
|
||||
return SectionType.Verse;
|
||||
case 'Refrain':
|
||||
case 'Refrain':
|
||||
return SectionType.Chorus;
|
||||
case 'Bridge':
|
||||
case 'Bridge':
|
||||
return SectionType.Bridge;
|
||||
}
|
||||
}
|
||||
|
||||
private readChords(chordLine: string): Chord[] {
|
||||
let match;
|
||||
let match: string[];
|
||||
const chords: Chord[] = [];
|
||||
|
||||
// https://regex101.com/r/68jMB8/5
|
||||
const regex = /(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))?(\d+|maj7)?/mg;
|
||||
const regex =
|
||||
/(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))?(\d+|maj7)?/gm;
|
||||
|
||||
while ((match = regex.exec(chordLine)) !== null) {
|
||||
const chord: Chord = {
|
||||
@@ -101,5 +102,4 @@ export class TextRenderingService {
|
||||
const isChrod = chordCount * 1.2 > lineCount;
|
||||
return isChrod ? chords : [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ describe('TransposeService', () => {
|
||||
let service: TransposeService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
void TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(TransposeService);
|
||||
});
|
||||
|
||||
@@ -15,6 +15,6 @@ describe('TransposeService', () => {
|
||||
const map = service.getMap('D', distance);
|
||||
|
||||
console.log(map);
|
||||
expect(service).toBeTruthy();
|
||||
void expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,11 +4,12 @@ import {LineType} from './line-type';
|
||||
import {Chord} from './chord';
|
||||
import {Line} from './line';
|
||||
|
||||
type TransposeMap = {[key: string]: string};
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class TransposeService {
|
||||
|
||||
public transpose(line: Line, baseKey: string, targetKey: string): Line {
|
||||
if (line.type !== LineType.chord) {
|
||||
return line;
|
||||
@@ -33,13 +34,10 @@ export class TransposeService {
|
||||
|
||||
public getDistance(baseKey: string, targetKey: string): number {
|
||||
const scale = getScaleType(baseKey);
|
||||
return scale ? (
|
||||
(scale[0].indexOf(targetKey) - scale[0].indexOf(baseKey)) ??
|
||||
(scale[1].indexOf(targetKey) - scale[1].indexOf(baseKey))
|
||||
) % 12 : 0;
|
||||
return scale ? (scale[0].indexOf(targetKey) - scale[0].indexOf(baseKey) ?? scale[1].indexOf(targetKey) - scale[1].indexOf(baseKey)) % 12 : 0;
|
||||
}
|
||||
|
||||
public getMap(baseKey: string, difference: number) {
|
||||
public getMap(baseKey: string, difference: number): TransposeMap | null {
|
||||
const scale = getScaleType(baseKey);
|
||||
if (!scale) {
|
||||
return null;
|
||||
@@ -59,10 +57,14 @@ export class TransposeService {
|
||||
return map;
|
||||
}
|
||||
|
||||
private transposeChord(chord: Chord, map: {}): Chord {
|
||||
private transposeChord(chord: Chord, map: TransposeMap): Chord {
|
||||
const translatedChord = map[chord.chord];
|
||||
const translatedSlashChord = chord.slashChord ? map[chord.slashChord] : null;
|
||||
return {...chord, chord: translatedChord, slashChord: translatedSlashChord};
|
||||
return {
|
||||
...chord,
|
||||
chord: translatedChord,
|
||||
slashChord: translatedSlashChord,
|
||||
};
|
||||
}
|
||||
|
||||
private renderLine(chords: Chord[]): string {
|
||||
@@ -83,9 +85,6 @@ export class TransposeService {
|
||||
}
|
||||
|
||||
private renderChord(chord: Chord) {
|
||||
return (
|
||||
scaleMapping[chord.chord] +
|
||||
(chord.add ? chord.add : '') +
|
||||
(chord.slashChord ? '/' + scaleMapping[chord.slashChord] : ''));
|
||||
return scaleMapping[chord.chord] + (chord.add ? chord.add : '') + (chord.slashChord ? '/' + scaleMapping[chord.slashChord] : '');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ import {TestBed} from '@angular/core/testing';
|
||||
import {UploadService} from './upload.service';
|
||||
|
||||
describe('UploadServiceService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
beforeEach(() => void TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: UploadService = TestBed.get(UploadService);
|
||||
expect(service).toBeTruthy();
|
||||
const service: UploadService = TestBed.inject(UploadService);
|
||||
void expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,17 +6,15 @@ import {finalize} from 'rxjs/operators';
|
||||
import {FileBase} from './fileBase';
|
||||
import {FileServer} from './fileServer';
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class UploadService extends FileBase {
|
||||
|
||||
constructor(private fileDataService: FileDataService, private angularFireStorage: AngularFireStorage) {
|
||||
public constructor(private fileDataService: FileDataService, private angularFireStorage: AngularFireStorage) {
|
||||
super();
|
||||
}
|
||||
|
||||
public async pushUpload(songId: string, upload: Upload) {
|
||||
public pushUpload(songId: string, upload: Upload): void {
|
||||
const directory = this.directory(songId);
|
||||
const filePath = `${directory}/${upload.file.name}`;
|
||||
upload.path = directory;
|
||||
@@ -24,20 +22,18 @@ export class UploadService extends FileBase {
|
||||
const ref = this.angularFireStorage.ref(filePath);
|
||||
const task = ref.put(upload.file);
|
||||
|
||||
task.percentageChanges().subscribe(percent => upload.progress = percent);
|
||||
task.snapshotChanges().pipe(
|
||||
finalize(() => {
|
||||
this.saveFileData(songId, upload);
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
task.percentageChanges().subscribe(percent => (upload.progress = percent));
|
||||
task
|
||||
.snapshotChanges()
|
||||
.pipe(finalize(() => void this.saveFileData(songId, upload)))
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private async saveFileData(songId: string, upload: Upload) {
|
||||
const file: FileServer = {
|
||||
name: upload.file.name,
|
||||
path: upload.path,
|
||||
createdAt: new Date()
|
||||
createdAt: new Date(),
|
||||
};
|
||||
await this.fileDataService.set(songId, file);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
export class Upload {
|
||||
public $key: string;
|
||||
public file: File;
|
||||
public name: string;
|
||||
public path: string;
|
||||
public progress: number;
|
||||
public createdAt: Date = new Date();
|
||||
|
||||
$key: string;
|
||||
file: Upload;
|
||||
name: string;
|
||||
path: string;
|
||||
progress: number;
|
||||
createdAt: Date = new Date();
|
||||
|
||||
constructor(file: Upload) {
|
||||
public constructor(file: File) {
|
||||
this.file = file;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<div [formGroup]="filterFormGroup">
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Titel oder Text</mat-label>
|
||||
<input formControlName="q" matInput>
|
||||
<input formControlName="q" matInput/>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="third">
|
||||
@@ -10,16 +9,19 @@
|
||||
<mat-label>Typ</mat-label>
|
||||
<mat-select formControlName="type">
|
||||
<mat-option [value]="null">- kein Filter -</mat-option>
|
||||
<mat-option *ngFor="let type of types" [value]="type">{{type | songType}}</mat-option>
|
||||
<mat-option *ngFor="let type of types" [value]="type">{{
|
||||
type | songType
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Tonart</mat-label>
|
||||
<mat-select formControlName="key">
|
||||
<mat-option [value]="null">- kein Filter -</mat-option>
|
||||
<mat-option *ngFor="let key of keys" [value]="key">{{key|key}}</mat-option>
|
||||
<mat-option *ngFor="let key of keys" [value]="key">{{
|
||||
key | key
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
@@ -27,7 +29,9 @@
|
||||
<mat-label>Rechtlicher Status</mat-label>
|
||||
<mat-select formControlName="legalType">
|
||||
<mat-option [value]="null">- kein Filter -</mat-option>
|
||||
<mat-option *ngFor="let key of legalType" [value]="key">{{key|legalType}}</mat-option>
|
||||
<mat-option *ngFor="let key of legalType" [value]="key">{{
|
||||
key | legalType
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
@@ -35,10 +39,12 @@
|
||||
<mat-label>Attribute</mat-label>
|
||||
<mat-select formControlName="flag">
|
||||
<mat-option [value]="null">- kein Filter -</mat-option>
|
||||
<mat-option *ngFor="let flag of getFlags()" [value]="flag">{{flag}}</mat-option>
|
||||
<mat-option *ngFor="let flag of getFlags()" [value]="flag">{{
|
||||
flag
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<i>Anzahl der Suchergebnisse: {{songs.length}}</i>
|
||||
<i>Anzahl der Suchergebnisse: {{ songs.length }}</i>
|
||||
</div>
|
||||
|
||||
@@ -6,12 +6,13 @@ describe('FilterComponent', () => {
|
||||
let component: FilterComponent;
|
||||
let fixture: ComponentFixture<FilterComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [FilterComponent]
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
declarations: [FilterComponent],
|
||||
}).compileComponents();
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FilterComponent);
|
||||
@@ -20,6 +21,6 @@ describe('FilterComponent', () => {
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
void expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {FormBuilder, FormGroup} from '@angular/forms';
|
||||
import {SongService} from '../../services/song.service';
|
||||
@@ -9,18 +9,17 @@ import {KEYS} from '../../services/key.helper';
|
||||
@Component({
|
||||
selector: 'app-filter',
|
||||
templateUrl: './filter.component.html',
|
||||
styleUrls: ['./filter.component.less']
|
||||
styleUrls: ['./filter.component.less'],
|
||||
})
|
||||
export class FilterComponent implements OnInit {
|
||||
|
||||
export class FilterComponent {
|
||||
public filterFormGroup: FormGroup;
|
||||
@Input() route: string;
|
||||
@Input() songs: Song[];
|
||||
@Input() public route: string;
|
||||
@Input() public songs: Song[];
|
||||
public types = SongService.TYPES;
|
||||
public legalType = SongService.LEGAL_TYPE;
|
||||
public keys = KEYS;
|
||||
|
||||
constructor(private router: Router, activatedRoute: ActivatedRoute, fb: FormBuilder) {
|
||||
public constructor(private router: Router, activatedRoute: ActivatedRoute, fb: FormBuilder) {
|
||||
this.filterFormGroup = fb.group({
|
||||
q: '',
|
||||
type: '',
|
||||
@@ -30,32 +29,18 @@ export class FilterComponent implements OnInit {
|
||||
});
|
||||
|
||||
activatedRoute.queryParams.subscribe((filterValues: FilterValues) => {
|
||||
if (filterValues.q) {
|
||||
this.filterFormGroup.controls.q.setValue(filterValues.q);
|
||||
}
|
||||
if (filterValues.type) {
|
||||
this.filterFormGroup.controls.type.setValue(filterValues.type);
|
||||
}
|
||||
if (filterValues.key) {
|
||||
this.filterFormGroup.controls.key.setValue(filterValues.key);
|
||||
}
|
||||
if (filterValues.legalType) {
|
||||
this.filterFormGroup.controls.legalType.setValue(filterValues.legalType);
|
||||
}
|
||||
if (filterValues.flag) {
|
||||
this.filterFormGroup.controls.flag.setValue(filterValues.flag);
|
||||
}
|
||||
if (filterValues.q) this.filterFormGroup.controls.q.setValue(filterValues.q);
|
||||
if (filterValues.type) this.filterFormGroup.controls.type.setValue(filterValues.type);
|
||||
if (filterValues.key) this.filterFormGroup.controls.key.setValue(filterValues.key);
|
||||
if (filterValues.legalType) this.filterFormGroup.controls.legalType.setValue(filterValues.legalType);
|
||||
if (filterValues.flag) this.filterFormGroup.controls.flag.setValue(filterValues.flag);
|
||||
});
|
||||
|
||||
this.filterFormGroup.controls.q.valueChanges.subscribe(_ => this.filerValueChanged('q', _));
|
||||
this.filterFormGroup.controls.key.valueChanges.subscribe(_ => this.filerValueChanged('key', _));
|
||||
this.filterFormGroup.controls.type.valueChanges.subscribe(_ => this.filerValueChanged('type', _));
|
||||
this.filterFormGroup.controls.legalType.valueChanges.subscribe(_ => this.filerValueChanged('legalType', _));
|
||||
this.filterFormGroup.controls.flag.valueChanges.subscribe(_ => this.filerValueChanged('flag', _));
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.filterFormGroup.controls.q.valueChanges.subscribe(_ => void this.filerValueChanged('q', _));
|
||||
this.filterFormGroup.controls.key.valueChanges.subscribe(_ => void this.filerValueChanged('key', _));
|
||||
this.filterFormGroup.controls.type.valueChanges.subscribe(_ => void this.filerValueChanged('type', _));
|
||||
this.filterFormGroup.controls.legalType.valueChanges.subscribe(_ => void this.filerValueChanged('legalType', _));
|
||||
this.filterFormGroup.controls.flag.valueChanges.subscribe(_ => void this.filerValueChanged('flag', _));
|
||||
}
|
||||
|
||||
public getFlags(): string[] {
|
||||
@@ -66,13 +51,14 @@ export class FilterComponent implements OnInit {
|
||||
.reduce((pn, u) => [...pn, ...u], [])
|
||||
.filter(_ => !!_);
|
||||
|
||||
const uqFlags = flags.filter((n, i) => flags.indexOf(n) === i);
|
||||
|
||||
return uqFlags;
|
||||
return flags.filter((n, i) => flags.indexOf(n) === i);
|
||||
}
|
||||
|
||||
private async filerValueChanged(key: string, value: string): Promise<void> {
|
||||
const route = this.router.createUrlTree([this.route], {queryParams: {[key]: value}, queryParamsHandling: 'merge'});
|
||||
const route = this.router.createUrlTree([this.route], {
|
||||
queryParams: {[key]: value},
|
||||
queryParamsHandling: 'merge',
|
||||
});
|
||||
await this.router.navigateByUrl(route);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,40 @@
|
||||
<div class="list-item">
|
||||
<div class="number">{{song.number}}</div>
|
||||
<div>{{song.title}}</div>
|
||||
<div class="number">{{ song.number }}</div>
|
||||
<div>{{ song.title }}</div>
|
||||
<div>
|
||||
<ng-container *appRole="['contributor']">
|
||||
<span *ngIf="song.status==='draft' || !song.status" class="warning" matTooltip="Entwurf"
|
||||
matTooltipPosition="before">
|
||||
<span
|
||||
*ngIf="song.status === 'draft' || !song.status"
|
||||
class="warning"
|
||||
matTooltip="Entwurf"
|
||||
matTooltipPosition="before"
|
||||
>
|
||||
<fa-icon [icon]="faDraft"></fa-icon>
|
||||
</span>
|
||||
<span *ngIf="song.status==='set'" class="neutral" matTooltip="Entwurf" matTooltipPosition="before">
|
||||
<span
|
||||
*ngIf="song.status === 'set'"
|
||||
class="neutral"
|
||||
matTooltip="Entwurf"
|
||||
matTooltipPosition="before"
|
||||
>
|
||||
<fa-icon [icon]="faDraft"></fa-icon>
|
||||
</span>
|
||||
<span *ngIf="song.status==='final'" class="success" matTooltip="Final" matTooltipPosition="before">
|
||||
<span
|
||||
*ngIf="song.status === 'final'"
|
||||
class="success"
|
||||
matTooltip="Final"
|
||||
matTooltipPosition="before"
|
||||
>
|
||||
<fa-icon [icon]="faFinal"></fa-icon>
|
||||
</span>
|
||||
</ng-container>
|
||||
<span *ngIf="song.legalType==='open'" class="warning" matTooltip="rechtlicher Status ist ungeklärt"
|
||||
matTooltipPosition="before"><fa-icon [icon]="faLegal"></fa-icon> </span>
|
||||
<span
|
||||
*ngIf="song.legalType === 'open'"
|
||||
class="warning"
|
||||
matTooltip="rechtlicher Status ist ungeklärt"
|
||||
matTooltipPosition="before"
|
||||
><fa-icon [icon]="faLegal"></fa-icon> </span
|
||||
>
|
||||
</div>
|
||||
<div>{{song.key}}</div>
|
||||
|
||||
<div>{{ song.key }}</div>
|
||||
</div>
|
||||
|
||||
@@ -6,12 +6,13 @@ describe('ListItemComponent', () => {
|
||||
let component: ListItemComponent;
|
||||
let fixture: ComponentFixture<ListItemComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ListItemComponent]
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
declarations: [ListItemComponent],
|
||||
}).compileComponents();
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ListItemComponent);
|
||||
@@ -20,6 +21,6 @@ describe('ListItemComponent', () => {
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
void expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {Song} from '../../services/song';
|
||||
import {faBalanceScaleRight} from '@fortawesome/free-solid-svg-icons/faBalanceScaleRight';
|
||||
import {faPencilRuler} from '@fortawesome/free-solid-svg-icons/faPencilRuler';
|
||||
@@ -7,18 +7,11 @@ import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
|
||||
@Component({
|
||||
selector: 'app-list-item',
|
||||
templateUrl: './list-item.component.html',
|
||||
styleUrls: ['./list-item.component.less']
|
||||
styleUrls: ['./list-item.component.less'],
|
||||
})
|
||||
export class ListItemComponent implements OnInit {
|
||||
export class ListItemComponent {
|
||||
@Input() public song: Song;
|
||||
public faLegal = faBalanceScaleRight;
|
||||
public faDraft = faPencilRuler;
|
||||
public faFinal = faCheck;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
</app-list-header>
|
||||
|
||||
<app-card [padding]="false">
|
||||
<app-list-item *ngFor="let song of songs" [routerLink]="song.id" [song]="song"></app-list-item>
|
||||
<app-list-item
|
||||
*ngFor="let song of songs"
|
||||
[routerLink]="song.id"
|
||||
[song]="song"
|
||||
></app-list-item>
|
||||
</app-card>
|
||||
</div>
|
||||
|
||||
@@ -9,24 +9,21 @@ describe('SongListComponent', () => {
|
||||
let component: SongListComponent;
|
||||
let fixture: ComponentFixture<SongListComponent>;
|
||||
|
||||
const songs = [
|
||||
{title: 'title1'}
|
||||
];
|
||||
const songs = [{title: 'title1'}];
|
||||
|
||||
const mockSongService = {
|
||||
list: () => of(songs)
|
||||
list: () => of(songs),
|
||||
};
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [SongListComponent],
|
||||
providers: [
|
||||
{provide: SongService, useValue: mockSongService}
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
declarations: [SongListComponent],
|
||||
providers: [{provide: SongService, useValue: mockSongService}],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
}).compileComponents();
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SongListComponent);
|
||||
@@ -35,13 +32,11 @@ describe('SongListComponent', () => {
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
void expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should read songs from SongService', fakeAsync(() => {
|
||||
tick();
|
||||
expect(component.songs$).toEqual([
|
||||
{title: 'title1'}
|
||||
] as any);
|
||||
void expect(component.songs$).toEqual([{title: 'title1'}]);
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -13,28 +13,21 @@ import {ScrollService} from '../../../services/scroll.service';
|
||||
selector: 'app-songs',
|
||||
templateUrl: './song-list.component.html',
|
||||
styleUrls: ['./song-list.component.less'],
|
||||
animations: [fade]
|
||||
animations: [fade],
|
||||
})
|
||||
export class SongListComponent implements OnInit, OnDestroy {
|
||||
|
||||
public songs$: Observable<Song[]>;
|
||||
public anyFilterActive = false;
|
||||
|
||||
constructor(
|
||||
private songService: SongService,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private scrollService: ScrollService) {
|
||||
}
|
||||
public constructor(private songService: SongService, private activatedRoute: ActivatedRoute, private scrollService: ScrollService) {}
|
||||
|
||||
ngOnInit() {
|
||||
public ngOnInit(): void {
|
||||
const filter$ = this.activatedRoute.queryParams.pipe(
|
||||
debounceTime(300),
|
||||
map(_ => _ as FilterValues)
|
||||
);
|
||||
|
||||
const songs$ = this.songService.list$().pipe(
|
||||
map(songs => songs.sort((a, b) => a.number - b.number)),
|
||||
);
|
||||
const songs$ = this.songService.list$().pipe(map(songs => songs.sort((a, b) => a.number - b.number)));
|
||||
|
||||
this.songs$ = combineLatest([filter$, songs$]).pipe(
|
||||
map(_ => {
|
||||
|
||||
@@ -17,7 +17,6 @@ import {MatTooltipModule} from '@angular/material/tooltip';
|
||||
import {RoleModule} from '../../../services/user/role.module';
|
||||
import {KeyTranslatorModule} from '../../../widget-modules/pipes/key-translator/key-translator.module';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [SongListComponent, ListItemComponent, FilterComponent],
|
||||
exports: [SongListComponent],
|
||||
@@ -37,7 +36,6 @@ import {KeyTranslatorModule} from '../../../widget-modules/pipes/key-translator/
|
||||
MatTooltipModule,
|
||||
RoleModule,
|
||||
KeyTranslatorModule,
|
||||
]
|
||||
],
|
||||
})
|
||||
export class SongListModule {
|
||||
}
|
||||
export class SongListModule {}
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
<app-card heading="Angehängte Dateien">
|
||||
|
||||
|
||||
<div *ngIf="currentUpload">
|
||||
<div class="progress">
|
||||
<div [ngStyle]="{ 'width': currentUpload?.progress + '%' }" class="progress-bar progress-bar-animated"></div>
|
||||
<div
|
||||
[ngStyle]="{ width: currentUpload?.progress + '%' }"
|
||||
class="progress-bar progress-bar-animated"
|
||||
></div>
|
||||
</div>
|
||||
Progress: {{currentUpload?.name}} | {{currentUpload?.progress}}% Complete
|
||||
Progress: {{ currentUpload?.name }} | {{ currentUpload?.progress }}%
|
||||
Complete
|
||||
</div>
|
||||
<div class="upload">
|
||||
<label>
|
||||
<input (change)="detectFiles($event)" type="file">
|
||||
<input (change)="detectFiles($event)" type="file"/>
|
||||
</label>
|
||||
|
||||
<button (click)="uploadSingle()"
|
||||
[disabled]="!selectedFiles"
|
||||
mat-icon-button>
|
||||
<button
|
||||
(click)="uploadSingle()"
|
||||
[disabled]="!selectedFiles"
|
||||
mat-icon-button
|
||||
>
|
||||
<mat-icon>cloud_upload</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p *ngFor="let file of (files$|async)">
|
||||
<p *ngFor="let file of files$ | async">
|
||||
<app-file [file]="file" [songId]="songId"></app-file>
|
||||
</p>
|
||||
</app-card>
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@ describe('EditFileComponent', () => {
|
||||
let component: EditFileComponent;
|
||||
let fixture: ComponentFixture<EditFileComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [EditFileComponent]
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
declarations: [EditFileComponent],
|
||||
}).compileComponents();
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EditFileComponent);
|
||||
@@ -20,6 +21,6 @@ describe('EditFileComponent', () => {
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
void expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,41 +10,33 @@ import {File} from '../../../services/file';
|
||||
@Component({
|
||||
selector: 'app-edit-file',
|
||||
templateUrl: './edit-file.component.html',
|
||||
styleUrls: ['./edit-file.component.less']
|
||||
styleUrls: ['./edit-file.component.less'],
|
||||
})
|
||||
export class EditFileComponent {
|
||||
|
||||
public selectedFiles: FileList;
|
||||
public currentUpload: Upload;
|
||||
public songId: string;
|
||||
public files$: Observable<File[]>;
|
||||
|
||||
|
||||
constructor(
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private uploadService: UploadService,
|
||||
private fileService: FileDataService,
|
||||
) {
|
||||
this.activatedRoute.params.pipe(
|
||||
map(param => param.songId),
|
||||
).subscribe(songId => {
|
||||
public constructor(private activatedRoute: ActivatedRoute, private uploadService: UploadService, private fileService: FileDataService) {
|
||||
this.activatedRoute.params.pipe(map((param: {songId: string}) => param.songId)).subscribe(songId => {
|
||||
this.songId = songId;
|
||||
});
|
||||
|
||||
this.files$ = this.activatedRoute.params.pipe(
|
||||
map(param => param.songId),
|
||||
map((param: {songId: string}) => param.songId),
|
||||
switchMap(songId => this.fileService.read$(songId))
|
||||
);
|
||||
}
|
||||
|
||||
detectFiles(event) {
|
||||
this.selectedFiles = event.target.files;
|
||||
public detectFiles(event: Event): void {
|
||||
const target = event.target as HTMLInputElement;
|
||||
this.selectedFiles = target.files;
|
||||
}
|
||||
|
||||
public async uploadSingle() {
|
||||
public uploadSingle(): void {
|
||||
const file = this.selectedFiles.item(0);
|
||||
this.currentUpload = new Upload(file as any);
|
||||
await this.uploadService.pushUpload(this.songId, this.currentUpload);
|
||||
this.currentUpload = new Upload(file);
|
||||
this.uploadService.pushUpload(this.songId, this.currentUpload);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
<fa-icon [icon]="faTrash"></fa-icon>
|
||||
</button>
|
||||
|
||||
<a [href]="url$|async" target="_blank">{{name}}</a>
|
||||
<a [href]="url$ | async" target="_blank">{{ name }}</a>
|
||||
|
||||
@@ -6,12 +6,13 @@ describe('FileComponent', () => {
|
||||
let component: FileComponent;
|
||||
let fixture: ComponentFixture<FileComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [FileComponent]
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
declarations: [FileComponent],
|
||||
}).compileComponents();
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FileComponent);
|
||||
@@ -20,6 +21,6 @@ describe('FileComponent', () => {
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
void expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,20 +7,20 @@ import {FileService} from '../../../../services/file.service';
|
||||
@Component({
|
||||
selector: 'app-file',
|
||||
templateUrl: './file.component.html',
|
||||
styleUrls: ['./file.component.less']
|
||||
styleUrls: ['./file.component.less'],
|
||||
})
|
||||
export class FileComponent {
|
||||
public url$: Observable<string>;
|
||||
public name: string;
|
||||
public faTrash = faTrashAlt;
|
||||
@Input() songId: string;
|
||||
@Input() public songId: string;
|
||||
private fileId: string;
|
||||
private path: string;
|
||||
|
||||
constructor(private fileService: FileService) {
|
||||
}
|
||||
public constructor(private fileService: FileService) {}
|
||||
|
||||
@Input() set file(file: File) {
|
||||
@Input()
|
||||
public set file(file: File) {
|
||||
this.url$ = this.fileService.getDownloadUrl(file.path + '/' + file.name);
|
||||
this.name = file.name;
|
||||
this.fileId = file.id;
|
||||
|
||||
@@ -6,11 +6,11 @@ describe('EditSongGuard', () => {
|
||||
let guard: EditSongGuard;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
void TestBed.configureTestingModule({});
|
||||
guard = TestBed.inject(EditSongGuard);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(guard).toBeTruthy();
|
||||
void expect(guard).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,16 +4,15 @@ import {Observable} from 'rxjs';
|
||||
import {EditComponent} from './edit.component';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class EditSongGuard implements CanDeactivate<unknown> {
|
||||
canDeactivate(
|
||||
public canDeactivate(
|
||||
component: EditComponent,
|
||||
currentRoute: ActivatedRouteSnapshot,
|
||||
currentState: RouterStateSnapshot,
|
||||
nextState?: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
|
||||
|
||||
nextState?: RouterStateSnapshot
|
||||
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
|
||||
return component.editSongComponent.askForSave(nextState);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,48 +1,58 @@
|
||||
<app-card *ngIf="song" [heading]="song.number + ' bearbeiten'" closeLink="../">
|
||||
|
||||
<form [formGroup]="form" class="form">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Titel</mat-label>
|
||||
<input formControlName="title" matInput>
|
||||
<input formControlName="title" matInput/>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="fourth">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Typ</mat-label>
|
||||
<mat-select formControlName="type">
|
||||
<mat-option *ngFor="let type of types" [value]="type">{{type | songType}}</mat-option>
|
||||
<mat-option *ngFor="let type of types" [value]="type">{{
|
||||
type | songType
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Tonart</mat-label>
|
||||
<mat-select formControlName="key">
|
||||
<mat-option *ngFor="let key of keys" [value]="key">{{key|key}}</mat-option>
|
||||
<mat-option *ngFor="let key of keys" [value]="key">{{
|
||||
key | key
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Tempo</mat-label>
|
||||
<input formControlName="tempo" matInput>
|
||||
<input formControlName="tempo" matInput/>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Status</mat-label>
|
||||
<mat-select formControlName="status">
|
||||
<mat-option *ngFor="let status of status" [value]="status">{{status | status}}</mat-option>
|
||||
<mat-option *ngFor="let status of status" [value]="status">{{
|
||||
status | status
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Songtext</mat-label>
|
||||
<textarea (focus)="songtextFocus=true" (focusout)="songtextFocus=false" [mat-autosize]="true"
|
||||
formControlName="text" matInput></textarea>
|
||||
<textarea
|
||||
(focus)="songtextFocus = true"
|
||||
(focusout)="songtextFocus = false"
|
||||
[mat-autosize]="true"
|
||||
formControlName="text"
|
||||
matInput
|
||||
></textarea>
|
||||
</mat-form-field>
|
||||
<div *ngIf="songtextFocus" class="song-text-help">
|
||||
<h3>Vorschau</h3>
|
||||
<app-song-text [text]="form.value.text" chordMode="show"></app-song-text>
|
||||
<h3>Hinweise zur Bearbeitung</h3>
|
||||
<h4>Aufbau</h4>
|
||||
Der Liedtext wird hintereinander weg geschrieben. Dabei werden Strophen, Refrain und Bridge jeweils durch
|
||||
eine zusätzliche Zeile Markiert. z.B.
|
||||
Der Liedtext wird hintereinander weg geschrieben. Dabei werden Strophen,
|
||||
Refrain und Bridge jeweils durch eine zusätzliche Zeile Markiert. z.B.
|
||||
<pre>
|
||||
Strophe
|
||||
Text der ersten Strophe
|
||||
@@ -54,9 +64,10 @@
|
||||
Und hier der Refrain
|
||||
</pre>
|
||||
<h3>Akkorde</h3>
|
||||
Die Akktorde werden jeweils in der Zeile über dem jeweiligen Liedtext geschrieben.
|
||||
Sie werden jeweils durch Leerzeichen an die entsprechende Position gebracht.
|
||||
Bitte keine Tabulatoren verwenden! Folgende Schreibweisen sind erlaubt:
|
||||
Die Akktorde werden jeweils in der Zeile über dem jeweiligen Liedtext
|
||||
geschrieben. Sie werden jeweils durch Leerzeichen an die entsprechende
|
||||
Position gebracht. Bitte keine Tabulatoren verwenden! Folgende
|
||||
Schreibweisen sind erlaubt:
|
||||
<pre>
|
||||
Dur: C D E
|
||||
Moll: c d e
|
||||
@@ -76,21 +87,31 @@
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Kommentar</mat-label>
|
||||
<textarea [mat-autosize]="true" formControlName="comment" matInput></textarea>
|
||||
<textarea
|
||||
[mat-autosize]="true"
|
||||
formControlName="comment"
|
||||
matInput
|
||||
></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-chip-list #chipList>
|
||||
<mat-chip (removed)="removeFlag(flag)" *ngFor="let flag of flags"
|
||||
[removable]="true" [selectable]="false">
|
||||
{{flag}}
|
||||
<mat-chip
|
||||
(removed)="removeFlag(flag)"
|
||||
*ngFor="let flag of flags"
|
||||
[removable]="true"
|
||||
[selectable]="false"
|
||||
>
|
||||
{{ flag }}
|
||||
<fa-icon (click)="removeFlag(flag)" [icon]="faRemove"></fa-icon>
|
||||
</mat-chip>
|
||||
<input (matChipInputTokenEnd)="addFlag($event)"
|
||||
[matChipInputAddOnBlur]="true"
|
||||
[matChipInputFor]="chipList"
|
||||
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||
placeholder="Attribute">
|
||||
<input
|
||||
(matChipInputTokenEnd)="addFlag($event)"
|
||||
[matChipInputAddOnBlur]="true"
|
||||
[matChipInputFor]="chipList"
|
||||
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||
placeholder="Attribute"
|
||||
/>
|
||||
</mat-chip-list>
|
||||
</mat-form-field>
|
||||
|
||||
@@ -98,54 +119,62 @@
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Rechtlicher Status</mat-label>
|
||||
<mat-select formControlName="legalType">
|
||||
<mat-option *ngFor="let key of legalType" [value]="key">{{key|legalType}}</mat-option>
|
||||
<mat-option *ngFor="let key of legalType" [value]="key">{{
|
||||
key | legalType
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Rechteinhaber</mat-label>
|
||||
<mat-select formControlName="legalOwner">
|
||||
<mat-option *ngFor="let key of legalOwner" [value]="key">{{key|legalOwner}}</mat-option>
|
||||
<mat-option *ngFor="let key of legalOwner" [value]="key">{{
|
||||
key | legalOwner
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Rechteinhaber ID (z.B. CCLI Liednummer)</mat-label>
|
||||
<input formControlName="legalOwnerId" matInput>
|
||||
<a *ngIf="form.value.legalOwner==='CCLI'" class="link-ccli"
|
||||
href="https://songselect.ccli.com/Songs/{{form.value.legalOwnerId}}"
|
||||
matSuffix
|
||||
matTooltip="CCLI Link: https://songselect.ccli.com/Songs/{{form.value.legalOwnerId}}"
|
||||
matTooltipPosition="before" target="_blank">
|
||||
<input formControlName="legalOwnerId" matInput/>
|
||||
<a
|
||||
*ngIf="form.value.legalOwner === 'CCLI'"
|
||||
class="link-ccli"
|
||||
href="https://songselect.ccli.com/Songs/{{ form.value.legalOwnerId }}"
|
||||
matSuffix
|
||||
matTooltip="CCLI Link: https://songselect.ccli.com/Songs/{{
|
||||
form.value.legalOwnerId
|
||||
}}"
|
||||
matTooltipPosition="before"
|
||||
target="_blank"
|
||||
>
|
||||
<fa-icon [icon]="faLink"></fa-icon>
|
||||
</a>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Künstler</mat-label>
|
||||
<input formControlName="artist" matInput>
|
||||
<input formControlName="artist" matInput/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Verlag</mat-label>
|
||||
<input formControlName="label" matInput>
|
||||
<input formControlName="label" matInput/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Nutzungsbedingungen</mat-label>
|
||||
<input formControlName="termsOfUse" matInput>
|
||||
<input formControlName="termsOfUse" matInput/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>abweichende Quelle</mat-label>
|
||||
<input formControlName="origin" matInput>
|
||||
<input formControlName="origin" matInput/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<app-button-row>
|
||||
<app-button (click)="onSave()" [icon]="faSave">Speichern</app-button>
|
||||
</app-button-row>
|
||||
</app-card>
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@ describe('EditSongComponent', () => {
|
||||
let component: EditSongComponent;
|
||||
let fixture: ComponentFixture<EditSongComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [EditSongComponent]
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
declarations: [EditSongComponent],
|
||||
}).compileComponents();
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EditSongComponent);
|
||||
@@ -20,6 +21,6 @@ describe('EditSongComponent', () => {
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
void expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ import {SaveDialogComponent} from './save-dialog/save-dialog.component';
|
||||
@Component({
|
||||
selector: 'app-edit-song',
|
||||
templateUrl: './edit-song.component.html',
|
||||
styleUrls: ['./edit-song.component.less']
|
||||
styleUrls: ['./edit-song.component.less'],
|
||||
})
|
||||
export class EditSongComponent implements OnInit {
|
||||
public song: Song;
|
||||
@@ -28,36 +28,31 @@ export class EditSongComponent implements OnInit {
|
||||
public legalOwner = SongService.LEGAL_OWNER;
|
||||
public legalType = SongService.LEGAL_TYPE;
|
||||
public flags: string[] = [];
|
||||
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
|
||||
public readonly separatorKeysCodes: number[] = [ENTER, COMMA];
|
||||
public faRemove = faTimesCircle;
|
||||
public faSave = faSave;
|
||||
public faLink = faExternalLinkAlt;
|
||||
public songtextFocus = false;
|
||||
|
||||
constructor(
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private songService: SongService,
|
||||
private editService: EditService,
|
||||
private router: Router,
|
||||
public dialog: MatDialog
|
||||
) {
|
||||
}
|
||||
public constructor(private activatedRoute: ActivatedRoute, private songService: SongService, private editService: EditService, private router: Router, public dialog: MatDialog) {}
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.activatedRoute.params.pipe(
|
||||
map(param => param.songId),
|
||||
switchMap(songId => this.songService.read$(songId)),
|
||||
first()
|
||||
).subscribe(song => {
|
||||
this.song = song;
|
||||
this.form = this.editService.createSongForm(song);
|
||||
this.form.controls.flags.valueChanges.subscribe(_ => this.onFlagsChanged(_));
|
||||
this.onFlagsChanged(this.form.controls.flags.value);
|
||||
});
|
||||
this.activatedRoute.params
|
||||
.pipe(
|
||||
map((param: {songId: string}) => param.songId),
|
||||
switchMap(songId => this.songService.read$(songId)),
|
||||
first()
|
||||
)
|
||||
.subscribe(song => {
|
||||
this.song = song;
|
||||
this.form = this.editService.createSongForm(song);
|
||||
this.form.controls.flags.valueChanges.subscribe(_ => this.onFlagsChanged(_));
|
||||
this.onFlagsChanged(this.form.controls.flags.value);
|
||||
});
|
||||
}
|
||||
|
||||
public async onSave(): Promise<void> {
|
||||
const data = this.form.value;
|
||||
const data = this.form.value as Partial<Song>;
|
||||
await this.songService.update$(this.song.id, data);
|
||||
this.form.markAsPristine();
|
||||
await this.router.navigateByUrl('songs/' + this.song.id);
|
||||
@@ -83,6 +78,22 @@ export class EditSongComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
public askForSave(nextState?: RouterStateSnapshot): boolean {
|
||||
if (!this.form.dirty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const dialogRef = this.dialog.open(SaveDialogComponent, {
|
||||
width: '350px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((save: boolean) => {
|
||||
void this.onSaveDialogAfterClosed(save, nextState.url).then();
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private onFlagsChanged(flagArray: string): void {
|
||||
if (!flagArray) {
|
||||
this.flags = [];
|
||||
@@ -92,30 +103,13 @@ export class EditSongComponent implements OnInit {
|
||||
this.flags = flagArray.split(';').filter(_ => !!_);
|
||||
}
|
||||
|
||||
public askForSave(nextState?: RouterStateSnapshot): boolean {
|
||||
if (!this.form.dirty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const dialogRef = this.dialog.open(SaveDialogComponent, {
|
||||
width: '350px'
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((save: boolean) => {
|
||||
this.onSaveDialogAfterClosed(save, nextState.url).then();
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async onSaveDialogAfterClosed(save: boolean, url: string) {
|
||||
if (save) {
|
||||
const data = this.form.value;
|
||||
const data = this.form.value as Partial<Song>;
|
||||
await this.songService.update$(this.song.id, data);
|
||||
}
|
||||
|
||||
this.form.markAsPristine();
|
||||
await this.router.navigateByUrl(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,5 +4,7 @@
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
<button [mat-dialog-close]="false" mat-button>Änderungen verwerfen</button>
|
||||
<button [mat-dialog-close]="true" cdkFocusInitial mat-button>Speichern</button>
|
||||
<button [mat-dialog-close]="true" cdkFocusInitial mat-button>
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -6,12 +6,13 @@ describe('SaveDialogComponent', () => {
|
||||
let component: SaveDialogComponent;
|
||||
let fixture: ComponentFixture<SaveDialogComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [SaveDialogComponent]
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
declarations: [SaveDialogComponent],
|
||||
}).compileComponents();
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SaveDialogComponent);
|
||||
@@ -20,6 +21,6 @@ describe('SaveDialogComponent', () => {
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
void expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-save-dialog',
|
||||
templateUrl: './save-dialog.component.html',
|
||||
styleUrls: ['./save-dialog.component.less']
|
||||
styleUrls: ['./save-dialog.component.less'],
|
||||
})
|
||||
export class SaveDialogComponent implements OnInit {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
export class SaveDialogComponent {}
|
||||
|
||||
@@ -6,12 +6,13 @@ describe('EditComponent', () => {
|
||||
let component: EditComponent;
|
||||
let fixture: ComponentFixture<EditComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [EditComponent]
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
declarations: [EditComponent],
|
||||
}).compileComponents();
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EditComponent);
|
||||
@@ -20,6 +21,6 @@ describe('EditComponent', () => {
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
void expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import {EditSongComponent} from './edit-song/edit-song.component';
|
||||
@Component({
|
||||
selector: 'app-edit',
|
||||
templateUrl: './edit.component.html',
|
||||
styleUrls: ['./edit.component.less']
|
||||
styleUrls: ['./edit.component.less'],
|
||||
})
|
||||
export class EditComponent {
|
||||
@ViewChild(EditSongComponent) public editSongComponent: EditSongComponent;
|
||||
|
||||
@@ -27,7 +27,6 @@ import {MatDialogModule} from '@angular/material/dialog';
|
||||
import {HistoryComponent} from './history/history.component';
|
||||
import {SongTextModule} from '../../../../widget-modules/components/song-text/song-text.module';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [EditComponent, EditSongComponent, EditFileComponent, FileComponent, SaveDialogComponent, HistoryComponent],
|
||||
exports: [EditComponent],
|
||||
@@ -56,8 +55,6 @@ import {SongTextModule} from '../../../../widget-modules/components/song-text/so
|
||||
MatTooltipModule,
|
||||
MatDialogModule,
|
||||
SongTextModule,
|
||||
|
||||
]
|
||||
],
|
||||
})
|
||||
export class EditModule {
|
||||
}
|
||||
export class EditModule {}
|
||||
|
||||
@@ -3,10 +3,10 @@ import {TestBed} from '@angular/core/testing';
|
||||
import {EditService} from './edit.service';
|
||||
|
||||
describe('EditService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
beforeEach(() => void TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: EditService = TestBed.get(EditService);
|
||||
expect(service).toBeTruthy();
|
||||
const service: EditService = TestBed.inject(EditService);
|
||||
void expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,13 +3,9 @@ import {Song} from '../../services/song';
|
||||
import {FormControl, FormGroup} from '@angular/forms';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class EditService {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public createSongForm(song: Song): FormGroup {
|
||||
return new FormGroup({
|
||||
text: new FormControl(song.text),
|
||||
|
||||
@@ -1,6 +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>{{ edit.username }}</div>
|
||||
<div>{{ edit.timestamp.toDate() | date: "dd.MM.yyyy" }}</div>
|
||||
</div>
|
||||
</app-card>
|
||||
|
||||
@@ -6,12 +6,13 @@ describe('HistoryComponent', () => {
|
||||
let component: HistoryComponent;
|
||||
let fixture: ComponentFixture<HistoryComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [HistoryComponent]
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
declarations: [HistoryComponent],
|
||||
}).compileComponents();
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HistoryComponent);
|
||||
@@ -20,6 +21,6 @@ describe('HistoryComponent', () => {
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
void expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,25 +7,22 @@ import {Song} from '../../../services/song';
|
||||
@Component({
|
||||
selector: 'app-history',
|
||||
templateUrl: './history.component.html',
|
||||
styleUrls: ['./history.component.less']
|
||||
styleUrls: ['./history.component.less'],
|
||||
})
|
||||
export class HistoryComponent implements OnInit {
|
||||
public song: Song;
|
||||
|
||||
constructor(
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private songService: SongService,
|
||||
) {
|
||||
}
|
||||
|
||||
public 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;
|
||||
});
|
||||
this.activatedRoute.params
|
||||
.pipe(
|
||||
map((param: {songId: string}) => param.songId),
|
||||
switchMap(songId => this.songService.read$(songId)),
|
||||
first()
|
||||
)
|
||||
.subscribe(song => {
|
||||
this.song = song;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<a [href]="url$|async" target="_blank">
|
||||
{{name}}
|
||||
<a [href]="url$ | async" target="_blank">
|
||||
{{ name }}
|
||||
</a>
|
||||
|
||||
@@ -6,12 +6,13 @@ describe('FileComponent', () => {
|
||||
let component: FileComponent;
|
||||
let fixture: ComponentFixture<FileComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [FileComponent]
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
declarations: [FileComponent],
|
||||
}).compileComponents();
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FileComponent);
|
||||
@@ -20,6 +21,6 @@ describe('FileComponent', () => {
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
void expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {File} from '../../services/file';
|
||||
import {AngularFireStorage} from '@angular/fire/storage';
|
||||
import {Observable} from 'rxjs';
|
||||
@@ -6,24 +6,18 @@ import {Observable} from 'rxjs';
|
||||
@Component({
|
||||
selector: 'app-file',
|
||||
templateUrl: './file.component.html',
|
||||
styleUrls: ['./file.component.less']
|
||||
styleUrls: ['./file.component.less'],
|
||||
})
|
||||
export class FileComponent implements OnInit {
|
||||
export class FileComponent {
|
||||
public url$: Observable<string>;
|
||||
public name: string;
|
||||
|
||||
constructor(private storage: AngularFireStorage) {
|
||||
}
|
||||
|
||||
@Input() set file(file: File) {
|
||||
public constructor(private storage: AngularFireStorage) {}
|
||||
|
||||
@Input()
|
||||
public set file(file: File) {
|
||||
const ref = this.storage.ref(file.path + '/' + file.name);
|
||||
this.url$ = ref.getDownloadURL();
|
||||
this.url$ = ref.getDownloadURL() as Observable<string>;
|
||||
this.name = file.name;
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<app-card closeLink="../" heading="Neues Lied">
|
||||
|
||||
<div [formGroup]="form" class="split">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Nummer</mat-label>
|
||||
<input formControlName="number" matInput>
|
||||
<input formControlName="number" matInput/>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Titel</mat-label>
|
||||
<input autofocus formControlName="title" matInput>
|
||||
<input autofocus formControlName="title" matInput/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
@@ -15,4 +14,3 @@
|
||||
<app-button (click)="onSave()" [icon]="faSave">Anlegen</app-button>
|
||||
</app-button-row>
|
||||
</app-card>
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@ describe('NewComponent', () => {
|
||||
let component: NewComponent;
|
||||
let fixture: ComponentFixture<NewComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [NewComponent]
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
declarations: [NewComponent],
|
||||
}).compileComponents();
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NewComponent);
|
||||
@@ -20,6 +21,6 @@ describe('NewComponent', () => {
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
void expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,14 +10,13 @@ import {Router} from '@angular/router';
|
||||
@Component({
|
||||
selector: 'app-new',
|
||||
templateUrl: './new.component.html',
|
||||
styleUrls: ['./new.component.less']
|
||||
styleUrls: ['./new.component.less'],
|
||||
})
|
||||
export class NewComponent implements OnInit {
|
||||
public faSave = faSave;
|
||||
public form: FormGroup;
|
||||
|
||||
constructor(private songService: SongService, private router: Router) {
|
||||
}
|
||||
public constructor(private songService: SongService, private router: Router) {}
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.form = new FormGroup({
|
||||
@@ -25,23 +24,27 @@ export class NewComponent implements OnInit {
|
||||
title: new FormControl(null, Validators.required),
|
||||
});
|
||||
|
||||
this.songService.list$().pipe(autoComplete(this)).subscribe(songs => {
|
||||
const freeSongnumber = this.getFreeSongNumber(songs);
|
||||
this.form.controls.number.setValue(freeSongnumber);
|
||||
});
|
||||
this.songService
|
||||
.list$()
|
||||
.pipe(autoComplete(this))
|
||||
.subscribe(songs => {
|
||||
const freeSongnumber = this.getFreeSongNumber(songs);
|
||||
this.form.controls.number.setValue(freeSongnumber);
|
||||
});
|
||||
}
|
||||
|
||||
public async onSave(): Promise<void> {
|
||||
const number = this.form.value.number;
|
||||
const title = this.form.value.title;
|
||||
const newSongId = await this.songService.new(number, title);
|
||||
const value = this.form.value as {number: number; title: string};
|
||||
const songNumber = value.number;
|
||||
const title = value.title;
|
||||
const newSongId = await this.songService.new(songNumber, title);
|
||||
await this.router.navigateByUrl('/songs/' + newSongId + '/edit');
|
||||
}
|
||||
|
||||
private getFreeSongNumber(songs: Song[]): Number {
|
||||
const numbers = songs.map(_ => _.number);
|
||||
private getFreeSongNumber(songs: Song[]): number {
|
||||
const songNumber = songs.map(_ => _.number);
|
||||
for (let i = 1; i < Number.MAX_SAFE_INTEGER; i++) {
|
||||
if (!numbers.some(_ => _ === i)) {
|
||||
if (!songNumber.some(_ => _ === i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,19 +9,8 @@ import {ButtonRowModule} from '../../../../widget-modules/components/button-row/
|
||||
import {ButtonModule} from '../../../../widget-modules/components/button/button.module';
|
||||
import {AutofocusModule} from '../../../../widget-modules/directives/autofocus/autofocus.module';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [NewComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
CardModule,
|
||||
ReactiveFormsModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
ButtonRowModule,
|
||||
ButtonModule,
|
||||
AutofocusModule
|
||||
]
|
||||
imports: [CommonModule, CardModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule, ButtonRowModule, ButtonModule, AutofocusModule],
|
||||
})
|
||||
export class NewModule {
|
||||
}
|
||||
export class NewModule {}
|
||||
|
||||
@@ -1,47 +1,74 @@
|
||||
<div class="split">
|
||||
<app-card *ngIf="song$ | async as song" [heading]="song.number + ' - ' + song.title" closeLink="../">
|
||||
<app-card
|
||||
*ngIf="song$ | async as song"
|
||||
[heading]="song.number + ' - ' + song.title"
|
||||
closeLink="../"
|
||||
>
|
||||
<div class="song">
|
||||
<div>
|
||||
<div *appRole="['leader', 'contributor']" class="detail">
|
||||
<div>Typ: {{song.type | songType}}</div>
|
||||
<div>Tonart: {{song.key}}</div>
|
||||
<div>Tempo: {{song.tempo}}</div>
|
||||
<div>Status: {{(song.status|status) || 'entwurf'}}</div>
|
||||
<div *ngIf="song.legalOwner">Rechteinhaber: {{song.legalOwner|legalOwner}}</div>
|
||||
<div *ngIf="song.legalOwnerId && song.legalOwner==='CCLI'">
|
||||
<a href="https://songselect.ccli.com/Songs/{{song.legalOwnerId}}" target="_blank">
|
||||
CCLI Nummer: {{song.legalOwnerId}}
|
||||
<div>Typ: {{ song.type | songType }}</div>
|
||||
<div>Tonart: {{ song.key }}</div>
|
||||
<div>Tempo: {{ song.tempo }}</div>
|
||||
<div>Status: {{ (song.status | status) || "entwurf" }}</div>
|
||||
<div *ngIf="song.legalOwner">
|
||||
Rechteinhaber: {{ song.legalOwner | legalOwner }}
|
||||
</div>
|
||||
<div *ngIf="song.legalOwnerId && song.legalOwner === 'CCLI'">
|
||||
<a
|
||||
href="https://songselect.ccli.com/Songs/{{ song.legalOwnerId }}"
|
||||
target="_blank"
|
||||
>
|
||||
CCLI Nummer: {{ song.legalOwnerId }}
|
||||
</a>
|
||||
</div>
|
||||
<div *ngIf="song.legalOwnerId && song.legalOwner!=='CCLI'">Rechteinhaber ID: {{song.legalOwnerId}}</div>
|
||||
<div *ngIf="song.artist">Künstler: {{song.artist}}</div>
|
||||
<div *ngIf="song.label">Verlag: {{song.label}}</div>
|
||||
<div *ngIf="song.origin">Quelle: {{song.origin}}</div>
|
||||
<div *ngIf="song.origin">Quelle: {{song.origin}}</div>
|
||||
<div *ngIf="song.legalOwnerId && song.legalOwner !== 'CCLI'">
|
||||
Rechteinhaber ID: {{ song.legalOwnerId }}
|
||||
</div>
|
||||
<div *ngIf="song.artist">Künstler: {{ song.artist }}</div>
|
||||
<div *ngIf="song.label">Verlag: {{ song.label }}</div>
|
||||
<div *ngIf="song.origin">Quelle: {{ song.origin }}</div>
|
||||
<div *ngIf="song.origin">Quelle: {{ song.origin }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="text">{{song.text}}</div>-->
|
||||
<app-song-text *ngIf="user$|async as user" [chordMode]="user.chordMode" [showSwitch]="true"
|
||||
[text]="song.text"></app-song-text>
|
||||
<app-song-text
|
||||
*ngIf="user$ | async as user"
|
||||
[chordMode]="user.chordMode"
|
||||
[showSwitch]="true"
|
||||
[text]="song.text"
|
||||
></app-song-text>
|
||||
|
||||
<mat-chip-list *appRole="['leader', 'contributor']" aria-label="Attribute">
|
||||
<mat-chip *ngFor="let flag of getFlags(song.flags)">{{flag}}</mat-chip>
|
||||
<mat-chip-list
|
||||
*appRole="['leader', 'contributor']"
|
||||
aria-label="Attribute"
|
||||
>
|
||||
<mat-chip *ngFor="let flag of getFlags(song.flags)">{{
|
||||
flag
|
||||
}}</mat-chip>
|
||||
</mat-chip-list>
|
||||
|
||||
<div *appRole="['leader', 'contributor']" class="text">{{song.comment}}</div>
|
||||
|
||||
<div *appRole="['leader', 'contributor']" class="text">
|
||||
{{ song.comment }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-button-row>
|
||||
<app-button (click)="onDelete(song.id)" *appRole="['admin']" [icon]="faDelete">Löschen</app-button>
|
||||
<app-button *appRole="['contributor']" [icon]="faEdit" routerLink="edit">Bearbeiten</app-button>
|
||||
<app-button
|
||||
(click)="onDelete(song.id)"
|
||||
*appRole="['admin']"
|
||||
[icon]="faDelete"
|
||||
>Löschen
|
||||
</app-button>
|
||||
<app-button *appRole="['contributor']" [icon]="faEdit" routerLink="edit"
|
||||
>Bearbeiten
|
||||
</app-button>
|
||||
</app-button-row>
|
||||
|
||||
</app-card>
|
||||
<ng-container *ngIf="(files$|async) as files">
|
||||
<app-card *ngIf="files.length>0" heading="Anhänge">
|
||||
<p *ngFor="let file of (files$|async)">
|
||||
<ng-container *ngIf="files$ | async as files">
|
||||
<app-card *ngIf="files.length > 0" heading="Anhänge">
|
||||
<p *ngFor="let file of files$ | async">
|
||||
<app-file [file]="file"></app-file>
|
||||
</p>
|
||||
</app-card>
|
||||
|
||||
@@ -9,18 +9,17 @@ describe('SongComponent', () => {
|
||||
let fixture: ComponentFixture<SongComponent>;
|
||||
|
||||
const mockActivatedRoute = {
|
||||
params: of({songId: '4711'})
|
||||
params: of({songId: '4711'}),
|
||||
};
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [SongComponent],
|
||||
providers: [
|
||||
{provide: ActivatedRoute, useValue: mockActivatedRoute}
|
||||
]
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
declarations: [SongComponent],
|
||||
providers: [{provide: ActivatedRoute, useValue: mockActivatedRoute}],
|
||||
}).compileComponents();
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SongComponent);
|
||||
@@ -29,7 +28,6 @@ describe('SongComponent', () => {
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
void expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ import {faTrash} from '@fortawesome/free-solid-svg-icons/faTrash';
|
||||
@Component({
|
||||
selector: 'app-song',
|
||||
templateUrl: './song.component.html',
|
||||
styleUrls: ['./song.component.less']
|
||||
styleUrls: ['./song.component.less'],
|
||||
})
|
||||
export class SongComponent implements OnInit {
|
||||
public song$: Observable<Song>;
|
||||
@@ -23,24 +23,18 @@ export class SongComponent implements OnInit {
|
||||
public faEdit = faEdit;
|
||||
public faDelete = faTrash;
|
||||
|
||||
constructor(
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private songService: SongService,
|
||||
private fileService: FileDataService,
|
||||
private userService: UserService,
|
||||
private router: Router,
|
||||
) {
|
||||
public constructor(private activatedRoute: ActivatedRoute, private songService: SongService, private fileService: FileDataService, private userService: UserService, private router: Router) {
|
||||
this.user$ = userService.user$;
|
||||
}
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.song$ = this.activatedRoute.params.pipe(
|
||||
map(param => param.songId),
|
||||
map((param: {songId: string}) => param.songId),
|
||||
switchMap(songId => this.songService.read$(songId))
|
||||
);
|
||||
|
||||
this.files$ = this.activatedRoute.params.pipe(
|
||||
map(param => param.songId),
|
||||
map((param: {songId: string}) => param.songId),
|
||||
switchMap(songId => this.fileService.read$(songId))
|
||||
);
|
||||
}
|
||||
@@ -50,7 +44,7 @@ export class SongComponent implements OnInit {
|
||||
return [];
|
||||
}
|
||||
return flags.split(';').filter(_ => !!_);
|
||||
}
|
||||
};
|
||||
|
||||
public async onDelete(songId: string): Promise<void> {
|
||||
await this.songService.delete(songId);
|
||||
|
||||
@@ -14,7 +14,6 @@ import {StatusTranslaterModule} from '../../../widget-modules/pipes/status-trans
|
||||
import {ButtonModule} from '../../../widget-modules/components/button/button.module';
|
||||
import {FileComponent} from './file/file.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [SongComponent, FileComponent],
|
||||
exports: [SongComponent],
|
||||
@@ -32,7 +31,6 @@ import {FileComponent} from './file/file.component';
|
||||
RoleModule,
|
||||
StatusTranslaterModule,
|
||||
ButtonModule,
|
||||
]
|
||||
],
|
||||
})
|
||||
export class SongModule {
|
||||
}
|
||||
export class SongModule {}
|
||||
|
||||
@@ -6,31 +6,29 @@ import {EditComponent} from './song/edit/edit.component';
|
||||
import {NewComponent} from './song/new/new.component';
|
||||
import {EditSongGuard} from './song/edit/edit-song.guard';
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: SongListComponent,
|
||||
pathMatch: 'full'
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
path: 'new',
|
||||
component: NewComponent
|
||||
component: NewComponent,
|
||||
},
|
||||
{
|
||||
path: ':songId/edit',
|
||||
component: EditComponent,
|
||||
canDeactivate: [EditSongGuard]
|
||||
canDeactivate: [EditSongGuard],
|
||||
},
|
||||
{
|
||||
path: ':songId',
|
||||
component: SongComponent
|
||||
}
|
||||
component: SongComponent,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class SongsRoutingModule {
|
||||
}
|
||||
export class SongsRoutingModule {}
|
||||
|
||||
@@ -9,14 +9,6 @@ import {NewModule} from './song/new/new.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SongsRoutingModule,
|
||||
SongListModule,
|
||||
SongModule,
|
||||
EditModule,
|
||||
NewModule,
|
||||
]
|
||||
imports: [CommonModule, SongsRoutingModule, SongListModule, SongModule, EditModule, NewModule],
|
||||
})
|
||||
export class SongsModule {
|
||||
}
|
||||
export class SongsModule {}
|
||||
|
||||
Reference in New Issue
Block a user