update tslint -> eslint

This commit is contained in:
2021-05-21 20:17:26 +02:00
parent 80260df71f
commit a195fafa6b
252 changed files with 3080 additions and 2420 deletions

59
.eslintrc.json Normal file
View File

@@ -0,0 +1,59 @@
{
"root": true,
"ignorePatterns": [
"projects/**/*"
],
"overrides": [
{
"files": [
"*.ts"
],
"parserOptions": {
"project": [
"tsconfig.json",
"e2e/tsconfig.json"
],
"createDefaultProgram": true
},
"extends": [
"plugin:@angular-eslint/recommended",
"plugin:@angular-eslint/template/process-inline-templates",
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:prettier/recommended"
],
"rules": {
"@typescript-eslint/explicit-member-accessibility": "error",
"@angular-eslint/component-selector": [
"error",
{
"prefix": "app",
"style": "kebab-case",
"type": "element"
}
],
"@typescript-eslint/unbound-method": [
"off"
],
"@angular-eslint/directive-selector": [
"error",
{
"prefix": "app",
"style": "camelCase",
"type": "attribute"
}
]
}
},
{
"files": [
"*.html"
],
"extends": [
"plugin:@angular-eslint/template/recommended"
],
"rules": {}
}
]
}

26
.prettierrc Normal file
View File

@@ -0,0 +1,26 @@
{
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"quoteProps": "as-needed",
"trailingComma": "es5",
"bracketSpacing": false,
"arrowParens": "avoid",
"jsxBracketSameLine": false,
"printWidth": 200,
"overrides": [
{
"files": "*.component.html",
"options": {
"parser": "angular"
}
},
{
"files": "*.html",
"options": {
"parser": "html"
}
}
]
}

View File

@@ -116,15 +116,11 @@
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"builder": "@angular-eslint/builder:lint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
"lintFilePatterns": [
"src/**/*.ts",
"src/**/*.html"
]
}
},
@@ -145,6 +141,7 @@
},
"defaultProject": "wgenerator",
"cli": {
"analytics": "4047dcd7-89f4-402f-958e-e365a5505c55"
"analytics": "4047dcd7-89f4-402f-958e-e365a5505c55",
"defaultCollection": "@angular-eslint/schematics"
}
}

View File

@@ -1,5 +1,5 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
import {AppPage} from './app.po';
import {browser, logging} from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
@@ -9,15 +9,17 @@ describe('workspace-project App', () => {
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('wgenerator app is running!');
void page.navigateTo();
void expect(page.getTitleText()).toEqual('wgenerator app is running!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
void expect(logs).not.toContain(
jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry)
);
});
});

1143
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
"scripts": {
"start": "ng serve --aot",
"build": "ng build",
"deploy": "ng build --prod && firebase deploy",
"deploy": "npm i && ng build --prod && firebase deploy",
"test": "ng test",
"lint": "ng lint --fix",
"postinstall": "ngcc"
@@ -39,13 +39,22 @@
"devDependencies": {
"@angular-devkit/architect": "0.1200.1",
"@angular-devkit/build-angular": "~12.0.1",
"@angular-eslint/builder": "12.0.0",
"@angular-eslint/eslint-plugin": "12.0.0",
"@angular-eslint/eslint-plugin-template": "12.0.0",
"@angular-eslint/schematics": "12.0.0",
"@angular-eslint/template-parser": "12.0.0",
"@angular/cli": "~12.0.1",
"@angular/compiler-cli": "~12.0.1",
"@angular/language-service": "~12.0.1",
"@types/jasmine": "~3.6.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.12.36",
"codelyzer": "^6.0.0",
"@typescript-eslint/eslint-plugin": "4.23.0",
"@typescript-eslint/parser": "4.23.0",
"eslint": "^7.26.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"firebase-tools": "^7.12.0",
"fuzzy": "^0.1.3",
"inquirer": "^6.2.2",
@@ -57,10 +66,10 @@
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"prettier": "^2.3.0",
"protractor": "~7.0.0",
"swiper": "^6.6.2",
"ts-node": "~7.0.0",
"tslint": "~6.1.0",
"typescript": "~4.2.4"
}
}

View File

@@ -3,23 +3,20 @@ import {animate, query, state, style, transition, trigger} from '@angular/animat
export const fade = [
// the fade-in/fade-out animation.
trigger('fade', [
// the "in" style determines the "resting" state of the element when it is visible.
state('in', style({opacity: 1, transform: 'translateY(0px)'})),
// fade in when created. this could also be written as transition('void => *')
transition(':enter', [
style({opacity: 0, transform: 'translateY(-10px)'}),
animate(200)
]),
])
transition(':enter', [style({opacity: 0, transform: 'translateY(-10px)'}), animate(200)]),
]),
];
export const fader =
trigger('fader', [
transition('* <=> *', [
// Set a default style for enter and leave
query(':enter, :leave', [
export const fader = trigger('fader', [
transition('* <=> *', [
// Set a default style for enter and leave
query(
':enter, :leave',
[
style({
position: 'absolute',
left: 0,
@@ -27,10 +24,22 @@ export const fader =
opacity: 0,
transform: 'scale(1) translateY(-10px)',
}),
], {optional: true}),
// Animate the new page in
query(':enter', [
animate('200ms ease', style({opacity: 1, transform: 'scale(1) translateY(0)'})),
], {optional: true}),
]),
]);
],
{optional: true}
),
// Animate the new page in
query(
':enter',
[
animate(
'200ms ease',
style({
opacity: 1,
transform: 'scale(1) translateY(0)',
})
),
],
{optional: true}
),
]),
]);

View File

@@ -7,7 +7,7 @@ const routes: Routes = [
{
path: '',
redirectTo: 'songs',
pathMatch: 'full'
pathMatch: 'full',
},
{
path: 'songs',
@@ -15,8 +15,8 @@ const routes: Routes = [
canActivate: [AngularFireAuthGuard, RoleGuard],
data: {
authGuardPipe: () => redirectUnauthorizedTo(['user', 'login']),
requiredRoles: ['user']
}
requiredRoles: ['user'],
},
},
{
path: 'shows',
@@ -24,8 +24,8 @@ const routes: Routes = [
canActivate: [AngularFireAuthGuard, RoleGuard],
data: {
authGuardPipe: () => redirectUnauthorizedTo(['user', 'login']),
requiredRoles: ['leader']
}
requiredRoles: ['leader'],
},
},
{
path: 'presentation',
@@ -33,12 +33,12 @@ const routes: Routes = [
canActivate: [AngularFireAuthGuard, RoleGuard],
data: {
authGuardPipe: () => redirectUnauthorizedTo(['user', 'login']),
requiredRoles: ['presenter']
}
requiredRoles: ['presenter'],
},
},
{
path: 'user',
loadChildren: () => import('./modules/user/user.module').then(m => m.UserModule)
loadChildren: () => import('./modules/user/user.module').then(m => m.UserModule),
},
{
path: 'brand',
@@ -47,12 +47,16 @@ const routes: Routes = [
{
path: 'guest',
loadChildren: () => import('./modules/guest/guest.module').then(m => m.GuestModule),
}
},
];
@NgModule({
imports: [RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules, relativeLinkResolution: 'legacy' })],
exports: [RouterModule]
imports: [
RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules,
relativeLinkResolution: 'legacy',
}),
],
exports: [RouterModule],
})
export class AppRoutingModule {
}
export class AppRoutingModule {}

View File

@@ -1,6 +1,10 @@
<app-navigation></app-navigation>
<perfect-scrollbar #scrollbar (psScrollY)="onScoll($event)" [perfectScrollbar]
class="scroll" style="max-height: calc(100vh); width: 100%; overflow: hidden;"
<perfect-scrollbar
#scrollbar
(psScrollY)="onScoll($event)"
[perfectScrollbar]
class="scroll"
style="max-height: calc(100vh); width: 100%; overflow: hidden"
>
<div [@fader]="o.isActivated ? o.activatedRoute : ''" class="content">
<router-outlet #o="outlet"></router-outlet>

View File

@@ -3,20 +3,17 @@ import {RouterTestingModule} from '@angular/router/testing';
import {AppComponent} from './app.component';
describe('AppComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
}));
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [AppComponent],
}).compileComponents();
})
);
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
void expect(fixture.debugElement.componentInstance).toBeTruthy();
});
});

View File

@@ -7,30 +7,24 @@ import {PerfectScrollbarComponent} from 'ngx-perfect-scrollbar';
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.less'],
animations: [fader]
animations: [fader],
})
export class AppComponent implements OnInit {
@ViewChild('scrollbar', {static: false}) public scrollbar: PerfectScrollbarComponent;
@ViewChild('scrollbar', {static: false}) scrollbar: PerfectScrollbarComponent;
constructor(private scrollService: ScrollService) {
public constructor(private scrollService: ScrollService) {
scrollService.restoreScrollPosition$.subscribe(pos => {
if (this.scrollbar && pos) {
// this.scrollbar.scrollTo(pos, 0);
this.scrollbar.directiveRef.scrollTo(0, pos, 300);
// debugger;
}
if (this.scrollbar && pos) this.scrollbar.directiveRef.scrollTo(0, pos, 300);
});
}
public static hideLoader = () => document.querySelector('#load-bg').classList.add('hidden');
public static hideLoader: () => void = () => document.querySelector('#load-bg').classList.add('hidden');
public ngOnInit(): void {
setTimeout(() => AppComponent.hideLoader(), 800);
}
onScoll($event: { srcElement: { scrollTop: number } }) {
public onScoll($event: {srcElement: {scrollTop: number}}): void {
this.scrollService.saveScrollPosition($event.srcElement.scrollTop);
}
}

View File

@@ -18,14 +18,14 @@ import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {PerfectScrollbarModule} from 'ngx-perfect-scrollbar';
@NgModule({
declarations: [
AppComponent
],
declarations: [AppComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
ServiceWorkerModule.register('ngsw-worker.js', {enabled: environment.production}),
ServiceWorkerModule.register('ngsw-worker.js', {
enabled: environment.production,
}),
BrowserAnimationsModule,
ApplicationFrameModule,
@@ -38,13 +38,8 @@ import {PerfectScrollbarModule} from 'ngx-perfect-scrollbar';
AngularFireAuthGuardModule,
FontAwesomeModule,
PerfectScrollbarModule,
],
providers: [
{provide: MAT_DATE_LOCALE, useValue: 'de-DE'},
],
providers: [{provide: MAT_DATE_LOCALE, useValue: 'de-DE'}],
bootstrap: [AppComponent],
})
export class AppModule {
}
export class AppModule {}

View File

@@ -6,12 +6,13 @@ describe('BrandComponent', () => {
let component: BrandComponent;
let fixture: ComponentFixture<BrandComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [BrandComponent]
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [BrandComponent],
}).compileComponents();
})
.compileComponents();
}));
);
beforeEach(() => {
fixture = TestBed.createComponent(BrandComponent);
@@ -20,6 +21,6 @@ describe('BrandComponent', () => {
});
it('should create', () => {
expect(component).toBeTruthy();
void expect(component).toBeTruthy();
});
});

View File

@@ -1,16 +1,8 @@
import {Component, OnInit} from '@angular/core';
import {Component} from '@angular/core';
@Component({
selector: 'app-brand',
templateUrl: './brand.component.html',
styleUrls: ['./brand.component.less']
styleUrls: ['./brand.component.less'],
})
export class BrandComponent implements OnInit {
constructor() {
}
ngOnInit(): void {
}
}
export class BrandComponent {}

View File

@@ -5,26 +5,20 @@ import {RouterModule, Routes} from '@angular/router';
import {LogoModule} from '../../widget-modules/components/logo/logo.module';
import {NewUserComponent} from './new-user/new-user.component';
const routes: Routes = [
{
path: '',
pathMatch: 'full',
component: BrandComponent
component: BrandComponent,
},
{
path: 'new-user',
component: NewUserComponent
component: NewUserComponent,
},
];
@NgModule({
declarations: [BrandComponent, NewUserComponent],
imports: [
CommonModule,
RouterModule.forChild(routes),
LogoModule
]
imports: [CommonModule, RouterModule.forChild(routes), LogoModule],
})
export class BrandModule {
}
export class BrandModule {}

View File

@@ -1,8 +1,11 @@
<div class="frame">
<app-brand class="brand"></app-brand>
<div *ngIf="user$|async as user" class="text">
<div *ngIf="user$ | async as user" class="text">
<div class="welcome">WILLKOMMEN</div>
<div class="name">{{user.name}}</div>
<div class="roles">Es wurden noch keine Berechtigungen zugeteilt, bitte wende Dich an den Administrator!</div>
<div class="name">{{ user.name }}</div>
<div class="roles">
Es wurden noch keine Berechtigungen zugeteilt, bitte wende Dich an den
Administrator!
</div>
</div>
</div>

View File

@@ -6,12 +6,13 @@ describe('NewUserComponent', () => {
let component: NewUserComponent;
let fixture: ComponentFixture<NewUserComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [NewUserComponent]
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [NewUserComponent],
}).compileComponents();
})
.compileComponents();
}));
);
beforeEach(() => {
fixture = TestBed.createComponent(NewUserComponent);
@@ -20,6 +21,6 @@ describe('NewUserComponent', () => {
});
it('should create', () => {
expect(component).toBeTruthy();
void expect(component).toBeTruthy();
});
});

View File

@@ -6,12 +6,12 @@ import {User} from '../../../services/user/user';
@Component({
selector: 'app-new-user',
templateUrl: './new-user.component.html',
styleUrls: ['./new-user.component.less']
styleUrls: ['./new-user.component.less'],
})
export class NewUserComponent {
public user$: Observable<User>;
constructor(private userService: UserService) {
public constructor(private userService: UserService) {
this.user$ = userService.user$;
}
}

View File

@@ -1,8 +1,11 @@
<div *ngIf="songs$|async as songs" class="view">
<div *ngIf="songs$ | async as songs" class="view">
<swiper>
<div *ngFor="let song of songs" class="song">
<app-song-text [showSwitch]="false" [text]="song|async"
chordMode="hide"></app-song-text>
<app-song-text
[showSwitch]="false"
[text]="song | async"
chordMode="hide"
></app-song-text>
</div>
</swiper>
</div>

View File

@@ -6,12 +6,13 @@ describe('GuestComponent', () => {
let component: GuestComponent;
let fixture: ComponentFixture<GuestComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [GuestComponent]
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [GuestComponent],
}).compileComponents();
})
.compileComponents();
}));
);
beforeEach(() => {
fixture = TestBed.createComponent(GuestComponent);
@@ -20,6 +21,6 @@ describe('GuestComponent', () => {
});
it('should create', () => {
expect(component).toBeTruthy();
void expect(component).toBeTruthy();
});
});

View File

@@ -8,28 +8,18 @@ import {ShowSongService} from '../shows/services/show-song.service';
@Component({
selector: 'app-guest',
templateUrl: './guest.component.html',
styleUrls: ['./guest.component.less']
styleUrls: ['./guest.component.less'],
})
export class GuestComponent implements OnInit {
public songs$: Observable<Observable<string>[]>;
constructor(
private songService: SongService,
private globalSettingsService: GlobalSettingsService,
private showSongService: ShowSongService,
) {
}
public constructor(private songService: SongService, private globalSettingsService: GlobalSettingsService, private showSongService: ShowSongService) {}
public ngOnInit(): void {
this.songs$ = this.globalSettingsService.get$.pipe(
map(_ => _.currentShow),
switchMap(_ => this.showSongService.list$(_)),
map(_ => _
.sort((x, y) => x.order - y.order)
.map(showSong => this.songService.read$(showSong.songId).pipe(map(song => song.text)))
)
map(_ => _.sort((x, y) => x.order - y.order).map(showSong => this.songService.read$(showSong.songId).pipe(map(song => song.text))))
);
}
}

View File

@@ -13,23 +13,16 @@ const DEFAULT_SWIPER_CONFIG: SwiperConfigInterface = {
scrollbar: false,
navigation: true,
pagination: false,
};
@NgModule({
declarations: [GuestComponent],
imports: [
CommonModule,
RouterModule.forChild([{path: '', component: GuestComponent}]),
SwiperModule,
SongTextModule
],
imports: [CommonModule, RouterModule.forChild([{path: '', component: GuestComponent}]), SwiperModule, SongTextModule],
providers: [
{
provide: SWIPER_CONFIG,
useValue: DEFAULT_SWIPER_CONFIG
}
]
useValue: DEFAULT_SWIPER_CONFIG,
},
],
})
export class GuestModule {
}
export class GuestModule {}

View File

@@ -1,10 +1,12 @@
<p *ngIf="song.artist">{{song.artist}}</p>
<p *ngIf="song.label">{{song.label}}</p>
<p *ngIf="song.termsOfUse" class="terms-of-use">{{song.termsOfUse}}</p>
<p *ngIf="song.origin">{{song.origin}}</p>
<p *ngIf="song.artist">{{ song.artist }}</p>
<p *ngIf="song.label">{{ song.label }}</p>
<p *ngIf="song.termsOfUse" class="terms-of-use">{{ song.termsOfUse }}</p>
<p *ngIf="song.origin">{{ song.origin }}</p>
<div *ngIf="song.legalOwnerId">
<p *ngIf="song.legalOwner==='CCLI' && config">CCLI-Liednummer {{song.legalOwnerId}},
CCLI-Lizenznummer {{config.ccliLicenseId}}</p>
<p *ngIf="song.legalOwner!=='CCLI'">Liednummer {{song.legalOwnerId}}</p>
<p *ngIf="song.legalOwner === 'CCLI' && config">
CCLI-Liednummer {{ song.legalOwnerId }}, CCLI-Lizenznummer
{{ config.ccliLicenseId }}
</p>
<p *ngIf="song.legalOwner !== 'CCLI'">Liednummer {{ song.legalOwnerId }}</p>
</div>

View File

@@ -6,12 +6,13 @@ describe('LegalComponent', () => {
let component: LegalComponent;
let fixture: ComponentFixture<LegalComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [LegalComponent]
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [LegalComponent],
}).compileComponents();
})
.compileComponents();
}));
);
beforeEach(() => {
fixture = TestBed.createComponent(LegalComponent);
@@ -20,6 +21,6 @@ describe('LegalComponent', () => {
});
it('should create', () => {
expect(component).toBeTruthy();
void expect(component).toBeTruthy();
});
});

View File

@@ -5,7 +5,7 @@ import {Config} from '../../../../services/config';
@Component({
selector: 'app-legal',
templateUrl: './legal.component.html',
styleUrls: ['./legal.component.less']
styleUrls: ['./legal.component.less'],
})
export class LegalComponent {
@Input() public song: Song;

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -6,12 +6,13 @@ describe('LogoComponent', () => {
let component: LogoComponent;
let fixture: ComponentFixture<LogoComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [LogoComponent]
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [LogoComponent],
}).compileComponents();
})
.compileComponents();
}));
);
beforeEach(() => {
fixture = TestBed.createComponent(LogoComponent);
@@ -20,6 +21,6 @@ describe('LogoComponent', () => {
});
it('should create', () => {
expect(component).toBeTruthy();
void expect(component).toBeTruthy();
});
});

View File

@@ -1,16 +1,8 @@
import {Component, OnInit} from '@angular/core';
import {Component} from '@angular/core';
@Component({
selector: 'app-logo',
templateUrl: './logo.component.html',
styleUrls: ['./logo.component.less']
styleUrls: ['./logo.component.less'],
})
export class LogoComponent implements OnInit {
constructor() {
}
ngOnInit(): void {
}
}
export class LogoComponent {}

View File

@@ -1,20 +1,31 @@
<div *ngIf="song" [style.font-size.px]="zoom" class="fullscreen background">
<div @songSwitch [class.blur]="songId==='title'" [class.hide]="songId!=='title' && songId!=='empty'"
class="start fullscreen logo">
<div
@songSwitch
[class.blur]="songId === 'title'"
[class.hide]="songId !== 'title' && songId !== 'empty'"
class="start fullscreen logo"
>
<app-logo></app-logo>
</div>
<div *ngIf="songId==='title'" @songSwitch class="start fullscreen">
<div>{{showType|showType}}</div>
<div class="date">{{date|date:'dd.MM.yyyy'}}</div>
<div *ngIf="songId === 'title'" @songSwitch class="start fullscreen">
<div>{{ showType | showType }}</div>
<div class="date">{{ date | date: "dd.MM.yyyy" }}</div>
</div>
<app-song-text *ngIf="songId!=='title' && songId!=='empty'" @songSwitch [fullscreen]="true" [index]="index"
[showSwitch]="false" [text]="song.text"
chordMode="hide"></app-song-text>
<app-legal *ngIf="songId!=='title' && songId!=='empty'" @songSwitch [config]="config$|async"
[song]="song"></app-legal>
<app-song-text
*ngIf="songId !== 'title' && songId !== 'empty'"
@songSwitch
[fullscreen]="true"
[index]="index"
[showSwitch]="false"
[text]="song.text"
chordMode="hide"
></app-song-text>
<app-legal
*ngIf="songId !== 'title' && songId !== 'empty'"
@songSwitch
[config]="config$ | async"
[song]="song"
></app-legal>
</div>

View File

@@ -6,12 +6,13 @@ describe('MonitorComponent', () => {
let component: MonitorComponent;
let fixture: ComponentFixture<MonitorComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [MonitorComponent]
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [MonitorComponent],
}).compileComponents();
})
.compileComponents();
}));
);
beforeEach(() => {
fixture = TestBed.createComponent(MonitorComponent);
@@ -20,6 +21,6 @@ describe('MonitorComponent', () => {
});
it('should create', () => {
expect(component).toBeTruthy();
void expect(component).toBeTruthy();
});
});

View File

@@ -8,14 +8,14 @@ import {Config} from '../../../services/config';
import {Observable} from 'rxjs';
import {ConfigService} from '../../../services/config.service';
import {songSwitch} from '../../../widget-modules/components/song-text/animation';
import {Section} from '../../songs/services/section';
import {TextRenderingService} from '../../songs/services/text-rendering.service';
import {Show} from '../../shows/services/show';
@Component({
selector: 'app-monitor',
templateUrl: './monitor.component.html',
styleUrls: ['./monitor.component.less'],
animations: [songSwitch]
animations: [songSwitch],
})
export class MonitorComponent implements OnInit {
public song: Song;
@@ -25,35 +25,37 @@ export class MonitorComponent implements OnInit {
public index: number;
public showType: string;
public date: Date;
private sections: Section[];
public config$: Observable<Config>;
constructor(
// private sections: Section[];
public constructor(
private showService: ShowService,
private songService: SongService,
private textRenderingService: TextRenderingService,
private globalSettingsService: GlobalSettingsService,
private configService: ConfigService,
private configService: ConfigService
) {
this.config$ = configService.get$;
}
ngOnInit(): void {
this.globalSettingsService.get$.pipe(
map(_ => _.currentShow),
distinctUntilChanged(),
tap(_ => this.currentShowId = _),
switchMap(_ => this.showService.read$(_)),
tap(_ => this.showType = _.showType),
tap(_ => this.date = _.date.toDate()),
tap(_ => this.songId = _.presentationSongId),
tap(_ => this.index = _.presentationSection),
tap(_ => this.zoom = _.presentationZoom ?? 30),
switchMap(_ => this.songService.read$(_.presentationSongId))
).subscribe((_: Song) => {
this.song = _;
this.sections = this.textRenderingService.parse(_.text, null);
});
public ngOnInit(): void {
this.globalSettingsService.get$
.pipe(
map(_ => _.currentShow),
distinctUntilChanged(),
tap(_ => (this.currentShowId = _)),
switchMap(_ => this.showService.read$(_)),
tap(_ => (this.showType = _.showType)),
tap(_ => (this.date = _.date.toDate())),
tap(_ => (this.songId = _.presentationSongId)),
tap(_ => (this.index = _.presentationSection)),
tap(_ => (this.zoom = _.presentationZoom ?? 30)),
switchMap((_: Show) => this.songService.read$(_.presentationSongId))
)
.subscribe((_: Song) => {
this.song = _;
// this.sections = this.textRenderingService.parse(_.text, null);
});
}
}

View File

@@ -3,26 +3,24 @@ import {RouterModule, Routes} from '@angular/router';
import {RemoteComponent} from './remote/remote.component';
import {MonitorComponent} from './monitor/monitor.component';
const routes: Routes = [
{
path: '',
pathMatch: 'full',
redirectTo: 'remote'
redirectTo: 'remote',
},
{
path: 'remote',
component: RemoteComponent
component: RemoteComponent,
},
{
path: 'monitor',
component: MonitorComponent
}
component: MonitorComponent,
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
exports: [RouterModule],
})
export class PresentationRoutingModule {
}
export class PresentationRoutingModule {}

View File

@@ -18,12 +18,9 @@ import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {AddSongModule} from '../../widget-modules/components/add-song/add-song.module';
import {LogoComponent} from './monitor/logo/logo.component';
@NgModule({
declarations: [MonitorComponent, RemoteComponent, LegalComponent, LogoComponent],
exports: [
RemoteComponent
],
exports: [RemoteComponent],
imports: [
CommonModule,
PresentationRoutingModule,
@@ -38,8 +35,7 @@ import {LogoComponent} from './monitor/logo/logo.component';
MatSliderModule,
FormsModule,
AddSongModule,
ReactiveFormsModule
]
ReactiveFormsModule,
],
})
export class PresentationModule {
}
export class PresentationModule {}

View File

@@ -1,41 +1,60 @@
<div *ngIf="shows$|async as shows">
<div *ngIf="shows$ | async as shows">
<app-card>
<p *ngIf="!shows.length" @fade>
Es ist derzeit keine Veranstaltung vorhanden
</p>
<p *ngIf="!shows.length" @fade>Es ist derzeit keine Veranstaltung vorhanden</p>
<mat-form-field *ngIf="shows.length>0" @fade appearance="outline">
<mat-form-field *ngIf="shows.length > 0" @fade appearance="outline">
<mat-label>Veranstaltung</mat-label>
<mat-select [formControl]="showControl">
<mat-option *ngFor="let show of shows" [value]="show.id">
{{show.showType|showType}}, {{show.date.toDate()|date:'dd.MM.yyyy'}}
{{ show.showType | showType }},
{{ show.date.toDate() | date: "dd.MM.yyyy" }}
</mat-option>
</mat-select>
</mat-form-field>
<ng-container *ngIf="!progress">
<div *ngIf="show" class="song-parts padding-bottom">
<div (click)="onSectionClick('title', -1)"
[class.active]="show.presentationSongId==='title'"
class="song-part">
<div
(click)="onSectionClick('title', -1)"
[class.active]="show.presentationSongId === 'title'"
class="song-part"
>
<div class="head">Veranstaltung</div>
</div>
<div (click)="onSectionClick('empty', -1)"
[class.active]="show.presentationSongId==='empty'"
class="song-part">
<div
(click)="onSectionClick('empty', -1)"
[class.active]="show.presentationSongId === 'empty'"
class="song-part"
>
<div class="head">Leer</div>
</div>
</div>
<div *ngFor="let song of presentationSongs" @fade class="song">
<div [class.active]="show.presentationSongId===song.id" class="title song-part">
<div (click)="onSectionClick(song.id, -1)" class="head">{{song.title}}</div>
<div
[class.active]="show.presentationSongId === song.id"
class="title song-part"
>
<div (click)="onSectionClick(song.id, -1)" class="head">
{{ song.title }}
</div>
</div>
<div *ngIf="show" class="song-parts">
<div (click)="onSectionClick(song.id, i)" *ngFor="let section of song.sections; index as i"
[class.active]="show.presentationSongId===song.id && show.presentationSection===i"
class="song-part">
<div class="head">{{section.type|sectionType}} {{section.number + 1}}</div>
<div class="fragment">{{getFirstLine(section)}}</div>
<div
(click)="onSectionClick(song.id, i)"
*ngFor="let section of song.sections; index as i"
[class.active]="
show.presentationSongId === song.id &&
show.presentationSection === i
"
class="song-part"
>
<div class="head">
{{ section.type | sectionType }} {{ section.number + 1 }}
</div>
<div class="fragment">{{ getFirstLine(section) }}</div>
</div>
</div>
</div>
@@ -56,11 +75,13 @@
</mat-slider>
</div>
<app-add-song *ngIf="show" [addedLive]="true" [showId]="currentShowId" [showSongs]="showSongs"
[songs]="songs"></app-add-song>
<app-add-song
*ngIf="show"
[addedLive]="true"
[showId]="currentShowId"
[showSongs]="showSongs"
[songs]="songs"
></app-add-song>
</ng-container>
</app-card>
</div>

View File

@@ -6,12 +6,13 @@ describe('RemoteComponent', () => {
let component: RemoteComponent;
let fixture: ComponentFixture<RemoteComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [RemoteComponent]
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [RemoteComponent],
}).compileComponents();
})
.compileComponents();
}));
);
beforeEach(() => {
fixture = TestBed.createComponent(RemoteComponent);
@@ -20,6 +21,6 @@ describe('RemoteComponent', () => {
});
it('should create', () => {
expect(component).toBeTruthy();
void expect(component).toBeTruthy();
});
});

View File

@@ -25,7 +25,7 @@ export interface PresentationSong {
selector: 'app-remote',
templateUrl: './remote.component.html',
styleUrls: ['./remote.component.less'],
animations: [fade]
animations: [fade],
})
export class RemoteComponent {
public shows$: Observable<Show[]>;
@@ -39,24 +39,26 @@ export class RemoteComponent {
public faDesktop = faDesktop;
public showControl = new FormControl();
constructor(
public constructor(
private showService: ShowService,
private showSongService: ShowSongService,
private songService: SongService,
private textRenderingService: TextRenderingService,
private globalSettingsService: GlobalSettingsService,
private globalSettingsService: GlobalSettingsService
) {
this.shows$ = showService.list$(true);
songService.list$().subscribe(_ => this.songs = _);
songService.list$().subscribe(_ => (this.songs = _));
globalSettingsService.get$.pipe(
map(_ => _.currentShow),
distinctUntilChanged()
).subscribe(_ => {
this.showControl.setValue(_, {emitEvent: false});
this.onShowChanged(_, false);
});
this.showControl.valueChanges.subscribe(value => this.onShowChanged(value));
globalSettingsService.get$
.pipe(
map(_ => _.currentShow),
distinctUntilChanged()
)
.subscribe(_ => {
this.showControl.setValue(_, {emitEvent: false});
void this.onShowChanged(_, false);
});
this.showControl.valueChanges.subscribe((value: string) => void this.onShowChanged(value));
}
public async onShowChanged(change: string, updateShow = true): Promise<void> {
@@ -68,16 +70,14 @@ export class RemoteComponent {
await this.showService.update$(change, {presentationSongId: 'title'});
}
this.currentShowId = change;
this.showService.read$(change).subscribe(_ => this.show = _);
this.showService.read$(change).subscribe(_ => (this.show = _));
this.showSongService.list$(change).subscribe(_ => {
this.showSongs = _;
this.presentationSongs = _
.map(song => this.songs.filter(f => f.id == song.songId)[0])
.map(song => ({
id: song.id,
title: song.title,
sections: this.textRenderingService.parse(song.text, null)
}));
this.presentationSongs = _.map(song => this.songs.filter(f => f.id === song.songId)[0]).map(song => ({
id: song.id,
title: song.title,
sections: this.textRenderingService.parse(song.text, null),
}));
});
await delay(500);
this.progress = false;
@@ -90,13 +90,11 @@ export class RemoteComponent {
public async onSectionClick(id: string, index: number): Promise<void> {
await this.showService.update$(this.currentShowId, {
presentationSongId: id,
presentationSection: index
presentationSection: index,
});
}
public async onZoom(zoom: number) {
await this.showService.update$(this.currentShowId, {
presentationZoom: zoom,
});
public async onZoom(zoom: number): Promise<void> {
await this.showService.update$(this.currentShowId, {presentationZoom: zoom});
}
}

View File

@@ -6,11 +6,11 @@ describe('PresentationService', () => {
let service: PresentationService;
beforeEach(() => {
TestBed.configureTestingModule({});
void TestBed.configureTestingModule({});
service = TestBed.inject(PresentationService);
});
it('should be created', () => {
expect(service).toBeTruthy();
void expect(service).toBeTruthy();
});
});

View File

@@ -1,10 +1,6 @@
import {Injectable} from '@angular/core';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class PresentationService {
constructor() {
}
}
export class PresentationService {}

View File

@@ -1,4 +1,4 @@
<div class="list-item">
<div>{{show.date.toDate()|date:'dd.MM.yyyy'}}</div>
<div>{{show.showType|showType}}</div>
<div>{{ show.date.toDate() | date: "dd.MM.yyyy" }}</div>
<div>{{ show.showType | showType }}</div>
</div>

View File

@@ -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();
});
});

View File

@@ -4,7 +4,7 @@ import {Show} from '../../services/show';
@Component({
selector: 'app-list-item',
templateUrl: './list-item.component.html',
styleUrls: ['./list-item.component.less']
styleUrls: ['./list-item.component.less'],
})
export class ListItemComponent {
@Input() public show: Show;

View File

@@ -2,15 +2,32 @@
<app-list-header></app-list-header>
<ng-container *ngIf="shows$ | async as shows">
<app-card *ngIf="getPrivateSongs(shows).length>0" [@fade] [padding]="false" heading="meine Veranstaltungen">
<app-list-item *ngFor="let show of getPrivateSongs(shows)" [routerLink]="show.id" [show]="show"></app-list-item>
<app-card
*ngIf="getPrivateSongs(shows).length > 0"
[@fade]
[padding]="false"
heading="meine Veranstaltungen"
>
<app-list-item
*ngFor="let show of getPrivateSongs(shows)"
[routerLink]="show.id"
[show]="show"
></app-list-item>
</app-card>
</ng-container>
<ng-container *ngIf="shows$ | async as shows">
<app-card *ngIf="getPublicShows(shows).length>0" [@fade] [padding]="false"
heading="veröffentlichte Veranstaltungen">
<app-list-item *ngFor="let show of getPublicShows(shows)" [routerLink]="show.id" [show]="show"></app-list-item>
<app-card
*ngIf="getPublicShows(shows).length > 0"
[@fade]
[padding]="false"
heading="veröffentlichte Veranstaltungen"
>
<app-list-item
*ngFor="let show of getPublicShows(shows)"
[routerLink]="show.id"
[show]="show"
></app-list-item>
</app-card>
</ng-container>
</div>

View File

@@ -6,12 +6,13 @@ describe('ListComponent', () => {
let component: ListComponent;
let fixture: ComponentFixture<ListComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ListComponent]
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [ListComponent],
}).compileComponents();
})
.compileComponents();
}));
);
beforeEach(() => {
fixture = TestBed.createComponent(ListComponent);
@@ -20,6 +21,6 @@ describe('ListComponent', () => {
});
it('should create', () => {
expect(component).toBeTruthy();
void expect(component).toBeTruthy();
});
});

View File

@@ -8,12 +8,12 @@ import {ShowService} from '../services/show.service';
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.less'],
animations: [fade]
animations: [fade],
})
export class ListComponent {
public shows$: Observable<Show[]>;
constructor(showService: ShowService) {
public constructor(showService: ShowService) {
this.shows$ = showService.list$();
}
@@ -24,5 +24,4 @@ export class ListComponent {
public getPrivateSongs(songs: Show[]): Show[] {
return songs.filter(_ => !_.published);
}
}

View File

@@ -1,21 +1,24 @@
<div>
<app-card closeLink="/shows" heading="Neue Veranstaltung">
<div [formGroup]="form" class="split">
<mat-form-field appearance="outline">
<mat-label>Art der Veranstaltung</mat-label>
<mat-select formControlName="showType">
<mat-optgroup label="öffentlich">
<mat-option *ngFor="let key of showTypePublic" [value]="key">{{key|showType}}</mat-option>
<mat-option *ngFor="let key of showTypePublic" [value]="key">{{
key | showType
}}</mat-option>
</mat-optgroup>
<mat-optgroup label="privat">
<mat-option *ngFor="let key of showTypePrivate" [value]="key">{{key|showType}}</mat-option>
<mat-option *ngFor="let key of showTypePrivate" [value]="key">{{
key | showType
}}</mat-option>
</mat-optgroup>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Datum</mat-label>
<input [matDatepicker]="picker" formControlName="date" matInput>
<input [matDatepicker]="picker" formControlName="date" matInput/>
<mat-datepicker-toggle [for]="picker" matSuffix></mat-datepicker-toggle>
<mat-datepicker #picker touchUi></mat-datepicker>
</mat-form-field>

View File

@@ -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();
});
});

View File

@@ -10,7 +10,7 @@ import {faSave} from '@fortawesome/free-solid-svg-icons/faSave';
@Component({
selector: 'app-new',
templateUrl: './new.component.html',
styleUrls: ['./new.component.less']
styleUrls: ['./new.component.less'],
})
export class NewComponent implements OnInit {
public shows$: Observable<Show[]>;
@@ -19,7 +19,7 @@ export class NewComponent implements OnInit {
public form: FormGroup;
public faSave = faSave;
constructor(private showService: ShowService, showDataService: ShowDataService, private router: Router) {
public constructor(private showService: ShowService, showDataService: ShowDataService, private router: Router) {
this.shows$ = showDataService.list$();
}
@@ -30,7 +30,7 @@ export class NewComponent implements OnInit {
});
}
public async onSave() {
public async onSave(): Promise<void> {
this.form.markAllAsTouched();
if (!this.form.valid) {
return;

View File

@@ -6,11 +6,11 @@ describe('DocxService', () => {
let service: DocxService;
beforeEach(() => {
TestBed.configureTestingModule({});
void TestBed.configureTestingModule({});
service = TestBed.inject(DocxService);
});
it('should be created', () => {
expect(service).toBeTruthy();
void expect(service).toBeTruthy();
});
});

View File

@@ -24,29 +24,24 @@ export interface DownloadOptions {
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class DocxService {
constructor(
public constructor(
private showService: ShowService,
private showSongService: ShowSongService,
private songService: SongService,
private textRenderingService: TextRenderingService,
private userService: UserService,
private configService: ConfigService,
) {
}
private configService: ConfigService
) {}
public async create(showId: string, options: DownloadOptions = {}): Promise<any> {
public async create(showId: string, options: DownloadOptions = {}): Promise<void> {
const {show, songs, user, config} = await this.prepareData(showId);
const type = new ShowTypePipe().transform(show.showType);
const title = `${type} ${show.date.toDate().toLocaleDateString()}`;
const paragraphs = [
...this.renderTitle(title),
...this.renderSongs(songs, options, config),
];
const paragraphs = [...this.renderTitle(title), ...this.renderSongs(songs, options, config)];
const sections: ISectionOptions[] = [
{
@@ -56,11 +51,10 @@ export class DocxService {
},
},
children: paragraphs,
}
]
},
];
const document = this.prepareNewDocument(type, user.name, options, sections);
const blob = await Packer.toBlob(document);
// saveAs from FileSaver will download the file
@@ -72,7 +66,7 @@ export class DocxService {
creator: name,
title: type,
description: '... mit Beschreibung',
sections: sections,
sections,
styles: {
paragraphStyles: [
{
@@ -83,7 +77,8 @@ export class DocxService {
quickFormat: true,
run: options?.chordMode === 'hide' ? {} : {font: 'courier new'},
paragraph: {indent: {left: 0}},
}, {
},
{
id: 'licence',
name: 'Lizenz',
basedOn: 'Normal',
@@ -92,13 +87,13 @@ export class DocxService {
run: {size: 15, color: 'grey'},
paragraph: {indent: {left: 0}},
},
]
}
],
},
});
}
private renderSongs(songs: { showSong: ShowSong; song: Song; sections: Section[] }[], options: DownloadOptions, config: Config): Paragraph[] {
return songs.reduce((p, song) => [...p, ...this.renderSong(song.showSong, song.song, song.sections, options, config)], []);
private renderSongs(songs: {showSong: ShowSong; song: Song; sections: Section[]}[], options: DownloadOptions, config: Config): Paragraph[] {
return songs.reduce((p: Paragraph[], song) => [...p, ...this.renderSong(song.showSong, song.song, song.sections, options, config)], []);
}
private renderSong(showSong: ShowSong, song: Song, sections: Section[], options: DownloadOptions, config: Config): Paragraph[] {
@@ -106,15 +101,11 @@ export class DocxService {
const copyright = this.renderCopyright(song, options, config);
const songText = this.renderSongText(sections, options?.chordMode ?? showSong.chordMode);
return [
songTitle,
copyright,
...songText
].filter(_ => _);
return [songTitle, copyright, ...songText].filter(_ => _);
}
private renderSongText(sections: Section[], chordMode: ChordMode) {
return sections.reduce((p, section) => [...p, ...this.renderSection(section, chordMode)], []);
private renderSongText(sections: Section[], chordMode: ChordMode): Paragraph[] {
return sections.reduce((p: Paragraph[], section) => [...p, ...this.renderSection(section, chordMode)], []);
}
private renderSongTitle(song: Song): Paragraph {
@@ -137,9 +128,7 @@ export class DocxService {
const artist = song.artist ? song.artist + ', ' : '';
const termsOfUse = song.termsOfUse ? song.termsOfUse + ', ' : '';
const origin = song.origin ? song.origin + ', ' : '';
const licence = song.legalOwner === 'CCLI'
? 'CCLI-Liednummer: ' + song.legalOwnerId + ', CCLI-Lizenz: ' + config.ccliLicenseId
: 'CCLI-Liednummer: ' + song.legalOwnerId;
const licence = song.legalOwner === 'CCLI' ? 'CCLI-Liednummer: ' + song.legalOwnerId + ', CCLI-Lizenz: ' + config.ccliLicenseId : 'CCLI-Liednummer: ' + song.legalOwnerId;
return new Paragraph({
text: artist + label + termsOfUse + origin + licence,
@@ -147,7 +136,6 @@ export class DocxService {
});
}
private renderSection(section: Section, chordMode: ChordMode): Paragraph[] {
return section.lines
.filter(line => {
@@ -171,13 +159,11 @@ export class DocxService {
return new Paragraph({
text: line.text,
style: 'songtext',
spacing
spacing,
});
}
private renderTitle(type: string): Paragraph[] {
const songTitle = new Paragraph({
text: type,
heading: HeadingLevel.HEADING_1,
@@ -187,34 +173,39 @@ export class DocxService {
return [songTitle];
}
private async prepareData(showId: string): Promise<{ songs: ({ showSong: ShowSong, song: Song, sections: Section[] })[]; show: Show, user: User, config: Config }> {
private async prepareData(showId: string): Promise<{
songs: {showSong: ShowSong; song: Song; sections: Section[]}[];
show: Show;
user: User;
config: Config;
}> {
const show = await this.showService.read$(showId).pipe(first()).toPromise();
const user = await this.userService.getUserbyId(show.owner);
const config = await this.configService.get();
const showSongs = await this.showSongService.list(showId);
const songsAsync = await showSongs.map(async showSong => {
const songsAsync = showSongs.map(async showSong => {
const song = await this.songService.read(showSong.songId);
const sections = this.textRenderingService.parse(song.text, {
baseKey: showSong.keyOriginal,
targetKey: showSong.key
targetKey: showSong.key,
});
return {
showSong,
song,
sections
sections,
};
});
const songs = await Promise.all(songsAsync);
return {songs, show, user, config};
}
private saveAs(blob, fileName) {
const a = document.createElement('a') as any;
private saveAs(blob: Blob, fileName: string) {
const a = document.createElement('a');
document.body.appendChild(a);
a.setAttribute('target', '_self');
a.style = 'display: none';
a.style.display = 'none';
const url = window.URL.createObjectURL(blob);
a.href = url;

View File

@@ -3,10 +3,10 @@ import {TestBed} from '@angular/core/testing';
import {ShowDataService} from './show-data.service';
describe('ShowDataService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
beforeEach(() => void TestBed.configureTestingModule({}));
it('should be created', () => {
const service: ShowDataService = TestBed.get(ShowDataService);
expect(service).toBeTruthy();
const service: ShowDataService = TestBed.inject(ShowDataService);
void expect(service).toBeTruthy();
});
});

View File

@@ -2,17 +2,17 @@ import {Injectable} from '@angular/core';
import {Observable} from 'rxjs';
import {DbService} from '../../../services/db.service';
import {Show} from './show';
import {QueryFn} from '@angular/fire/firestore/interfaces';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class ShowDataService {
private collection = 'shows';
constructor(private dbService: DbService) {
}
public constructor(private dbService: DbService) {}
public list$ = (queryFn?): Observable<Show[]> => this.dbService.col$(this.collection, queryFn);
public list$ = (queryFn?: QueryFn): Observable<Show[]> => this.dbService.col$(this.collection, queryFn);
public read$ = (showId: string): Observable<Show | undefined> => this.dbService.doc$(`${this.collection}/${showId}`);
public update = async (showId: string, data: Partial<Show>): Promise<void> => await this.dbService.doc(`${this.collection}/${showId}`).update(data);
public add = async (data: Partial<Show>): Promise<string> => (await this.dbService.col(this.collection).add(data)).id;

View File

@@ -6,11 +6,11 @@ describe('ShowSongDataService', () => {
let service: ShowSongDataService;
beforeEach(() => {
TestBed.configureTestingModule({});
void TestBed.configureTestingModule({});
service = TestBed.inject(ShowSongDataService);
});
it('should be created', () => {
expect(service).toBeTruthy();
void expect(service).toBeTruthy();
});
});

View File

@@ -2,20 +2,21 @@ import {Injectable} from '@angular/core';
import {DbService} from '../../../services/db.service';
import {Observable} from 'rxjs';
import {ShowSong} from './show-song';
import {QueryFn} from '@angular/fire/firestore/interfaces';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class ShowSongDataService {
private collection = 'shows';
private subCollection = 'songs';
constructor(private dbService: DbService) {
}
public constructor(private dbService: DbService) {}
public list$ = (showId: string, queryFn?): Observable<ShowSong[]> => this.dbService.col$(`${this.collection}/${showId}/${this.subCollection}`, queryFn);
public list$ = (showId: string, queryFn?: QueryFn): Observable<ShowSong[]> => this.dbService.col$(`${this.collection}/${showId}/${this.subCollection}`, queryFn);
public read$ = (showId: string, songId: string): Observable<ShowSong | undefined> => this.dbService.doc$(`${this.collection}/${showId}/${this.subCollection}/${songId}`);
public update$ = async (showId: string, songId: string, data: Partial<ShowSong>): Promise<void> => await this.dbService.doc(`${this.collection}/${showId}/${this.subCollection}/${songId}`).update(data);
public update$ = async (showId: string, songId: string, data: Partial<ShowSong>): Promise<void> =>
await this.dbService.doc(`${this.collection}/${showId}/${this.subCollection}/${songId}`).update(data);
public delete = async (showId: string, songId: string): Promise<void> => await this.dbService.doc(`${this.collection}/${showId}/${this.subCollection}/${songId}`).delete();
public add = async (showId: string, data: Partial<ShowSong>): Promise<string> => (await this.dbService.col(`${this.collection}/${showId}/${this.subCollection}`).add(data)).id;
}

View File

@@ -6,11 +6,11 @@ describe('ShowSongService', () => {
let service: ShowSongService;
beforeEach(() => {
TestBed.configureTestingModule({});
void TestBed.configureTestingModule({});
service = TestBed.inject(ShowSongService);
});
it('should be created', () => {
expect(service).toBeTruthy();
void expect(service).toBeTruthy();
});
});

View File

@@ -7,16 +7,10 @@ import {first, take} from 'rxjs/operators';
import {UserService} from '../../../services/user/user.service';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class ShowSongService {
constructor(
private showSongDataService: ShowSongDataService,
private songDataService: SongDataService,
private userService: UserService,
) {
}
public constructor(private showSongDataService: ShowSongDataService, private songDataService: SongDataService, private userService: UserService) {}
public async new$(showId: string, songId: string, order: number, addedLive = false): Promise<string> {
const song = await this.songDataService.read$(songId).pipe(take(1)).toPromise();
@@ -27,7 +21,7 @@ export class ShowSongService {
key: song.key,
keyOriginal: song.key,
chordMode: user.chordMode,
addedLive
addedLive,
};
return await this.showSongDataService.add(showId, data);
}

View File

@@ -5,33 +5,40 @@ import {ShowDataService} from './show-data.service';
describe('ShowService', () => {
const mockShowDataService = {add: Promise.resolve(null)};
beforeEach(() => TestBed.configureTestingModule({
providers: [
{provide: ShowDataService, useValue: mockShowDataService}
]
}));
beforeEach(
() =>
void TestBed.configureTestingModule({
providers: [{provide: ShowDataService, useValue: mockShowDataService}],
})
);
ShowService.SHOW_TYPE_PUBLIC.forEach(type => {
it('should calc public flag for ' + type, async () => {
const service: ShowService = TestBed.get(ShowService);
const service: ShowService = TestBed.inject(ShowService);
const addSpy = spyOn(TestBed.inject(ShowDataService), 'add').and.returnValue(Promise.resolve('id'));
const id = await service.new$({showType: type});
expect(id).toBe('id');
expect(addSpy).toHaveBeenCalledWith({showType: type, public: true});
void expect(id).toBe('id');
void expect(addSpy).toHaveBeenCalledWith({
showType: type,
public: true,
});
});
});
ShowService.SHOW_TYPE_PRIVATE.forEach(type => {
it('should calc private flag for ' + type, async () => {
const service: ShowService = TestBed.get(ShowService);
const service: ShowService = TestBed.inject(ShowService);
const addSpy = spyOn(TestBed.inject(ShowDataService), 'add').and.returnValue(Promise.resolve('id'));
const id = await service.new$({showType: type});
expect(id).toBe('id');
expect(addSpy).toHaveBeenCalledWith({showType: type, public: false});
void expect(id).toBe('id');
void expect(addSpy).toHaveBeenCalledWith({
showType: type,
public: false,
});
});
});
});

View File

@@ -7,31 +7,28 @@ import {map, switchMap} from 'rxjs/operators';
import {User} from '../../../services/user/user';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class ShowService {
public static SHOW_TYPE = ['service-worship', 'service-praise', 'home-group-big', 'home-group', 'prayer-group', 'teens-group', 'kids-group', 'misc-public', 'misc-private'];
public static SHOW_TYPE_PUBLIC = ['service-worship', 'service-praise', 'home-group-big', 'teens-group', 'kids-group', 'misc-public'];
public static SHOW_TYPE_PRIVATE = ['home-group', 'prayer-group', 'misc-private',];
public static SHOW_TYPE_PRIVATE = ['home-group', 'prayer-group', 'misc-private'];
private user: User;
constructor(private showDataService: ShowDataService, private userService: UserService) {
userService.user$.subscribe(_ => this.user = _);
public constructor(private showDataService: ShowDataService, private userService: UserService) {
userService.user$.subscribe(_ => (this.user = _));
}
public read$ = (showId: string): Observable<Show> => this.showDataService.read$(showId);
public list$(publishedOnly: boolean = false): Observable<Show[]> {
public list$(publishedOnly = false): Observable<Show[]> {
return this.userService.user$.pipe(
switchMap(_ => this.showDataService.list$(), (user: User, shows: Show[]) => ({user, shows})),
map(_ => _.shows
.filter(_ => !_.archived)
.filter(show => show.published || (show.owner === _.user.id && !publishedOnly))
)
switchMap(
() => this.showDataService.list$(),
(user: User, shows: Show[]) => ({user, shows})
),
map(s => s.shows.filter(_ => !_.archived).filter(show => show.published || (show.owner === s.user.id && !publishedOnly)))
);
}
public update$ = async (showId: string, data: Partial<Show>): Promise<void> => this.showDataService.update(showId, data);

View File

@@ -14,6 +14,4 @@ export interface Show {
presentationSongId: string;
presentationSection: number;
presentationZoom: number;
}

View File

@@ -1,49 +1,82 @@
<div *ngIf="(show$|async) as show">
<div *ngIf="show$ | async as show">
<app-card
closeLink="../"
heading="{{show.showType|showType}}, {{show.date.toDate()|date:'dd.MM.yyyy'}} - {{getStatus(show)}}">
<i *ngIf="show.public">öffentliche Veranstaltung von
heading="{{ show.showType | showType }}, {{
show.date.toDate() | date: 'dd.MM.yyyy'
}} - {{ getStatus(show) }}"
>
<i *ngIf="show.public"
>öffentliche Veranstaltung von
<app-user-name [userId]="show.owner"></app-user-name>
</i>
<i *ngIf="!show.public">geschlossene Veranstaltung von
<i *ngIf="!show.public"
>geschlossene Veranstaltung von
<app-user-name [userId]="show.owner"></app-user-name>
</i>
<p *ngIf="!show.published">
<mat-checkbox [(ngModel)]="showText">Text anzeigen</mat-checkbox>
</p>
<div *ngIf="showSongs && songs" class="song-list">
<app-song *ngFor="let song of showSongs" [showId]="showId"
[showSong]="song"
[showSongs]="showSongs"
[showText]="showText"
[show]="show"
[song]="getSong(song.songId)"
class="song-row"
<app-song
*ngFor="let song of showSongs"
[Song]="getSong(song.songId)"
[showId]="showId"
[showSong]="song"
[showSongs]="showSongs"
[showText]="showText"
[show]="show"
class="song-row"
></app-song>
</div>
<app-add-song *ngIf="songs && !show.published" [showId]="showId" [showSongs]="showSongs"
[songs]="songs"></app-add-song>
<app-add-song
*ngIf="songs && !show.published"
[showId]="showId"
[showSongs]="showSongs"
[songs]="songs"
></app-add-song>
<app-button-row>
<ng-container *appOwner="show.owner">
<app-button (click)="onArchive(true)" *ngIf="!show.archived" [icon]="faBox">
<app-button
(click)="onArchive(true)"
*ngIf="!show.archived"
[icon]="faBox"
>
Archivieren
</app-button>
<app-button (click)="onArchive(false)" *ngIf="show.archived" [icon]="faBoxOpen">
<app-button
(click)="onArchive(false)"
*ngIf="show.archived"
[icon]="faBoxOpen"
>
Wiederherstellen
</app-button>
<app-button (click)="onPublish(true)" *ngIf="!show.published" [icon]="faPublish">
<app-button
(click)="onPublish(true)"
*ngIf="!show.published"
[icon]="faPublish"
>
Veröffentlichen
</app-button>
<app-button (click)="onPublish(false)" *ngIf="show.published" [icon]="faUnpublish">
<app-button
(click)="onPublish(false)"
*ngIf="show.published"
[icon]="faUnpublish"
>
Veröffentlichung zurückziehen
</app-button>
</ng-container>
<app-button [icon]="faDownload" [matMenuTriggerFor]="menu">Herunterladen</app-button>
<app-button [icon]="faDownload" [matMenuTriggerFor]="menu"
>Herunterladen
</app-button>
<mat-menu #menu="matMenu">
<app-button (click)="onDownload()" [icon]="faUser">Ablauf für Lobpreisleiter</app-button>
<app-button (click)="onDownloadHandout()" [icon]="faUsers">Handout mit Copyright Infos</app-button>
<app-button (click)="onDownload()" [icon]="faUser"
>Ablauf für Lobpreisleiter
</app-button>
<app-button (click)="onDownloadHandout()" [icon]="faUsers"
>Handout mit Copyright Infos
</app-button>
</mat-menu>
</app-button-row>
</app-card>

View File

@@ -6,12 +6,13 @@ describe('ShowComponent', () => {
let component: ShowComponent;
let fixture: ComponentFixture<ShowComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ShowComponent]
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [ShowComponent],
}).compileComponents();
})
.compileComponents();
}));
);
beforeEach(() => {
fixture = TestBed.createComponent(ShowComponent);
@@ -20,6 +21,6 @@ describe('ShowComponent', () => {
});
it('should create', () => {
expect(component).toBeTruthy();
void expect(component).toBeTruthy();
});
});

View File

@@ -20,7 +20,7 @@ import {faUsers} from '@fortawesome/free-solid-svg-icons/faUsers';
@Component({
selector: 'app-show',
templateUrl: './show.component.html',
styleUrls: ['./show.component.less']
styleUrls: ['./show.component.less'],
})
export class ShowComponent implements OnInit {
public show$: Observable<Show>;
@@ -37,29 +37,31 @@ export class ShowComponent implements OnInit {
public faUser = faUser;
public faUsers = faUsers;
constructor(
public constructor(
private activatedRoute: ActivatedRoute,
private showService: ShowService,
private songService: SongService,
private showSongService: ShowSongService,
private docxService: DocxService,
) {
}
private docxService: DocxService
) {}
ngOnInit(): void {
public ngOnInit(): void {
this.show$ = this.activatedRoute.params.pipe(
map(param => param.showId),
tap(_ => this.showId = _),
switchMap(showId => this.showService.read$(showId))
map((param: {showId: string}) => param.showId),
tap((_: string) => (this.showId = _)),
switchMap((showId: string) => this.showService.read$(showId))
);
this.activatedRoute.params.pipe(
map(param => param.showId),
switchMap(showId => this.showSongService.list$(showId)),
filter(_ => !!_)
).subscribe(_ => this.showSongs = _);
this.songService.list$().pipe(
filter(_ => !!_)
).subscribe(_ => this.songs = _);
this.activatedRoute.params
.pipe(
map((param: {showId: string}) => param.showId),
switchMap(showId => this.showSongService.list$(showId)),
filter(_ => !!_)
)
.subscribe(_ => (this.showSongs = _));
this.songService
.list$()
.pipe(filter(_ => !!_))
.subscribe(_ => (this.songs = _));
}
public getSong(songId: string): Song {
@@ -90,6 +92,9 @@ export class ShowComponent implements OnInit {
}
public async onDownloadHandout(): Promise<void> {
await this.docxService.create(this.showId, {chordMode: 'hide', copyright: true});
await this.docxService.create(this.showId, {
chordMode: 'hide',
copyright: true,
});
}
}

View File

@@ -1,21 +1,42 @@
<div *ngIf="_song">
<div *ngIf="show.published" class="title published">{{_song.title}}</div>
<div *ngIf="iSong">
<div *ngIf="show.published" class="title published">{{ iSong.title }}</div>
<div *ngIf="!show.published" class="song">
<app-menu-button (click)="reorder(true)" [icon]="faUp" class="btn-up btn-icon"></app-menu-button>
<app-menu-button (click)="reorder(false)" [icon]="faDown" class="btn-down btn-icon"></app-menu-button>
<span class="title">{{_song.title}}</span>
<app-menu-button
(click)="reorder(true)"
[icon]="faUp"
class="btn-up btn-icon"
></app-menu-button>
<app-menu-button
(click)="reorder(false)"
[icon]="faDown"
class="btn-down btn-icon"
></app-menu-button>
<span class="title">{{ iSong.title }}</span>
<span class="keys">
<span *ngIf="showSong.keyOriginal!==showSong.key">{{showSong.keyOriginal}}&nbsp;&nbsp;</span>
<mat-form-field *ngIf="keys" appearance="standard">
<span *ngIf="showSong.keyOriginal !== showSong.key"
>{{ showSong.keyOriginal }}&nbsp;&nbsp;</span
>
<mat-form-field *ngIf="keys" appearance="standard">
<mat-select [formControl]="keyFormControl">
<mat-option *ngFor="let key of keys" [value]="key">{{key}}</mat-option>
<mat-option *ngFor="let key of keys" [value]="key">{{
key
}}</mat-option>
</mat-select>
</mat-form-field>
</span>
<app-menu-button (click)="onDelete()" [icon]="faDelete" class="btn-delete btn-icon"></app-menu-button>
</span>
<app-menu-button
(click)="onDelete()"
[icon]="faDelete"
class="btn-delete btn-icon"
></app-menu-button>
</div>
<app-song-text (chordModeChanged)="onChordModeChanged($event)" *ngIf="showText || show.published"
[chordMode]="showSong.chordMode" [transpose]="{baseKey: showSong.keyOriginal, targetKey: showSong.key}"
[showSwitch]="!show.published" [text]="_song.text"></app-song-text>
<app-song-text
(chordModeChanged)="onChordModeChanged($event)"
*ngIf="showText || show.published"
[chordMode]="showSong.chordMode"
[showSwitch]="!show.published"
[text]="iSong.text"
[transpose]="{ baseKey: showSong.keyOriginal, targetKey: showSong.key }"
></app-song-text>
</div>

View File

@@ -6,12 +6,13 @@ describe('SongComponent', () => {
let component: SongComponent;
let fixture: ComponentFixture<SongComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [SongComponent]
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [SongComponent],
}).compileComponents();
})
.compileComponents();
}));
);
beforeEach(() => {
fixture = TestBed.createComponent(SongComponent);
@@ -20,6 +21,6 @@ describe('SongComponent', () => {
});
it('should create', () => {
expect(component).toBeTruthy();
void expect(component).toBeTruthy();
});
});

View File

@@ -13,7 +13,7 @@ import {Show} from '../../services/show';
@Component({
selector: 'app-song',
templateUrl: './song.component.html',
styleUrls: ['./song.component.less']
styleUrls: ['./song.component.less'],
})
export class SongComponent implements OnInit {
@Input() public show: Show;
@@ -22,30 +22,25 @@ export class SongComponent implements OnInit {
@Input() public showId: string;
@Input() public showText: boolean;
public keys: string[];
public faDelete = faTrash;
public faUp = faCaretUp;
public faDown = faCaretDown;
public keyFormControl: FormControl;
public iSong: Song;
constructor(
private showSongService: ShowSongService,
) {
}
public _song: Song;
public constructor(private showSongService: ShowSongService) {}
@Input()
public set song(song: Song) {
this._song = song;
this.keys = !!song ? getScale(song.key) : [];
public set Song(song: Song) {
this.iSong = song;
this.keys = song ? getScale(song.key) : [];
}
public ngOnInit(): void {
this.keyFormControl = new FormControl(this.showSong.key);
this.keyFormControl.valueChanges.subscribe(async value => {
await this.showSongService.update$(this.showId, this.showSong.id, {key: value});
this.keyFormControl.valueChanges.subscribe((value: string) => {
void this.showSongService.update$(this.showId, this.showSong.id, {key: value});
});
}
@@ -53,7 +48,6 @@ export class SongComponent implements OnInit {
await this.showSongService.delete$(this.showId, this.showSong.id);
}
public async reorder(up: boolean): Promise<void> {
if (up) {
await this.reorderUp();
@@ -63,7 +57,7 @@ export class SongComponent implements OnInit {
}
public async reorderUp(): Promise<void> {
const index = this.showSongs.findIndex(_ => _.songId === this._song.id);
const index = this.showSongs.findIndex(_ => _.songId === this.iSong.id);
if (index === 0) {
return;
}
@@ -71,12 +65,16 @@ export class SongComponent implements OnInit {
const song = this.showSongs[index];
const toggleSong = this.showSongs[index - 1];
await this.showSongService.update$(this.showId, song.id, {order: toggleSong.order});
await this.showSongService.update$(this.showId, toggleSong.id, {order: song.order});
await this.showSongService.update$(this.showId, song.id, {
order: toggleSong.order,
});
await this.showSongService.update$(this.showId, toggleSong.id, {
order: song.order,
});
}
public async reorderDown(): Promise<void> {
const index = this.showSongs.findIndex(_ => _.songId === this._song.id);
const index = this.showSongs.findIndex(_ => _.songId === this.iSong.id);
if (index === this.showSongs.length - 1) {
return;
}
@@ -84,11 +82,17 @@ export class SongComponent implements OnInit {
const song = this.showSongs[index];
const toggleSong = this.showSongs[index + 1];
await this.showSongService.update$(this.showId, song.id, {order: toggleSong.order});
await this.showSongService.update$(this.showId, toggleSong.id, {order: song.order});
await this.showSongService.update$(this.showId, song.id, {
order: toggleSong.order,
});
await this.showSongService.update$(this.showId, toggleSong.id, {
order: song.order,
});
}
public async onChordModeChanged(value: ChordMode): Promise<void> {
await this.showSongService.update$(this.showId, this.showSong.id, {chordMode: value});
await this.showSongService.update$(this.showId, this.showSong.id, {
chordMode: value,
});
}
}

View File

@@ -4,26 +4,24 @@ import {NewComponent} from './new/new.component';
import {ListComponent} from './list/list.component';
import {ShowComponent} from './show/show.component';
const routes: Routes = [
{
path: '',
pathMatch: 'full',
component: ListComponent
component: ListComponent,
},
{
path: 'new',
component: NewComponent
component: NewComponent,
},
{
path: ':showId',
component: ShowComponent
}
component: ShowComponent,
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
exports: [RouterModule],
})
export class ShowsRoutingModule {
}
export class ShowsRoutingModule {}

View File

@@ -29,7 +29,6 @@ import {OwnerModule} from '../../services/user/owner.module';
import {UserNameModule} from '../../services/user/user-name/user-name.module';
import {MatMenuModule} from '@angular/material/menu';
@NgModule({
declarations: [NewComponent, ListComponent, ListItemComponent, ShowComponent, SongComponent],
imports: [
@@ -57,7 +56,6 @@ import {MatMenuModule} from '@angular/material/menu';
OwnerModule,
UserNameModule,
MatMenuModule,
]
],
})
export class ShowsModule {
}
export class ShowsModule {}

View File

@@ -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();
});
});

View File

@@ -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'});
}
}

View File

@@ -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();
});
});

View File

@@ -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);

View File

@@ -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}`;
}

View File

@@ -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];

View File

@@ -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');
});
})
);
});

View File

@@ -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();
}

View File

@@ -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');
});
})
);
});

View File

@@ -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);
// }
}

View File

@@ -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');
});
});

View File

@@ -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 : [];
}
}

View File

@@ -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();
});
});

View File

@@ -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] : '');
}
}

View File

@@ -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();
});
});

View File

@@ -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);
}

View 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;
}
}

View 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>

View File

@@ -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();
});
});

View File

@@ -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);
}
}

View File

@@ -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> &nbsp;
</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> &nbsp;
</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> &nbsp;
</span>
</ng-container>
<span *ngIf="song.legalType==='open'" class="warning" matTooltip="rechtlicher Status ist ungeklärt"
matTooltipPosition="before"><fa-icon [icon]="faLegal"></fa-icon> &nbsp;</span>
<span
*ngIf="song.legalType === 'open'"
class="warning"
matTooltip="rechtlicher Status ist ungeklärt"
matTooltipPosition="before"
><fa-icon [icon]="faLegal"></fa-icon> &nbsp;</span
>
</div>
<div>{{song.key}}</div>
<div>{{ song.key }}</div>
</div>

View File

@@ -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();
});
});

View File

@@ -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() {
}
}

View File

@@ -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>

View File

@@ -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'}]);
}));
});

View File

@@ -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(_ => {

View File

@@ -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 {}

View File

@@ -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>

View File

@@ -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();
});
});

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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();
});
});

View File

@@ -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;

View File

@@ -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();
});
});

Some files were not shown because too many files have changed in this diff Show More