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": { "lint": {
"builder": "@angular-devkit/build-angular:tslint", "builder": "@angular-eslint/builder:lint",
"options": { "options": {
"tsConfig": [ "lintFilePatterns": [
"tsconfig.app.json", "src/**/*.ts",
"tsconfig.spec.json", "src/**/*.html"
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
] ]
} }
}, },
@@ -145,6 +141,7 @@
}, },
"defaultProject": "wgenerator", "defaultProject": "wgenerator",
"cli": { "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 {AppPage} from './app.po';
import { browser, logging } from 'protractor'; import {browser, logging} from 'protractor';
describe('workspace-project App', () => { describe('workspace-project App', () => {
let page: AppPage; let page: AppPage;
@@ -9,15 +9,17 @@ describe('workspace-project App', () => {
}); });
it('should display welcome message', () => { it('should display welcome message', () => {
page.navigateTo(); void page.navigateTo();
expect(page.getTitleText()).toEqual('wgenerator app is running!'); void expect(page.getTitleText()).toEqual('wgenerator app is running!');
}); });
afterEach(async () => { afterEach(async () => {
// Assert that there are no errors emitted from the browser // Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER); const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({ void expect(logs).not.toContain(
jasmine.objectContaining({
level: logging.Level.SEVERE, level: logging.Level.SEVERE,
} as logging.Entry)); } 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": { "scripts": {
"start": "ng serve --aot", "start": "ng serve --aot",
"build": "ng build", "build": "ng build",
"deploy": "ng build --prod && firebase deploy", "deploy": "npm i && ng build --prod && firebase deploy",
"test": "ng test", "test": "ng test",
"lint": "ng lint --fix", "lint": "ng lint --fix",
"postinstall": "ngcc" "postinstall": "ngcc"
@@ -39,13 +39,22 @@
"devDependencies": { "devDependencies": {
"@angular-devkit/architect": "0.1200.1", "@angular-devkit/architect": "0.1200.1",
"@angular-devkit/build-angular": "~12.0.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/cli": "~12.0.1",
"@angular/compiler-cli": "~12.0.1", "@angular/compiler-cli": "~12.0.1",
"@angular/language-service": "~12.0.1", "@angular/language-service": "~12.0.1",
"@types/jasmine": "~3.6.0", "@types/jasmine": "~3.6.0",
"@types/jasminewd2": "~2.0.3", "@types/jasminewd2": "~2.0.3",
"@types/node": "^12.12.36", "@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", "firebase-tools": "^7.12.0",
"fuzzy": "^0.1.3", "fuzzy": "^0.1.3",
"inquirer": "^6.2.2", "inquirer": "^6.2.2",
@@ -57,10 +66,10 @@
"karma-coverage-istanbul-reporter": "~3.0.2", "karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0", "karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0", "karma-jasmine-html-reporter": "^1.5.0",
"prettier": "^2.3.0",
"protractor": "~7.0.0", "protractor": "~7.0.0",
"swiper": "^6.6.2", "swiper": "^6.6.2",
"ts-node": "~7.0.0", "ts-node": "~7.0.0",
"tslint": "~6.1.0",
"typescript": "~4.2.4" "typescript": "~4.2.4"
} }
} }

View File

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

View File

@@ -7,7 +7,7 @@ const routes: Routes = [
{ {
path: '', path: '',
redirectTo: 'songs', redirectTo: 'songs',
pathMatch: 'full' pathMatch: 'full',
}, },
{ {
path: 'songs', path: 'songs',
@@ -15,8 +15,8 @@ const routes: Routes = [
canActivate: [AngularFireAuthGuard, RoleGuard], canActivate: [AngularFireAuthGuard, RoleGuard],
data: { data: {
authGuardPipe: () => redirectUnauthorizedTo(['user', 'login']), authGuardPipe: () => redirectUnauthorizedTo(['user', 'login']),
requiredRoles: ['user'] requiredRoles: ['user'],
} },
}, },
{ {
path: 'shows', path: 'shows',
@@ -24,8 +24,8 @@ const routes: Routes = [
canActivate: [AngularFireAuthGuard, RoleGuard], canActivate: [AngularFireAuthGuard, RoleGuard],
data: { data: {
authGuardPipe: () => redirectUnauthorizedTo(['user', 'login']), authGuardPipe: () => redirectUnauthorizedTo(['user', 'login']),
requiredRoles: ['leader'] requiredRoles: ['leader'],
} },
}, },
{ {
path: 'presentation', path: 'presentation',
@@ -33,12 +33,12 @@ const routes: Routes = [
canActivate: [AngularFireAuthGuard, RoleGuard], canActivate: [AngularFireAuthGuard, RoleGuard],
data: { data: {
authGuardPipe: () => redirectUnauthorizedTo(['user', 'login']), authGuardPipe: () => redirectUnauthorizedTo(['user', 'login']),
requiredRoles: ['presenter'] requiredRoles: ['presenter'],
} },
}, },
{ {
path: 'user', path: 'user',
loadChildren: () => import('./modules/user/user.module').then(m => m.UserModule) loadChildren: () => import('./modules/user/user.module').then(m => m.UserModule),
}, },
{ {
path: 'brand', path: 'brand',
@@ -47,12 +47,16 @@ const routes: Routes = [
{ {
path: 'guest', path: 'guest',
loadChildren: () => import('./modules/guest/guest.module').then(m => m.GuestModule), loadChildren: () => import('./modules/guest/guest.module').then(m => m.GuestModule),
} },
]; ];
@NgModule({ @NgModule({
imports: [RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules, relativeLinkResolution: 'legacy' })], imports: [
exports: [RouterModule] 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> <app-navigation></app-navigation>
<perfect-scrollbar #scrollbar (psScrollY)="onScoll($event)" [perfectScrollbar] <perfect-scrollbar
class="scroll" style="max-height: calc(100vh); width: 100%; overflow: hidden;" #scrollbar
(psScrollY)="onScoll($event)"
[perfectScrollbar]
class="scroll"
style="max-height: calc(100vh); width: 100%; overflow: hidden"
> >
<div [@fader]="o.isActivated ? o.activatedRoute : ''" class="content"> <div [@fader]="o.isActivated ? o.activatedRoute : ''" class="content">
<router-outlet #o="outlet"></router-outlet> <router-outlet #o="outlet"></router-outlet>

View File

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

View File

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

View File

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

View File

@@ -6,12 +6,13 @@ describe('BrandComponent', () => {
let component: BrandComponent; let component: BrandComponent;
let fixture: ComponentFixture<BrandComponent>; let fixture: ComponentFixture<BrandComponent>;
beforeEach(waitForAsync(() => { beforeEach(
TestBed.configureTestingModule({ waitForAsync(() => {
declarations: [BrandComponent] void TestBed.configureTestingModule({
declarations: [BrandComponent],
}).compileComponents();
}) })
.compileComponents(); );
}));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(BrandComponent); fixture = TestBed.createComponent(BrandComponent);
@@ -20,6 +21,6 @@ describe('BrandComponent', () => {
}); });
it('should create', () => { 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({ @Component({
selector: 'app-brand', selector: 'app-brand',
templateUrl: './brand.component.html', templateUrl: './brand.component.html',
styleUrls: ['./brand.component.less'] styleUrls: ['./brand.component.less'],
}) })
export class BrandComponent implements OnInit { export class BrandComponent {}
constructor() {
}
ngOnInit(): void {
}
}

View File

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

View File

@@ -1,8 +1,11 @@
<div class="frame"> <div class="frame">
<app-brand class="brand"></app-brand> <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="welcome">WILLKOMMEN</div>
<div class="name">{{user.name}}</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="roles">
Es wurden noch keine Berechtigungen zugeteilt, bitte wende Dich an den
Administrator!
</div>
</div> </div>
</div> </div>

View File

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

View File

@@ -6,12 +6,12 @@ import {User} from '../../../services/user/user';
@Component({ @Component({
selector: 'app-new-user', selector: 'app-new-user',
templateUrl: './new-user.component.html', templateUrl: './new-user.component.html',
styleUrls: ['./new-user.component.less'] styleUrls: ['./new-user.component.less'],
}) })
export class NewUserComponent { export class NewUserComponent {
public user$: Observable<User>; public user$: Observable<User>;
constructor(private userService: UserService) { public constructor(private userService: UserService) {
this.user$ = userService.user$; 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> <swiper>
<div *ngFor="let song of songs" class="song"> <div *ngFor="let song of songs" class="song">
<app-song-text [showSwitch]="false" [text]="song|async" <app-song-text
chordMode="hide"></app-song-text> [showSwitch]="false"
[text]="song | async"
chordMode="hide"
></app-song-text>
</div> </div>
</swiper> </swiper>
</div> </div>

View File

@@ -6,12 +6,13 @@ describe('GuestComponent', () => {
let component: GuestComponent; let component: GuestComponent;
let fixture: ComponentFixture<GuestComponent>; let fixture: ComponentFixture<GuestComponent>;
beforeEach(waitForAsync(() => { beforeEach(
TestBed.configureTestingModule({ waitForAsync(() => {
declarations: [GuestComponent] void TestBed.configureTestingModule({
declarations: [GuestComponent],
}).compileComponents();
}) })
.compileComponents(); );
}));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(GuestComponent); fixture = TestBed.createComponent(GuestComponent);
@@ -20,6 +21,6 @@ describe('GuestComponent', () => {
}); });
it('should create', () => { 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({ @Component({
selector: 'app-guest', selector: 'app-guest',
templateUrl: './guest.component.html', templateUrl: './guest.component.html',
styleUrls: ['./guest.component.less'] styleUrls: ['./guest.component.less'],
}) })
export class GuestComponent implements OnInit { export class GuestComponent implements OnInit {
public songs$: Observable<Observable<string>[]>; public songs$: Observable<Observable<string>[]>;
public constructor(private songService: SongService, private globalSettingsService: GlobalSettingsService, private showSongService: ShowSongService) {}
constructor(
private songService: SongService,
private globalSettingsService: GlobalSettingsService,
private showSongService: ShowSongService,
) {
}
public ngOnInit(): void { public ngOnInit(): void {
this.songs$ = this.globalSettingsService.get$.pipe( this.songs$ = this.globalSettingsService.get$.pipe(
map(_ => _.currentShow), map(_ => _.currentShow),
switchMap(_ => this.showSongService.list$(_)), switchMap(_ => this.showSongService.list$(_)),
map(_ => _ map(_ => _.sort((x, y) => x.order - y.order).map(showSong => this.songService.read$(showSong.songId).pipe(map(song => song.text))))
.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, scrollbar: false,
navigation: true, navigation: true,
pagination: false, pagination: false,
}; };
@NgModule({ @NgModule({
declarations: [GuestComponent], declarations: [GuestComponent],
imports: [ imports: [CommonModule, RouterModule.forChild([{path: '', component: GuestComponent}]), SwiperModule, SongTextModule],
CommonModule,
RouterModule.forChild([{path: '', component: GuestComponent}]),
SwiperModule,
SongTextModule
],
providers: [ providers: [
{ {
provide: SWIPER_CONFIG, 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.artist">{{ song.artist }}</p>
<p *ngIf="song.label">{{song.label}}</p> <p *ngIf="song.label">{{ song.label }}</p>
<p *ngIf="song.termsOfUse" class="terms-of-use">{{song.termsOfUse}}</p> <p *ngIf="song.termsOfUse" class="terms-of-use">{{ song.termsOfUse }}</p>
<p *ngIf="song.origin">{{song.origin}}</p> <p *ngIf="song.origin">{{ song.origin }}</p>
<div *ngIf="song.legalOwnerId"> <div *ngIf="song.legalOwnerId">
<p *ngIf="song.legalOwner==='CCLI' && config">CCLI-Liednummer {{song.legalOwnerId}}, <p *ngIf="song.legalOwner === 'CCLI' && config">
CCLI-Lizenznummer {{config.ccliLicenseId}}</p> CCLI-Liednummer {{ song.legalOwnerId }}, CCLI-Lizenznummer
<p *ngIf="song.legalOwner!=='CCLI'">Liednummer {{song.legalOwnerId}}</p> {{ config.ccliLicenseId }}
</p>
<p *ngIf="song.legalOwner !== 'CCLI'">Liednummer {{ song.legalOwnerId }}</p>
</div> </div>

View File

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

View File

@@ -5,7 +5,7 @@ import {Config} from '../../../../services/config';
@Component({ @Component({
selector: 'app-legal', selector: 'app-legal',
templateUrl: './legal.component.html', templateUrl: './legal.component.html',
styleUrls: ['./legal.component.less'] styleUrls: ['./legal.component.less'],
}) })
export class LegalComponent { export class LegalComponent {
@Input() public song: Song; @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 component: LogoComponent;
let fixture: ComponentFixture<LogoComponent>; let fixture: ComponentFixture<LogoComponent>;
beforeEach(waitForAsync(() => { beforeEach(
TestBed.configureTestingModule({ waitForAsync(() => {
declarations: [LogoComponent] void TestBed.configureTestingModule({
declarations: [LogoComponent],
}).compileComponents();
}) })
.compileComponents(); );
}));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(LogoComponent); fixture = TestBed.createComponent(LogoComponent);
@@ -20,6 +21,6 @@ describe('LogoComponent', () => {
}); });
it('should create', () => { 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({ @Component({
selector: 'app-logo', selector: 'app-logo',
templateUrl: './logo.component.html', templateUrl: './logo.component.html',
styleUrls: ['./logo.component.less'] styleUrls: ['./logo.component.less'],
}) })
export class LogoComponent implements OnInit { export class LogoComponent {}
constructor() {
}
ngOnInit(): void {
}
}

View File

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

View File

@@ -6,12 +6,13 @@ describe('MonitorComponent', () => {
let component: MonitorComponent; let component: MonitorComponent;
let fixture: ComponentFixture<MonitorComponent>; let fixture: ComponentFixture<MonitorComponent>;
beforeEach(waitForAsync(() => { beforeEach(
TestBed.configureTestingModule({ waitForAsync(() => {
declarations: [MonitorComponent] void TestBed.configureTestingModule({
declarations: [MonitorComponent],
}).compileComponents();
}) })
.compileComponents(); );
}));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(MonitorComponent); fixture = TestBed.createComponent(MonitorComponent);
@@ -20,6 +21,6 @@ describe('MonitorComponent', () => {
}); });
it('should create', () => { 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 {Observable} from 'rxjs';
import {ConfigService} from '../../../services/config.service'; import {ConfigService} from '../../../services/config.service';
import {songSwitch} from '../../../widget-modules/components/song-text/animation'; import {songSwitch} from '../../../widget-modules/components/song-text/animation';
import {Section} from '../../songs/services/section';
import {TextRenderingService} from '../../songs/services/text-rendering.service'; import {TextRenderingService} from '../../songs/services/text-rendering.service';
import {Show} from '../../shows/services/show';
@Component({ @Component({
selector: 'app-monitor', selector: 'app-monitor',
templateUrl: './monitor.component.html', templateUrl: './monitor.component.html',
styleUrls: ['./monitor.component.less'], styleUrls: ['./monitor.component.less'],
animations: [songSwitch] animations: [songSwitch],
}) })
export class MonitorComponent implements OnInit { export class MonitorComponent implements OnInit {
public song: Song; public song: Song;
@@ -25,35 +25,37 @@ export class MonitorComponent implements OnInit {
public index: number; public index: number;
public showType: string; public showType: string;
public date: Date; public date: Date;
private sections: Section[];
public config$: Observable<Config>; public config$: Observable<Config>;
constructor( // private sections: Section[];
public constructor(
private showService: ShowService, private showService: ShowService,
private songService: SongService, private songService: SongService,
private textRenderingService: TextRenderingService, private textRenderingService: TextRenderingService,
private globalSettingsService: GlobalSettingsService, private globalSettingsService: GlobalSettingsService,
private configService: ConfigService, private configService: ConfigService
) { ) {
this.config$ = configService.get$; this.config$ = configService.get$;
} }
ngOnInit(): void { public ngOnInit(): void {
this.globalSettingsService.get$.pipe( this.globalSettingsService.get$
.pipe(
map(_ => _.currentShow), map(_ => _.currentShow),
distinctUntilChanged(), distinctUntilChanged(),
tap(_ => this.currentShowId = _), tap(_ => (this.currentShowId = _)),
switchMap(_ => this.showService.read$(_)), switchMap(_ => this.showService.read$(_)),
tap(_ => this.showType = _.showType), tap(_ => (this.showType = _.showType)),
tap(_ => this.date = _.date.toDate()), tap(_ => (this.date = _.date.toDate())),
tap(_ => this.songId = _.presentationSongId), tap(_ => (this.songId = _.presentationSongId)),
tap(_ => this.index = _.presentationSection), tap(_ => (this.index = _.presentationSection)),
tap(_ => this.zoom = _.presentationZoom ?? 30), tap(_ => (this.zoom = _.presentationZoom ?? 30)),
switchMap(_ => this.songService.read$(_.presentationSongId)) switchMap((_: Show) => this.songService.read$(_.presentationSongId))
).subscribe((_: Song) => { )
.subscribe((_: Song) => {
this.song = _; this.song = _;
this.sections = this.textRenderingService.parse(_.text, null); // 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 {RemoteComponent} from './remote/remote.component';
import {MonitorComponent} from './monitor/monitor.component'; import {MonitorComponent} from './monitor/monitor.component';
const routes: Routes = [ const routes: Routes = [
{ {
path: '', path: '',
pathMatch: 'full', pathMatch: 'full',
redirectTo: 'remote' redirectTo: 'remote',
}, },
{ {
path: 'remote', path: 'remote',
component: RemoteComponent component: RemoteComponent,
}, },
{ {
path: 'monitor', path: 'monitor',
component: MonitorComponent component: MonitorComponent,
} },
]; ];
@NgModule({ @NgModule({
imports: [RouterModule.forChild(routes)], 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 {AddSongModule} from '../../widget-modules/components/add-song/add-song.module';
import {LogoComponent} from './monitor/logo/logo.component'; import {LogoComponent} from './monitor/logo/logo.component';
@NgModule({ @NgModule({
declarations: [MonitorComponent, RemoteComponent, LegalComponent, LogoComponent], declarations: [MonitorComponent, RemoteComponent, LegalComponent, LogoComponent],
exports: [ exports: [RemoteComponent],
RemoteComponent
],
imports: [ imports: [
CommonModule, CommonModule,
PresentationRoutingModule, PresentationRoutingModule,
@@ -38,8 +35,7 @@ import {LogoComponent} from './monitor/logo/logo.component';
MatSliderModule, MatSliderModule,
FormsModule, FormsModule,
AddSongModule, 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> <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-label>Veranstaltung</mat-label>
<mat-select [formControl]="showControl"> <mat-select [formControl]="showControl">
<mat-option *ngFor="let show of shows" [value]="show.id"> <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-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<ng-container *ngIf="!progress"> <ng-container *ngIf="!progress">
<div *ngIf="show" class="song-parts padding-bottom"> <div *ngIf="show" class="song-parts padding-bottom">
<div (click)="onSectionClick('title', -1)" <div
[class.active]="show.presentationSongId==='title'" (click)="onSectionClick('title', -1)"
class="song-part"> [class.active]="show.presentationSongId === 'title'"
class="song-part"
>
<div class="head">Veranstaltung</div> <div class="head">Veranstaltung</div>
</div> </div>
<div (click)="onSectionClick('empty', -1)" <div
[class.active]="show.presentationSongId==='empty'" (click)="onSectionClick('empty', -1)"
class="song-part"> [class.active]="show.presentationSongId === 'empty'"
class="song-part"
>
<div class="head">Leer</div> <div class="head">Leer</div>
</div> </div>
</div> </div>
<div *ngFor="let song of presentationSongs" @fade class="song"> <div *ngFor="let song of presentationSongs" @fade class="song">
<div [class.active]="show.presentationSongId===song.id" class="title song-part"> <div
<div (click)="onSectionClick(song.id, -1)" class="head">{{song.title}}</div> [class.active]="show.presentationSongId === song.id"
class="title song-part"
>
<div (click)="onSectionClick(song.id, -1)" class="head">
{{ song.title }}
</div>
</div> </div>
<div *ngIf="show" class="song-parts"> <div *ngIf="show" class="song-parts">
<div (click)="onSectionClick(song.id, i)" *ngFor="let section of song.sections; index as i" <div
[class.active]="show.presentationSongId===song.id && show.presentationSection===i" (click)="onSectionClick(song.id, i)"
class="song-part"> *ngFor="let section of song.sections; index as i"
<div class="head">{{section.type|sectionType}} {{section.number + 1}}</div> [class.active]="
<div class="fragment">{{getFirstLine(section)}}</div> 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> </div>
</div> </div>
@@ -56,11 +75,13 @@
</mat-slider> </mat-slider>
</div> </div>
<app-add-song
<app-add-song *ngIf="show" [addedLive]="true" [showId]="currentShowId" [showSongs]="showSongs" *ngIf="show"
[songs]="songs"></app-add-song> [addedLive]="true"
[showId]="currentShowId"
[showSongs]="showSongs"
[songs]="songs"
></app-add-song>
</ng-container> </ng-container>
</app-card> </app-card>
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,12 +6,13 @@ describe('ListComponent', () => {
let component: ListComponent; let component: ListComponent;
let fixture: ComponentFixture<ListComponent>; let fixture: ComponentFixture<ListComponent>;
beforeEach(waitForAsync(() => { beforeEach(
TestBed.configureTestingModule({ waitForAsync(() => {
declarations: [ListComponent] void TestBed.configureTestingModule({
declarations: [ListComponent],
}).compileComponents();
}) })
.compileComponents(); );
}));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(ListComponent); fixture = TestBed.createComponent(ListComponent);
@@ -20,6 +21,6 @@ describe('ListComponent', () => {
}); });
it('should create', () => { 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', selector: 'app-list',
templateUrl: './list.component.html', templateUrl: './list.component.html',
styleUrls: ['./list.component.less'], styleUrls: ['./list.component.less'],
animations: [fade] animations: [fade],
}) })
export class ListComponent { export class ListComponent {
public shows$: Observable<Show[]>; public shows$: Observable<Show[]>;
constructor(showService: ShowService) { public constructor(showService: ShowService) {
this.shows$ = showService.list$(); this.shows$ = showService.list$();
} }
@@ -24,5 +24,4 @@ export class ListComponent {
public getPrivateSongs(songs: Show[]): Show[] { public getPrivateSongs(songs: Show[]): Show[] {
return songs.filter(_ => !_.published); return songs.filter(_ => !_.published);
} }
} }

View File

@@ -1,21 +1,24 @@
<div> <div>
<app-card closeLink="/shows" heading="Neue Veranstaltung"> <app-card closeLink="/shows" heading="Neue Veranstaltung">
<div [formGroup]="form" class="split"> <div [formGroup]="form" class="split">
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Art der Veranstaltung</mat-label> <mat-label>Art der Veranstaltung</mat-label>
<mat-select formControlName="showType"> <mat-select formControlName="showType">
<mat-optgroup label="öffentlich"> <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>
<mat-optgroup label="privat"> <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-optgroup>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Datum</mat-label> <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-toggle [for]="picker" matSuffix></mat-datepicker-toggle>
<mat-datepicker #picker touchUi></mat-datepicker> <mat-datepicker #picker touchUi></mat-datepicker>
</mat-form-field> </mat-form-field>

View File

@@ -6,12 +6,13 @@ describe('NewComponent', () => {
let component: NewComponent; let component: NewComponent;
let fixture: ComponentFixture<NewComponent>; let fixture: ComponentFixture<NewComponent>;
beforeEach(waitForAsync(() => { beforeEach(
TestBed.configureTestingModule({ waitForAsync(() => {
declarations: [NewComponent] void TestBed.configureTestingModule({
declarations: [NewComponent],
}).compileComponents();
}) })
.compileComponents(); );
}));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(NewComponent); fixture = TestBed.createComponent(NewComponent);
@@ -20,6 +21,6 @@ describe('NewComponent', () => {
}); });
it('should create', () => { 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({ @Component({
selector: 'app-new', selector: 'app-new',
templateUrl: './new.component.html', templateUrl: './new.component.html',
styleUrls: ['./new.component.less'] styleUrls: ['./new.component.less'],
}) })
export class NewComponent implements OnInit { export class NewComponent implements OnInit {
public shows$: Observable<Show[]>; public shows$: Observable<Show[]>;
@@ -19,7 +19,7 @@ export class NewComponent implements OnInit {
public form: FormGroup; public form: FormGroup;
public faSave = faSave; 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$(); this.shows$ = showDataService.list$();
} }
@@ -30,7 +30,7 @@ export class NewComponent implements OnInit {
}); });
} }
public async onSave() { public async onSave(): Promise<void> {
this.form.markAllAsTouched(); this.form.markAllAsTouched();
if (!this.form.valid) { if (!this.form.valid) {
return; return;

View File

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

View File

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

View File

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

View File

@@ -2,17 +2,17 @@ import {Injectable} from '@angular/core';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
import {DbService} from '../../../services/db.service'; import {DbService} from '../../../services/db.service';
import {Show} from './show'; import {Show} from './show';
import {QueryFn} from '@angular/fire/firestore/interfaces';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class ShowDataService { export class ShowDataService {
private collection = 'shows'; 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 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 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; 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; let service: ShowSongDataService;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({}); void TestBed.configureTestingModule({});
service = TestBed.inject(ShowSongDataService); service = TestBed.inject(ShowSongDataService);
}); });
it('should be created', () => { 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 {DbService} from '../../../services/db.service';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
import {ShowSong} from './show-song'; import {ShowSong} from './show-song';
import {QueryFn} from '@angular/fire/firestore/interfaces';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class ShowSongDataService { export class ShowSongDataService {
private collection = 'shows'; private collection = 'shows';
private subCollection = 'songs'; 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 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 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; 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; let service: ShowSongService;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({}); void TestBed.configureTestingModule({});
service = TestBed.inject(ShowSongService); service = TestBed.inject(ShowSongService);
}); });
it('should be created', () => { 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'; import {UserService} from '../../../services/user/user.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class ShowSongService { export class ShowSongService {
public constructor(private showSongDataService: ShowSongDataService, private songDataService: SongDataService, private userService: UserService) {}
constructor(
private showSongDataService: ShowSongDataService,
private songDataService: SongDataService,
private userService: UserService,
) {
}
public async new$(showId: string, songId: string, order: number, addedLive = false): Promise<string> { public async new$(showId: string, songId: string, order: number, addedLive = false): Promise<string> {
const song = await this.songDataService.read$(songId).pipe(take(1)).toPromise(); const song = await this.songDataService.read$(songId).pipe(take(1)).toPromise();
@@ -27,7 +21,7 @@ export class ShowSongService {
key: song.key, key: song.key,
keyOriginal: song.key, keyOriginal: song.key,
chordMode: user.chordMode, chordMode: user.chordMode,
addedLive addedLive,
}; };
return await this.showSongDataService.add(showId, data); return await this.showSongDataService.add(showId, data);
} }

View File

@@ -5,33 +5,40 @@ import {ShowDataService} from './show-data.service';
describe('ShowService', () => { describe('ShowService', () => {
const mockShowDataService = {add: Promise.resolve(null)}; const mockShowDataService = {add: Promise.resolve(null)};
beforeEach(() => TestBed.configureTestingModule({ beforeEach(
providers: [ () =>
{provide: ShowDataService, useValue: mockShowDataService} void TestBed.configureTestingModule({
] providers: [{provide: ShowDataService, useValue: mockShowDataService}],
})); })
);
ShowService.SHOW_TYPE_PUBLIC.forEach(type => { ShowService.SHOW_TYPE_PUBLIC.forEach(type => {
it('should calc public flag for ' + type, async () => { 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 addSpy = spyOn(TestBed.inject(ShowDataService), 'add').and.returnValue(Promise.resolve('id'));
const id = await service.new$({showType: type}); const id = await service.new$({showType: type});
expect(id).toBe('id'); void expect(id).toBe('id');
expect(addSpy).toHaveBeenCalledWith({showType: type, public: true}); void expect(addSpy).toHaveBeenCalledWith({
showType: type,
public: true,
});
}); });
}); });
ShowService.SHOW_TYPE_PRIVATE.forEach(type => { ShowService.SHOW_TYPE_PRIVATE.forEach(type => {
it('should calc private flag for ' + type, async () => { 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 addSpy = spyOn(TestBed.inject(ShowDataService), 'add').and.returnValue(Promise.resolve('id'));
const id = await service.new$({showType: type}); const id = await service.new$({showType: type});
expect(id).toBe('id'); void expect(id).toBe('id');
expect(addSpy).toHaveBeenCalledWith({showType: type, public: false}); 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'; import {User} from '../../../services/user/user';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class ShowService { 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 = ['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_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; private user: User;
constructor(private showDataService: ShowDataService, private userService: UserService) { public constructor(private showDataService: ShowDataService, private userService: UserService) {
userService.user$.subscribe(_ => this.user = _); userService.user$.subscribe(_ => (this.user = _));
} }
public read$ = (showId: string): Observable<Show> => this.showDataService.read$(showId); 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( return this.userService.user$.pipe(
switchMap(_ => this.showDataService.list$(), (user: User, shows: Show[]) => ({user, shows})), switchMap(
map(_ => _.shows () => this.showDataService.list$(),
.filter(_ => !_.archived) (user: User, shows: Show[]) => ({user, shows})
.filter(show => show.published || (show.owner === _.user.id && !publishedOnly)) ),
) 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); 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; presentationSongId: string;
presentationSection: number; presentationSection: number;
presentationZoom: number; presentationZoom: number;
} }

View File

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

View File

@@ -6,12 +6,13 @@ describe('ShowComponent', () => {
let component: ShowComponent; let component: ShowComponent;
let fixture: ComponentFixture<ShowComponent>; let fixture: ComponentFixture<ShowComponent>;
beforeEach(waitForAsync(() => { beforeEach(
TestBed.configureTestingModule({ waitForAsync(() => {
declarations: [ShowComponent] void TestBed.configureTestingModule({
declarations: [ShowComponent],
}).compileComponents();
}) })
.compileComponents(); );
}));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(ShowComponent); fixture = TestBed.createComponent(ShowComponent);
@@ -20,6 +21,6 @@ describe('ShowComponent', () => {
}); });
it('should create', () => { 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({ @Component({
selector: 'app-show', selector: 'app-show',
templateUrl: './show.component.html', templateUrl: './show.component.html',
styleUrls: ['./show.component.less'] styleUrls: ['./show.component.less'],
}) })
export class ShowComponent implements OnInit { export class ShowComponent implements OnInit {
public show$: Observable<Show>; public show$: Observable<Show>;
@@ -37,29 +37,31 @@ export class ShowComponent implements OnInit {
public faUser = faUser; public faUser = faUser;
public faUsers = faUsers; public faUsers = faUsers;
constructor( public constructor(
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
private showService: ShowService, private showService: ShowService,
private songService: SongService, private songService: SongService,
private showSongService: ShowSongService, private showSongService: ShowSongService,
private docxService: DocxService, private docxService: DocxService
) { ) {}
}
ngOnInit(): void { public ngOnInit(): void {
this.show$ = this.activatedRoute.params.pipe( this.show$ = this.activatedRoute.params.pipe(
map(param => param.showId), map((param: {showId: string}) => param.showId),
tap(_ => this.showId = _), tap((_: string) => (this.showId = _)),
switchMap(showId => this.showService.read$(showId)) switchMap((showId: string) => this.showService.read$(showId))
); );
this.activatedRoute.params.pipe( this.activatedRoute.params
map(param => param.showId), .pipe(
map((param: {showId: string}) => param.showId),
switchMap(showId => this.showSongService.list$(showId)), switchMap(showId => this.showSongService.list$(showId)),
filter(_ => !!_) filter(_ => !!_)
).subscribe(_ => this.showSongs = _); )
this.songService.list$().pipe( .subscribe(_ => (this.showSongs = _));
filter(_ => !!_) this.songService
).subscribe(_ => this.songs = _); .list$()
.pipe(filter(_ => !!_))
.subscribe(_ => (this.songs = _));
} }
public getSong(songId: string): Song { public getSong(songId: string): Song {
@@ -90,6 +92,9 @@ export class ShowComponent implements OnInit {
} }
public async onDownloadHandout(): Promise<void> { 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="iSong">
<div *ngIf="show.published" class="title published">{{_song.title}}</div> <div *ngIf="show.published" class="title published">{{ iSong.title }}</div>
<div *ngIf="!show.published" class="song"> <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
<app-menu-button (click)="reorder(false)" [icon]="faDown" class="btn-down btn-icon"></app-menu-button> (click)="reorder(true)"
<span class="title">{{_song.title}}</span> [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 class="keys">
<span *ngIf="showSong.keyOriginal!==showSong.key">{{showSong.keyOriginal}}&nbsp;&nbsp;</span> <span *ngIf="showSong.keyOriginal !== showSong.key"
>{{ showSong.keyOriginal }}&nbsp;&nbsp;</span
>
<mat-form-field *ngIf="keys" appearance="standard"> <mat-form-field *ngIf="keys" appearance="standard">
<mat-select [formControl]="keyFormControl"> <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-select>
</mat-form-field> </mat-form-field>
</span> </span>
<app-menu-button (click)="onDelete()" [icon]="faDelete" class="btn-delete btn-icon"></app-menu-button> <app-menu-button
(click)="onDelete()"
[icon]="faDelete"
class="btn-delete btn-icon"
></app-menu-button>
</div> </div>
<app-song-text (chordModeChanged)="onChordModeChanged($event)" *ngIf="showText || show.published" <app-song-text
[chordMode]="showSong.chordMode" [transpose]="{baseKey: showSong.keyOriginal, targetKey: showSong.key}" (chordModeChanged)="onChordModeChanged($event)"
[showSwitch]="!show.published" [text]="_song.text"></app-song-text> *ngIf="showText || show.published"
[chordMode]="showSong.chordMode"
[showSwitch]="!show.published"
[text]="iSong.text"
[transpose]="{ baseKey: showSong.keyOriginal, targetKey: showSong.key }"
></app-song-text>
</div> </div>

View File

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

View File

@@ -13,7 +13,7 @@ import {Show} from '../../services/show';
@Component({ @Component({
selector: 'app-song', selector: 'app-song',
templateUrl: './song.component.html', templateUrl: './song.component.html',
styleUrls: ['./song.component.less'] styleUrls: ['./song.component.less'],
}) })
export class SongComponent implements OnInit { export class SongComponent implements OnInit {
@Input() public show: Show; @Input() public show: Show;
@@ -22,30 +22,25 @@ export class SongComponent implements OnInit {
@Input() public showId: string; @Input() public showId: string;
@Input() public showText: boolean; @Input() public showText: boolean;
public keys: string[]; public keys: string[];
public faDelete = faTrash; public faDelete = faTrash;
public faUp = faCaretUp; public faUp = faCaretUp;
public faDown = faCaretDown; public faDown = faCaretDown;
public keyFormControl: FormControl; public keyFormControl: FormControl;
public iSong: Song;
constructor( public constructor(private showSongService: ShowSongService) {}
private showSongService: ShowSongService,
) {
}
public _song: Song;
@Input() @Input()
public set song(song: Song) { public set Song(song: Song) {
this._song = song; this.iSong = song;
this.keys = !!song ? getScale(song.key) : []; this.keys = song ? getScale(song.key) : [];
} }
public ngOnInit(): void { public ngOnInit(): void {
this.keyFormControl = new FormControl(this.showSong.key); this.keyFormControl = new FormControl(this.showSong.key);
this.keyFormControl.valueChanges.subscribe(async value => { this.keyFormControl.valueChanges.subscribe((value: string) => {
await this.showSongService.update$(this.showId, this.showSong.id, {key: value}); 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); await this.showSongService.delete$(this.showId, this.showSong.id);
} }
public async reorder(up: boolean): Promise<void> { public async reorder(up: boolean): Promise<void> {
if (up) { if (up) {
await this.reorderUp(); await this.reorderUp();
@@ -63,7 +57,7 @@ export class SongComponent implements OnInit {
} }
public async reorderUp(): Promise<void> { 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) { if (index === 0) {
return; return;
} }
@@ -71,12 +65,16 @@ export class SongComponent implements OnInit {
const song = this.showSongs[index]; const song = this.showSongs[index];
const toggleSong = this.showSongs[index - 1]; const toggleSong = this.showSongs[index - 1];
await this.showSongService.update$(this.showId, song.id, {order: toggleSong.order}); await this.showSongService.update$(this.showId, song.id, {
await this.showSongService.update$(this.showId, toggleSong.id, {order: song.order}); order: toggleSong.order,
});
await this.showSongService.update$(this.showId, toggleSong.id, {
order: song.order,
});
} }
public async reorderDown(): Promise<void> { 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) { if (index === this.showSongs.length - 1) {
return; return;
} }
@@ -84,11 +82,17 @@ export class SongComponent implements OnInit {
const song = this.showSongs[index]; const song = this.showSongs[index];
const toggleSong = this.showSongs[index + 1]; const toggleSong = this.showSongs[index + 1];
await this.showSongService.update$(this.showId, song.id, {order: toggleSong.order}); await this.showSongService.update$(this.showId, song.id, {
await this.showSongService.update$(this.showId, toggleSong.id, {order: song.order}); order: toggleSong.order,
});
await this.showSongService.update$(this.showId, toggleSong.id, {
order: song.order,
});
} }
public async onChordModeChanged(value: ChordMode): Promise<void> { 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 {ListComponent} from './list/list.component';
import {ShowComponent} from './show/show.component'; import {ShowComponent} from './show/show.component';
const routes: Routes = [ const routes: Routes = [
{ {
path: '', path: '',
pathMatch: 'full', pathMatch: 'full',
component: ListComponent component: ListComponent,
}, },
{ {
path: 'new', path: 'new',
component: NewComponent component: NewComponent,
}, },
{ {
path: ':showId', path: ':showId',
component: ShowComponent component: ShowComponent,
} },
]; ];
@NgModule({ @NgModule({
imports: [RouterModule.forChild(routes)], 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 {UserNameModule} from '../../services/user/user-name/user-name.module';
import {MatMenuModule} from '@angular/material/menu'; import {MatMenuModule} from '@angular/material/menu';
@NgModule({ @NgModule({
declarations: [NewComponent, ListComponent, ListItemComponent, ShowComponent, SongComponent], declarations: [NewComponent, ListComponent, ListItemComponent, ShowComponent, SongComponent],
imports: [ imports: [
@@ -57,7 +56,6 @@ import {MatMenuModule} from '@angular/material/menu';
OwnerModule, OwnerModule,
UserNameModule, UserNameModule,
MatMenuModule, 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'; import {FileDataService} from './file-data.service';
describe('FileDataService', () => { describe('FileDataService', () => {
beforeEach(() => TestBed.configureTestingModule({})); beforeEach(() => void TestBed.configureTestingModule({}));
it('should be created', () => { it('should be created', () => {
const service: FileDataService = TestBed.get(FileDataService); const service: FileDataService = TestBed.inject(FileDataService);
expect(service).toBeTruthy(); void expect(service).toBeTruthy();
}); });
}); });

View File

@@ -5,12 +5,10 @@ import {FileServer} from './fileServer';
import {DbService} from '../../../services/db.service'; import {DbService} from '../../../services/db.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class FileDataService { export class FileDataService {
public constructor(private db: DbService) {}
constructor(private db: DbService) {
}
public async set(songId: string, file: FileServer): Promise<string> { public async set(songId: string, file: FileServer): Promise<string> {
const songRef = this.db.doc('songs/' + songId); const songRef = this.db.doc('songs/' + songId);
@@ -27,8 +25,5 @@ export class FileDataService {
public read$(songId: string): Observable<File[]> { public read$(songId: string): Observable<File[]> {
const songRef = this.db.doc('songs/' + songId); const songRef = this.db.doc('songs/' + songId);
return songRef.collection<File>('files').valueChanges({idField: 'id'}); return songRef.collection<File>('files').valueChanges({idField: 'id'});
} }
} }

View File

@@ -6,11 +6,11 @@ describe('FileService', () => {
let service: FileService; let service: FileService;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({}); void TestBed.configureTestingModule({});
service = TestBed.inject(FileService); service = TestBed.inject(FileService);
}); });
it('should be created', () => { 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'; import {FileDataService} from './file-data.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class FileService { export class FileService {
public constructor(private storage: AngularFireStorage, private fileDataService: FileDataService) {}
constructor(
private storage: AngularFireStorage,
private fileDataService: FileDataService
) {
}
public getDownloadUrl(path: string): Observable<string> { public getDownloadUrl(path: string): Observable<string> {
const ref = this.storage.ref(path); 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); const ref = this.storage.ref(path);
await ref.delete().toPromise(); await ref.delete().toPromise();
await this.fileDataService.delete(songId, fileId); await this.fileDataService.delete(songId, fileId);

View File

@@ -1,5 +1,4 @@
export class FileBase { export class FileBase {
protected basePath = '/attachments'; 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 = [ export const KEYS: string[] = [
'C#', 'C', 'Db', 'D#', 'D', 'Eb', 'E', 'F#', 'F', 'Gb', 'G#', 'G', 'Ab', 'A#', 'A', 'B', 'H', 'C#',
'c#', 'c', 'db', 'd#', 'd', 'eb', 'e', 'f#', 'f', 'gb', 'g#', 'g', 'ab', 'a#', 'a', 'b', 'h' 'C',
]; 'Db',
export const KEYS_MAJOR_FLAT = [ 'D#',
'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'H', 'D',
]; 'Eb',
export const KEYS_MAJOR_B = [ 'E',
'C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'B', 'H', 'F#',
]; 'F',
export const KEYS_MINOR_FLAT = [ 'Gb',
'c', 'c#', 'd', 'd#', 'e', 'f', 'f#', 'g', 'g#', 'a', 'a#', 'h', 'G#',
]; 'G',
export const KEYS_MINOR_B = [ 'Ab',
'c', 'db', 'd', 'eb', 'e', 'f', 'gb', 'g', 'ab', 'a', 'b', 'h', '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[][] } = { const scaleTypeAssignment: {[key: string]: string[][]} = {
'C': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT], C: [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
'C#': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT], 'C#': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
'Db': [KEYS_MAJOR_B, KEYS_MINOR_B], Db: [KEYS_MAJOR_B, KEYS_MINOR_B],
'D': [KEYS_MAJOR_B, KEYS_MINOR_B], D: [KEYS_MAJOR_B, KEYS_MINOR_B],
'D#': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT], 'D#': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
'Eb': [KEYS_MAJOR_B, KEYS_MINOR_B], Eb: [KEYS_MAJOR_B, KEYS_MINOR_B],
'E': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT], E: [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
'F': [KEYS_MAJOR_B, KEYS_MINOR_B], F: [KEYS_MAJOR_B, KEYS_MINOR_B],
'F#': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT], 'F#': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
'Gb': [KEYS_MAJOR_B, KEYS_MINOR_B], Gb: [KEYS_MAJOR_B, KEYS_MINOR_B],
'G': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT], G: [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
'G#': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT], 'G#': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
'Ab': [KEYS_MAJOR_B, KEYS_MINOR_B], Ab: [KEYS_MAJOR_B, KEYS_MINOR_B],
'A': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT], A: [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
'A#': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT], 'A#': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
'B': [KEYS_MAJOR_B, KEYS_MINOR_B], B: [KEYS_MAJOR_B, KEYS_MINOR_B],
'H': [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT], H: [KEYS_MAJOR_FLAT, KEYS_MINOR_FLAT],
'c': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT], c: [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
'c#': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT], 'c#': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
'db': [KEYS_MINOR_B, KEYS_MAJOR_B], db: [KEYS_MINOR_B, KEYS_MAJOR_B],
'd': [KEYS_MINOR_B, KEYS_MAJOR_B], d: [KEYS_MINOR_B, KEYS_MAJOR_B],
'd#': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT], 'd#': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
'eb': [KEYS_MINOR_B, KEYS_MAJOR_B], eb: [KEYS_MINOR_B, KEYS_MAJOR_B],
'e': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT], e: [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
'f': [KEYS_MINOR_B, KEYS_MAJOR_B], f: [KEYS_MINOR_B, KEYS_MAJOR_B],
'f#': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT], 'f#': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
'gb': [KEYS_MINOR_B, KEYS_MAJOR_B], gb: [KEYS_MINOR_B, KEYS_MAJOR_B],
'g': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT], g: [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
'g#': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT], 'g#': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
'ab': [KEYS_MINOR_B, KEYS_MAJOR_B], ab: [KEYS_MINOR_B, KEYS_MAJOR_B],
'a': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT], a: [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
'a#': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT], 'a#': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT],
'b': [KEYS_MINOR_B, KEYS_MAJOR_B], b: [KEYS_MINOR_B, KEYS_MAJOR_B],
'h': [KEYS_MINOR_FLAT, KEYS_MAJOR_FLAT], 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,
}; };
export const scaleMapping = { const scaleAssignment: {[key: string]: string[]} = {
'C': 'C', 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♯', 'C#': 'C♯',
'Db': 'D♭', Db: 'D♭',
'D': 'D', D: 'D',
'D#': 'D♯', 'D#': 'D♯',
'Eb': 'E♭', Eb: 'E♭',
'E': 'E', E: 'E',
'F': 'F', F: 'F',
'F#': 'F♯', 'F#': 'F♯',
'Gb': 'D♭', Gb: 'D♭',
'G': 'G', G: 'G',
'G#': 'G♯', 'G#': 'G♯',
'Ab': 'A♭', Ab: 'A♭',
'A': 'A', A: 'A',
'A#': 'A♯', 'A#': 'A♯',
'B': 'B', B: 'B',
'H': 'H', H: 'H',
'c': 'c', c: 'c',
'c#': 'c♯', 'c#': 'c♯',
'db': 'd♭', db: 'd♭',
'd': 'd', d: 'd',
'd#': 'd♯', 'd#': 'd♯',
'eb': 'e♭', eb: 'e♭',
'e': 'e', e: 'e',
'f': 'f', f: 'f',
'f#': 'f♯', 'f#': 'f♯',
'gb': 'g♭', gb: 'g♭',
'g': 'g', g: 'g',
'g#': 'g♯', 'g#': 'g♯',
'ab': 'a♭', ab: 'a♭',
'a': 'a', a: 'a',
'a#': 'a♯', 'a#': 'a♯',
'b': 'b', b: 'b',
'h': 'h', h: 'h',
}; };
export const getScale = (key: string): string[] => scaleAssignment[key]; export const getScale = (key: string): string[] => scaleAssignment[key];
export const getScaleType = (key: string): string[][] => scaleTypeAssignment[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'; import {of} from 'rxjs';
describe('SongDataService', () => { describe('SongDataService', () => {
const songs = [{title: 'title1'}];
const songs = [
{title: 'title1'}
];
const angularFirestoreCollection = { const angularFirestoreCollection = {
valueChanges: () => of(songs) valueChanges: () => of(songs),
}; };
const mockAngularFirestore = { const mockAngularFirestore = {
collection: () => angularFirestoreCollection collection: () => angularFirestoreCollection,
}; };
beforeEach(() => TestBed.configureTestingModule({ beforeEach(
providers: [ () =>
{provide: AngularFirestore, useValue: mockAngularFirestore} void TestBed.configureTestingModule({
] providers: [{provide: AngularFirestore, useValue: mockAngularFirestore}],
})); })
);
it('should be created', () => { it('should be created', () => {
const service: SongDataService = TestBed.get(SongDataService); const service: SongDataService = TestBed.inject(SongDataService);
expect(service).toBeTruthy(); void expect(service).toBeTruthy();
}); });
it('should list songs', waitForAsync(() => { it(
const service: SongDataService = TestBed.get(SongDataService); 'should list songs',
waitForAsync(() => {
const service: SongDataService = TestBed.inject(SongDataService);
service.list$().subscribe(s => { service.list$().subscribe(s => {
expect(s).toEqual([ void expect(s[0].title).toEqual('title1');
{title: 'title1'} });
] as any); })
}
); );
}));
}); });

View File

@@ -4,19 +4,16 @@ import {Observable} from 'rxjs';
import {DbService} from '../../../services/db.service'; import {DbService} from '../../../services/db.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class SongDataService { export class SongDataService {
private collection = 'songs'; private collection = 'songs';
constructor(private dbService: DbService) { public constructor(private dbService: DbService) {}
}
public list$ = (): Observable<Song[]> => this.dbService.col$(this.collection); public list$ = (): Observable<Song[]> => this.dbService.col$(this.collection);
public read$ = (songId: string): Observable<Song | undefined> => this.dbService.doc$(this.collection + '/' + songId); 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 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 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(); 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'; import {of} from 'rxjs';
describe('SongService', () => { describe('SongService', () => {
const songs = [{title: 'title1'}];
const songs = [
{title: 'title1'}
];
const mockSongDataService = { const mockSongDataService = {
list: () => of(songs) list: () => of(songs),
}; };
beforeEach(() => TestBed.configureTestingModule({ beforeEach(
providers: [ () =>
{provide: SongDataService, useValue: mockSongDataService} void TestBed.configureTestingModule({
] providers: [{provide: SongDataService, useValue: mockSongDataService}],
})); })
);
it('should be created', () => { it('should be created', () => {
const service: SongService = TestBed.get(SongService); const service: SongService = TestBed.inject(SongService);
expect(service).toBeTruthy(); void expect(service).toBeTruthy();
}); });
it('should list songs', waitForAsync(() => { it(
const service: SongService = TestBed.get(SongService); 'should list songs',
waitForAsync(() => {
const service: SongService = TestBed.inject(SongService);
service.list$().subscribe(s => { service.list$().subscribe(s => {
expect(s).toEqual([ void expect(s[0].title).toEqual('title1');
{title: 'title1'}
] as any);
}); });
})); })
);
}); });

View File

@@ -2,31 +2,30 @@ import {Injectable} from '@angular/core';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
import {Song} from './song'; import {Song} from './song';
import {SongDataService} from './song-data.service'; import {SongDataService} from './song-data.service';
import {first, tap} from 'rxjs/operators'; import {first} from 'rxjs/operators';
import {UserService} from '../../../services/user/user.service'; import {UserService} from '../../../services/user/user.service';
import * as firebase from 'firebase'; import * as firebase from 'firebase';
import Timestamp = firebase.firestore.Timestamp; import Timestamp = firebase.firestore.Timestamp;
declare var importCCLI: any; // declare let importCCLI: any;
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class SongService { export class SongService {
public static TYPES = ['Praise', 'Worship']; public static TYPES = ['Praise', 'Worship'];
public static STATUS = ['draft', 'set', 'final']; public static STATUS = ['draft', 'set', 'final'];
public static LEGAL_OWNER = ['CCLI', 'other']; public static LEGAL_OWNER = ['CCLI', 'other'];
public static LEGAL_TYPE = ['open', 'allowed']; public static LEGAL_TYPE = ['open', 'allowed'];
private list: Song[]; // private list: Song[];
constructor(private songDataService: SongDataService, private userService: UserService) { public constructor(private songDataService: SongDataService, private userService: UserService) {
importCCLI = (songs: Song[]) => this.updateFromCLI(songs); // 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): Observable<Song | undefined> => this.songDataService.read$(songId);
public read = (songId: string): Promise<Song | undefined> => this.read$(songId).pipe(first()).toPromise(); public read = (songId: string): Promise<Song | undefined> => this.read$(songId).pipe(first()).toPromise();
@@ -38,38 +37,41 @@ export class SongService {
await this.songDataService.update$(songId, {...data, edits}); await this.songDataService.update$(songId, {...data, edits});
} }
public async new(number: number, title: string): Promise<string> { public async new(songNumber: number, title: string): Promise<string> {
return await this.songDataService.add({number, title, status: 'draft', legalType: 'open'}); return await this.songDataService.add({
number: songNumber,
title,
status: 'draft',
legalType: 'open',
});
} }
public async delete(songId: string): Promise<void> { public async delete(songId: string): Promise<void> {
await this.songDataService.delete(songId); await this.songDataService.delete(songId);
} }
// https://www.csvjson.com/csv2json // https://www.csvjson.com/csv2json
private async updateFromCLI(songs: Song[]) { // private async updateFromCLI(songs: Song[]) {
const mapped = songs.map(_ => ({ // const mapped = songs.map(_ => ({
number: _.number, // number: _.number,
legalType: _.legalType === 'ja' ? 'allowed' : 'open', // legalType: _.legalType === 'ja' ? 'allowed' : 'open',
legalOwner: _.legalOwner === 'ja' ? 'CCLI' : 'other', // legalOwner: _.legalOwner === 'ja' ? 'CCLI' : 'other',
title: _.title, // title: _.title,
legalOwnerId: _.legalOwnerId, // legalOwnerId: _.legalOwnerId,
origin: _.origin, // origin: _.origin,
artist: _.artist, // artist: _.artist,
comment: _.comment // comment: _.comment,
})); // }));
const promises = this.list.map(async _ => { // const promises = this.list.map(async _ => {
// tslint:disable-next-line:triple-equals // // eslint-disable-next-line eqeqeq
const mappedSongs = mapped.filter(f => f.number == _.number); // const mappedSongs = mapped.filter(f => f.number == _.number);
if (mappedSongs.length === 1) { // if (mappedSongs.length === 1) {
const mappedSong = mappedSongs[0]; // const mappedSong = mappedSongs[0];
const id = _.id; // const id = _.id;
return await this.update$(id, mappedSong); // return await this.update$(id, mappedSong);
} // }
}); // });
//
await Promise.all(promises); // await Promise.all(promises);
} // }
} }

View File

@@ -1,6 +1,7 @@
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
import {TextRenderingService} from './text-rendering.service';
import {LineType, SectionType, TextRenderingService} from './text-rendering.service'; import {LineType} from './line-type';
import {SectionType} from './section-type';
describe('TextRenderingService', () => { describe('TextRenderingService', () => {
const testText = `Strophe const testText = `Strophe
@@ -23,60 +24,59 @@ Bridge
Cool bridge without any chords Cool bridge without any chords
`; `;
beforeEach(() => void TestBed.configureTestingModule({}));
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => { it('should be created', () => {
const service: TextRenderingService = TestBed.get(TextRenderingService); const service: TextRenderingService = TestBed.inject(TextRenderingService);
expect(service).toBeTruthy(); void expect(service).toBeTruthy();
}); });
it('should parse section types', () => { it('should parse section types', () => {
const service: TextRenderingService = TestBed.get(TextRenderingService); const service: TextRenderingService = TestBed.inject(TextRenderingService);
const sections = service.parse(testText, null); const sections = service.parse(testText, null);
expect(sections[0].type).toBe(SectionType.Verse); void expect(sections[0].type).toBe(SectionType.Verse);
expect(sections[0].number).toBe(0); void expect(sections[0].number).toBe(0);
expect(sections[1].type).toBe(SectionType.Verse); void expect(sections[1].type).toBe(SectionType.Verse);
expect(sections[1].number).toBe(1); void expect(sections[1].number).toBe(1);
expect(sections[2].type).toBe(SectionType.Chorus); void expect(sections[2].type).toBe(SectionType.Chorus);
expect(sections[2].number).toBe(0); void expect(sections[2].number).toBe(0);
expect(sections[3].type).toBe(SectionType.Bridge); void expect(sections[3].type).toBe(SectionType.Bridge);
expect(sections[3].number).toBe(0); void expect(sections[3].number).toBe(0);
}); });
it('should parse text lines', () => { it('should parse text lines', () => {
const service: TextRenderingService = TestBed.get(TextRenderingService); const service: TextRenderingService = TestBed.inject(TextRenderingService);
const sections = service.parse(testText, null); const sections = service.parse(testText, null);
expect(sections[0].lines[1].type).toBe(LineType.text); void expect(sections[0].lines[1].type).toBe(LineType.text);
expect(sections[0].lines[1].text).toBe('Text Line 1-1'); void expect(sections[0].lines[1].text).toBe('Text Line 1-1');
expect(sections[0].lines[3].type).toBe(LineType.text); void expect(sections[0].lines[3].type).toBe(LineType.text);
expect(sections[0].lines[3].text).toBe('Text Line 2-1'); void expect(sections[0].lines[3].text).toBe('Text Line 2-1');
expect(sections[1].lines[1].type).toBe(LineType.text); void expect(sections[1].lines[1].type).toBe(LineType.text);
expect(sections[1].lines[1].text).toBe('Text Line 1-2'); void expect(sections[1].lines[1].text).toBe('Text Line 1-2');
expect(sections[1].lines[3].type).toBe(LineType.text); void expect(sections[1].lines[3].type).toBe(LineType.text);
expect(sections[1].lines[3].text).toBe('Text Line 2-2'); void expect(sections[1].lines[3].text).toBe('Text Line 2-2');
expect(sections[2].lines[1].type).toBe(LineType.text); void expect(sections[2].lines[1].type).toBe(LineType.text);
expect(sections[2].lines[1].text).toBe('and the chorus'); void expect(sections[2].lines[1].text).toBe('and the chorus');
expect(sections[3].lines[0].type).toBe(LineType.text); void expect(sections[3].lines[0].type).toBe(LineType.text);
expect(sections[3].lines[0].text).toBe('Cool bridge without any chords'); void expect(sections[3].lines[0].text).toBe('Cool bridge without any chords');
}); });
it('should parse chord lines', () => { it('should parse chord lines', () => {
const service: TextRenderingService = TestBed.inject(TextRenderingService); const service: TextRenderingService = TestBed.inject(TextRenderingService);
const sections = service.parse(testText, null); const sections = service.parse(testText, null);
expect(sections[0].lines[0].type).toBe(LineType.chord); void expect(sections[0].lines[0].type).toBe(LineType.chord);
expect(sections[0].lines[0].text).toBe('C D E F G A H'); void expect(sections[0].lines[0].text).toBe('C D E F G A H');
expect(sections[0].lines[2].type).toBe(LineType.chord); void 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'); void 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); void expect(sections[1].lines[0].type).toBe(LineType.chord);
expect(sections[1].lines[0].text).toBe('C D E F G A H'); void expect(sections[1].lines[0].text).toBe('C D E F G A H');
expect(sections[1].lines[2].type).toBe(LineType.chord); void 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'); void 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); void 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[2].lines[0].text).toBe('c c# db c7 cmaj7 c/e');
// 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: 1, position: 0},
{chord: 'c#', length: 2, position: 2}, {chord: 'c#', length: 2, position: 2},
{chord: 'db', length: 2, position: 5}, {chord: 'db', length: 2, position: 5},
@@ -92,10 +92,9 @@ Cool bridge without any chords
g# F# E g# F# E g# F# E g# F# E
text`; text`;
const sections = service.parse(text, null); const sections = service.parse(text, null);
expect(sections[0].lines[0].type).toBe(LineType.chord); void expect(sections[0].lines[0].type).toBe(LineType.chord);
expect(sections[0].lines[0].text).toBe('g# F# E g# F# E'); void expect(sections[0].lines[0].text).toBe('g# F# E g# F# E');
expect(sections[0].lines[1].type).toBe(LineType.text); void expect(sections[0].lines[1].type).toBe(LineType.text);
expect(sections[0].lines[1].text).toBe('text'); void expect(sections[0].lines[1].text).toBe('text');
}); });
}); });

View File

@@ -8,13 +8,12 @@ import {Chord} from './chord';
import {Line} from './line'; import {Line} from './line';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class TextRenderingService { export class TextRenderingService {
private regexSection = /(Strophe|Refrain|Bridge)/; private regexSection = /(Strophe|Refrain|Bridge)/;
constructor(private transposeService: TransposeService) { public constructor(private transposeService: TransposeService) {}
}
public parse(text: string, transpose: TransposeMode): Section[] { public parse(text: string, transpose: TransposeMode): Section[] {
if (!text) { if (!text) {
@@ -28,12 +27,15 @@ export class TextRenderingService {
}; };
return arrayOfLines.reduce((array, line) => { return arrayOfLines.reduce((array, line) => {
const type = this.getSectionTypeOfLine(line); const type = this.getSectionTypeOfLine(line);
if (line.match(this.regexSection)) { if (this.regexSection.exec(line)) {
return [...array, { return [
...array,
{
type, type,
number: indices[type]++, number: indices[type]++,
lines: [] lines: [],
}]; },
];
} }
array[array.length - 1].lines.push(this.getLineOfLineText(line, transpose)); array[array.length - 1].lines.push(this.getLineOfLineText(line, transpose));
return array; return array;
@@ -49,16 +51,14 @@ export class TextRenderingService {
const type = hasMatches ? LineType.chord : LineType.text; const type = hasMatches ? LineType.chord : LineType.text;
const line = {type, text, chords: hasMatches ? cords : undefined}; const line = {type, text, chords: hasMatches ? cords : undefined};
return transpose return transpose ? this.transposeService.transpose(line, transpose.baseKey, transpose.targetKey) : this.transposeService.renderChords(line);
? this.transposeService.transpose(line, transpose.baseKey, transpose.targetKey)
: this.transposeService.renderChords(line);
} }
private getSectionTypeOfLine(line: string): SectionType { private getSectionTypeOfLine(line: string): SectionType {
if (!line) { if (!line) {
return null; return null;
} }
const match = line.match(this.regexSection); const match = this.regexSection.exec(line);
if (!match || match.length < 2) { if (!match || match.length < 2) {
return null; return null;
} }
@@ -74,11 +74,12 @@ export class TextRenderingService {
} }
private readChords(chordLine: string): Chord[] { private readChords(chordLine: string): Chord[] {
let match; let match: string[];
const chords: Chord[] = []; const chords: Chord[] = [];
// https://regex101.com/r/68jMB8/5 // 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) { while ((match = regex.exec(chordLine)) !== null) {
const chord: Chord = { const chord: Chord = {
@@ -101,5 +102,4 @@ export class TextRenderingService {
const isChrod = chordCount * 1.2 > lineCount; const isChrod = chordCount * 1.2 > lineCount;
return isChrod ? chords : []; return isChrod ? chords : [];
} }
} }

View File

@@ -6,7 +6,7 @@ describe('TransposeService', () => {
let service: TransposeService; let service: TransposeService;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({}); void TestBed.configureTestingModule({});
service = TestBed.inject(TransposeService); service = TestBed.inject(TransposeService);
}); });
@@ -15,6 +15,6 @@ describe('TransposeService', () => {
const map = service.getMap('D', distance); const map = service.getMap('D', distance);
console.log(map); 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 {Chord} from './chord';
import {Line} from './line'; import {Line} from './line';
type TransposeMap = {[key: string]: string};
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class TransposeService { export class TransposeService {
public transpose(line: Line, baseKey: string, targetKey: string): Line { public transpose(line: Line, baseKey: string, targetKey: string): Line {
if (line.type !== LineType.chord) { if (line.type !== LineType.chord) {
return line; return line;
@@ -33,13 +34,10 @@ export class TransposeService {
public getDistance(baseKey: string, targetKey: string): number { public getDistance(baseKey: string, targetKey: string): number {
const scale = getScaleType(baseKey); const scale = getScaleType(baseKey);
return scale ? ( return scale ? (scale[0].indexOf(targetKey) - scale[0].indexOf(baseKey) ?? scale[1].indexOf(targetKey) - scale[1].indexOf(baseKey)) % 12 : 0;
(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); const scale = getScaleType(baseKey);
if (!scale) { if (!scale) {
return null; return null;
@@ -59,10 +57,14 @@ export class TransposeService {
return map; return map;
} }
private transposeChord(chord: Chord, map: {}): Chord { private transposeChord(chord: Chord, map: TransposeMap): Chord {
const translatedChord = map[chord.chord]; const translatedChord = map[chord.chord];
const translatedSlashChord = chord.slashChord ? map[chord.slashChord] : null; 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 { private renderLine(chords: Chord[]): string {
@@ -83,9 +85,6 @@ export class TransposeService {
} }
private renderChord(chord: Chord) { private renderChord(chord: Chord) {
return ( return scaleMapping[chord.chord] + (chord.add ? chord.add : '') + (chord.slashChord ? '/' + scaleMapping[chord.slashChord] : '');
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'; import {UploadService} from './upload.service';
describe('UploadServiceService', () => { describe('UploadServiceService', () => {
beforeEach(() => TestBed.configureTestingModule({})); beforeEach(() => void TestBed.configureTestingModule({}));
it('should be created', () => { it('should be created', () => {
const service: UploadService = TestBed.get(UploadService); const service: UploadService = TestBed.inject(UploadService);
expect(service).toBeTruthy(); void expect(service).toBeTruthy();
}); });
}); });

View File

@@ -6,17 +6,15 @@ import {finalize} from 'rxjs/operators';
import {FileBase} from './fileBase'; import {FileBase} from './fileBase';
import {FileServer} from './fileServer'; import {FileServer} from './fileServer';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class UploadService extends FileBase { export class UploadService extends FileBase {
public constructor(private fileDataService: FileDataService, private angularFireStorage: AngularFireStorage) {
constructor(private fileDataService: FileDataService, private angularFireStorage: AngularFireStorage) {
super(); super();
} }
public async pushUpload(songId: string, upload: Upload) { public pushUpload(songId: string, upload: Upload): void {
const directory = this.directory(songId); const directory = this.directory(songId);
const filePath = `${directory}/${upload.file.name}`; const filePath = `${directory}/${upload.file.name}`;
upload.path = directory; upload.path = directory;
@@ -24,20 +22,18 @@ export class UploadService extends FileBase {
const ref = this.angularFireStorage.ref(filePath); const ref = this.angularFireStorage.ref(filePath);
const task = ref.put(upload.file); const task = ref.put(upload.file);
task.percentageChanges().subscribe(percent => upload.progress = percent); task.percentageChanges().subscribe(percent => (upload.progress = percent));
task.snapshotChanges().pipe( task
finalize(() => { .snapshotChanges()
this.saveFileData(songId, upload); .pipe(finalize(() => void this.saveFileData(songId, upload)))
}) .subscribe();
).subscribe();
} }
private async saveFileData(songId: string, upload: Upload) { private async saveFileData(songId: string, upload: Upload) {
const file: FileServer = { const file: FileServer = {
name: upload.file.name, name: upload.file.name,
path: upload.path, path: upload.path,
createdAt: new Date() createdAt: new Date(),
}; };
await this.fileDataService.set(songId, file); await this.fileDataService.set(songId, file);
} }

View File

@@ -1,13 +1,12 @@
export class Upload { 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; public constructor(file: File) {
file: Upload;
name: string;
path: string;
progress: number;
createdAt: Date = new Date();
constructor(file: Upload) {
this.file = file; this.file = file;
} }
} }

View File

@@ -1,8 +1,7 @@
<div [formGroup]="filterFormGroup"> <div [formGroup]="filterFormGroup">
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Titel oder Text</mat-label> <mat-label>Titel oder Text</mat-label>
<input formControlName="q" matInput> <input formControlName="q" matInput/>
</mat-form-field> </mat-form-field>
<div class="third"> <div class="third">
@@ -10,16 +9,19 @@
<mat-label>Typ</mat-label> <mat-label>Typ</mat-label>
<mat-select formControlName="type"> <mat-select formControlName="type">
<mat-option [value]="null">- kein Filter -</mat-option> <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-select>
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Tonart</mat-label> <mat-label>Tonart</mat-label>
<mat-select formControlName="key"> <mat-select formControlName="key">
<mat-option [value]="null">- kein Filter -</mat-option> <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-select>
</mat-form-field> </mat-form-field>
@@ -27,7 +29,9 @@
<mat-label>Rechtlicher Status</mat-label> <mat-label>Rechtlicher Status</mat-label>
<mat-select formControlName="legalType"> <mat-select formControlName="legalType">
<mat-option [value]="null">- kein Filter -</mat-option> <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-select>
</mat-form-field> </mat-form-field>
@@ -35,10 +39,12 @@
<mat-label>Attribute</mat-label> <mat-label>Attribute</mat-label>
<mat-select formControlName="flag"> <mat-select formControlName="flag">
<mat-option [value]="null">- kein Filter -</mat-option> <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-select>
</mat-form-field> </mat-form-field>
</div> </div>
<i>Anzahl der Suchergebnisse: {{songs.length}}</i> <i>Anzahl der Suchergebnisse: {{ songs.length }}</i>
</div> </div>

View File

@@ -6,12 +6,13 @@ describe('FilterComponent', () => {
let component: FilterComponent; let component: FilterComponent;
let fixture: ComponentFixture<FilterComponent>; let fixture: ComponentFixture<FilterComponent>;
beforeEach(waitForAsync(() => { beforeEach(
TestBed.configureTestingModule({ waitForAsync(() => {
declarations: [FilterComponent] void TestBed.configureTestingModule({
declarations: [FilterComponent],
}).compileComponents();
}) })
.compileComponents(); );
}));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(FilterComponent); fixture = TestBed.createComponent(FilterComponent);
@@ -20,6 +21,6 @@ describe('FilterComponent', () => {
}); });
it('should create', () => { 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 {ActivatedRoute, Router} from '@angular/router';
import {FormBuilder, FormGroup} from '@angular/forms'; import {FormBuilder, FormGroup} from '@angular/forms';
import {SongService} from '../../services/song.service'; import {SongService} from '../../services/song.service';
@@ -9,18 +9,17 @@ import {KEYS} from '../../services/key.helper';
@Component({ @Component({
selector: 'app-filter', selector: 'app-filter',
templateUrl: './filter.component.html', templateUrl: './filter.component.html',
styleUrls: ['./filter.component.less'] styleUrls: ['./filter.component.less'],
}) })
export class FilterComponent implements OnInit { export class FilterComponent {
public filterFormGroup: FormGroup; public filterFormGroup: FormGroup;
@Input() route: string; @Input() public route: string;
@Input() songs: Song[]; @Input() public songs: Song[];
public types = SongService.TYPES; public types = SongService.TYPES;
public legalType = SongService.LEGAL_TYPE; public legalType = SongService.LEGAL_TYPE;
public keys = KEYS; public keys = KEYS;
constructor(private router: Router, activatedRoute: ActivatedRoute, fb: FormBuilder) { public constructor(private router: Router, activatedRoute: ActivatedRoute, fb: FormBuilder) {
this.filterFormGroup = fb.group({ this.filterFormGroup = fb.group({
q: '', q: '',
type: '', type: '',
@@ -30,32 +29,18 @@ export class FilterComponent implements OnInit {
}); });
activatedRoute.queryParams.subscribe((filterValues: FilterValues) => { activatedRoute.queryParams.subscribe((filterValues: FilterValues) => {
if (filterValues.q) { if (filterValues.q) this.filterFormGroup.controls.q.setValue(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.type) { if (filterValues.legalType) this.filterFormGroup.controls.legalType.setValue(filterValues.legalType);
this.filterFormGroup.controls.type.setValue(filterValues.type); if (filterValues.flag) this.filterFormGroup.controls.flag.setValue(filterValues.flag);
}
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.q.valueChanges.subscribe(_ => void this.filerValueChanged('q', _));
this.filterFormGroup.controls.key.valueChanges.subscribe(_ => this.filerValueChanged('key', _)); this.filterFormGroup.controls.key.valueChanges.subscribe(_ => void this.filerValueChanged('key', _));
this.filterFormGroup.controls.type.valueChanges.subscribe(_ => this.filerValueChanged('type', _)); this.filterFormGroup.controls.type.valueChanges.subscribe(_ => void this.filerValueChanged('type', _));
this.filterFormGroup.controls.legalType.valueChanges.subscribe(_ => this.filerValueChanged('legalType', _)); this.filterFormGroup.controls.legalType.valueChanges.subscribe(_ => void this.filerValueChanged('legalType', _));
this.filterFormGroup.controls.flag.valueChanges.subscribe(_ => this.filerValueChanged('flag', _)); this.filterFormGroup.controls.flag.valueChanges.subscribe(_ => void this.filerValueChanged('flag', _));
}
ngOnInit(): void {
} }
public getFlags(): string[] { public getFlags(): string[] {
@@ -66,13 +51,14 @@ export class FilterComponent implements OnInit {
.reduce((pn, u) => [...pn, ...u], []) .reduce((pn, u) => [...pn, ...u], [])
.filter(_ => !!_); .filter(_ => !!_);
const uqFlags = flags.filter((n, i) => flags.indexOf(n) === i); return flags.filter((n, i) => flags.indexOf(n) === i);
return uqFlags;
} }
private async filerValueChanged(key: string, value: string): Promise<void> { 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); await this.router.navigateByUrl(route);
} }
} }

View File

@@ -1,22 +1,40 @@
<div class="list-item"> <div class="list-item">
<div class="number">{{song.number}}</div> <div class="number">{{ song.number }}</div>
<div>{{song.title}}</div> <div>{{ song.title }}</div>
<div> <div>
<ng-container *appRole="['contributor']"> <ng-container *appRole="['contributor']">
<span *ngIf="song.status==='draft' || !song.status" class="warning" matTooltip="Entwurf" <span
matTooltipPosition="before"> *ngIf="song.status === 'draft' || !song.status"
class="warning"
matTooltip="Entwurf"
matTooltipPosition="before"
>
<fa-icon [icon]="faDraft"></fa-icon> &nbsp; <fa-icon [icon]="faDraft"></fa-icon> &nbsp;
</span> </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; <fa-icon [icon]="faDraft"></fa-icon> &nbsp;
</span> </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; <fa-icon [icon]="faFinal"></fa-icon> &nbsp;
</span> </span>
</ng-container> </ng-container>
<span *ngIf="song.legalType==='open'" class="warning" matTooltip="rechtlicher Status ist ungeklärt" <span
matTooltipPosition="before"><fa-icon [icon]="faLegal"></fa-icon> &nbsp;</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>
<div>{{song.key}}</div> <div>{{ song.key }}</div>
</div> </div>

View File

@@ -6,12 +6,13 @@ describe('ListItemComponent', () => {
let component: ListItemComponent; let component: ListItemComponent;
let fixture: ComponentFixture<ListItemComponent>; let fixture: ComponentFixture<ListItemComponent>;
beforeEach(waitForAsync(() => { beforeEach(
TestBed.configureTestingModule({ waitForAsync(() => {
declarations: [ListItemComponent] void TestBed.configureTestingModule({
declarations: [ListItemComponent],
}).compileComponents();
}) })
.compileComponents(); );
}));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(ListItemComponent); fixture = TestBed.createComponent(ListItemComponent);
@@ -20,6 +21,6 @@ describe('ListItemComponent', () => {
}); });
it('should create', () => { 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 {Song} from '../../services/song';
import {faBalanceScaleRight} from '@fortawesome/free-solid-svg-icons/faBalanceScaleRight'; import {faBalanceScaleRight} from '@fortawesome/free-solid-svg-icons/faBalanceScaleRight';
import {faPencilRuler} from '@fortawesome/free-solid-svg-icons/faPencilRuler'; import {faPencilRuler} from '@fortawesome/free-solid-svg-icons/faPencilRuler';
@@ -7,18 +7,11 @@ import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
@Component({ @Component({
selector: 'app-list-item', selector: 'app-list-item',
templateUrl: './list-item.component.html', 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; @Input() public song: Song;
public faLegal = faBalanceScaleRight; public faLegal = faBalanceScaleRight;
public faDraft = faPencilRuler; public faDraft = faPencilRuler;
public faFinal = faCheck; public faFinal = faCheck;
constructor() {
}
ngOnInit() {
}
} }

View File

@@ -4,6 +4,10 @@
</app-list-header> </app-list-header>
<app-card [padding]="false"> <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> </app-card>
</div> </div>

View File

@@ -9,24 +9,21 @@ describe('SongListComponent', () => {
let component: SongListComponent; let component: SongListComponent;
let fixture: ComponentFixture<SongListComponent>; let fixture: ComponentFixture<SongListComponent>;
const songs = [ const songs = [{title: 'title1'}];
{title: 'title1'}
];
const mockSongService = { const mockSongService = {
list: () => of(songs) list: () => of(songs),
}; };
beforeEach(waitForAsync(() => { beforeEach(
TestBed.configureTestingModule({ waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [SongListComponent], declarations: [SongListComponent],
providers: [ providers: [{provide: SongService, useValue: mockSongService}],
{provide: SongService, useValue: mockSongService} schemas: [NO_ERRORS_SCHEMA],
], }).compileComponents();
schemas: [NO_ERRORS_SCHEMA]
}) })
.compileComponents(); );
}));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(SongListComponent); fixture = TestBed.createComponent(SongListComponent);
@@ -35,13 +32,11 @@ describe('SongListComponent', () => {
}); });
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); void expect(component).toBeTruthy();
}); });
it('should read songs from SongService', fakeAsync(() => { it('should read songs from SongService', fakeAsync(() => {
tick(); tick();
expect(component.songs$).toEqual([ void expect(component.songs$).toEqual([{title: 'title1'}]);
{title: 'title1'}
] as any);
})); }));
}); });

View File

@@ -13,28 +13,21 @@ import {ScrollService} from '../../../services/scroll.service';
selector: 'app-songs', selector: 'app-songs',
templateUrl: './song-list.component.html', templateUrl: './song-list.component.html',
styleUrls: ['./song-list.component.less'], styleUrls: ['./song-list.component.less'],
animations: [fade] animations: [fade],
}) })
export class SongListComponent implements OnInit, OnDestroy { export class SongListComponent implements OnInit, OnDestroy {
public songs$: Observable<Song[]>; public songs$: Observable<Song[]>;
public anyFilterActive = false; public anyFilterActive = false;
constructor( public constructor(private songService: SongService, private activatedRoute: ActivatedRoute, private scrollService: ScrollService) {}
private songService: SongService,
private activatedRoute: ActivatedRoute,
private scrollService: ScrollService) {
}
ngOnInit() { public ngOnInit(): void {
const filter$ = this.activatedRoute.queryParams.pipe( const filter$ = this.activatedRoute.queryParams.pipe(
debounceTime(300), debounceTime(300),
map(_ => _ as FilterValues) map(_ => _ as FilterValues)
); );
const songs$ = this.songService.list$().pipe( const songs$ = this.songService.list$().pipe(map(songs => songs.sort((a, b) => a.number - b.number)));
map(songs => songs.sort((a, b) => a.number - b.number)),
);
this.songs$ = combineLatest([filter$, songs$]).pipe( this.songs$ = combineLatest([filter$, songs$]).pipe(
map(_ => { map(_ => {

View File

@@ -17,7 +17,6 @@ import {MatTooltipModule} from '@angular/material/tooltip';
import {RoleModule} from '../../../services/user/role.module'; import {RoleModule} from '../../../services/user/role.module';
import {KeyTranslatorModule} from '../../../widget-modules/pipes/key-translator/key-translator.module'; import {KeyTranslatorModule} from '../../../widget-modules/pipes/key-translator/key-translator.module';
@NgModule({ @NgModule({
declarations: [SongListComponent, ListItemComponent, FilterComponent], declarations: [SongListComponent, ListItemComponent, FilterComponent],
exports: [SongListComponent], exports: [SongListComponent],
@@ -37,7 +36,6 @@ import {KeyTranslatorModule} from '../../../widget-modules/pipes/key-translator/
MatTooltipModule, MatTooltipModule,
RoleModule, RoleModule,
KeyTranslatorModule, KeyTranslatorModule,
] ],
}) })
export class SongListModule { export class SongListModule {}
}

View File

@@ -1,26 +1,29 @@
<app-card heading="Angehängte Dateien"> <app-card heading="Angehängte Dateien">
<div *ngIf="currentUpload"> <div *ngIf="currentUpload">
<div class="progress"> <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> </div>
Progress: {{currentUpload?.name}} | {{currentUpload?.progress}}% Complete Progress: {{ currentUpload?.name }} | {{ currentUpload?.progress }}%
Complete
</div> </div>
<div class="upload"> <div class="upload">
<label> <label>
<input (change)="detectFiles($event)" type="file"> <input (change)="detectFiles($event)" type="file"/>
</label> </label>
<button (click)="uploadSingle()" <button
(click)="uploadSingle()"
[disabled]="!selectedFiles" [disabled]="!selectedFiles"
mat-icon-button> mat-icon-button
>
<mat-icon>cloud_upload</mat-icon> <mat-icon>cloud_upload</mat-icon>
</button> </button>
</div> </div>
<p *ngFor="let file of (files$|async)"> <p *ngFor="let file of files$ | async">
<app-file [file]="file" [songId]="songId"></app-file> <app-file [file]="file" [songId]="songId"></app-file>
</p> </p>
</app-card> </app-card>

View File

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

View File

@@ -10,41 +10,33 @@ import {File} from '../../../services/file';
@Component({ @Component({
selector: 'app-edit-file', selector: 'app-edit-file',
templateUrl: './edit-file.component.html', templateUrl: './edit-file.component.html',
styleUrls: ['./edit-file.component.less'] styleUrls: ['./edit-file.component.less'],
}) })
export class EditFileComponent { export class EditFileComponent {
public selectedFiles: FileList; public selectedFiles: FileList;
public currentUpload: Upload; public currentUpload: Upload;
public songId: string; public songId: string;
public files$: Observable<File[]>; public files$: Observable<File[]>;
public constructor(private activatedRoute: ActivatedRoute, private uploadService: UploadService, private fileService: FileDataService) {
constructor( this.activatedRoute.params.pipe(map((param: {songId: string}) => param.songId)).subscribe(songId => {
private activatedRoute: ActivatedRoute,
private uploadService: UploadService,
private fileService: FileDataService,
) {
this.activatedRoute.params.pipe(
map(param => param.songId),
).subscribe(songId => {
this.songId = songId; this.songId = songId;
}); });
this.files$ = this.activatedRoute.params.pipe( this.files$ = this.activatedRoute.params.pipe(
map(param => param.songId), map((param: {songId: string}) => param.songId),
switchMap(songId => this.fileService.read$(songId)) switchMap(songId => this.fileService.read$(songId))
); );
} }
detectFiles(event) { public detectFiles(event: Event): void {
this.selectedFiles = event.target.files; const target = event.target as HTMLInputElement;
this.selectedFiles = target.files;
} }
public async uploadSingle() { public uploadSingle(): void {
const file = this.selectedFiles.item(0); const file = this.selectedFiles.item(0);
this.currentUpload = new Upload(file as any); this.currentUpload = new Upload(file);
await this.uploadService.pushUpload(this.songId, this.currentUpload); this.uploadService.pushUpload(this.songId, this.currentUpload);
} }
} }

View File

@@ -2,4 +2,4 @@
<fa-icon [icon]="faTrash"></fa-icon> <fa-icon [icon]="faTrash"></fa-icon>
</button> </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 component: FileComponent;
let fixture: ComponentFixture<FileComponent>; let fixture: ComponentFixture<FileComponent>;
beforeEach(waitForAsync(() => { beforeEach(
TestBed.configureTestingModule({ waitForAsync(() => {
declarations: [FileComponent] void TestBed.configureTestingModule({
declarations: [FileComponent],
}).compileComponents();
}) })
.compileComponents(); );
}));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(FileComponent); fixture = TestBed.createComponent(FileComponent);
@@ -20,6 +21,6 @@ describe('FileComponent', () => {
}); });
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); void expect(component).toBeTruthy();
}); });
}); });

View File

@@ -7,20 +7,20 @@ import {FileService} from '../../../../services/file.service';
@Component({ @Component({
selector: 'app-file', selector: 'app-file',
templateUrl: './file.component.html', templateUrl: './file.component.html',
styleUrls: ['./file.component.less'] styleUrls: ['./file.component.less'],
}) })
export class FileComponent { export class FileComponent {
public url$: Observable<string>; public url$: Observable<string>;
public name: string; public name: string;
public faTrash = faTrashAlt; public faTrash = faTrashAlt;
@Input() songId: string; @Input() public songId: string;
private fileId: string; private fileId: string;
private path: 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.url$ = this.fileService.getDownloadUrl(file.path + '/' + file.name);
this.name = file.name; this.name = file.name;
this.fileId = file.id; this.fileId = file.id;

View File

@@ -6,11 +6,11 @@ describe('EditSongGuard', () => {
let guard: EditSongGuard; let guard: EditSongGuard;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({}); void TestBed.configureTestingModule({});
guard = TestBed.inject(EditSongGuard); guard = TestBed.inject(EditSongGuard);
}); });
it('should be created', () => { 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