linting
This commit is contained in:
@@ -10,8 +10,7 @@
|
||||
],
|
||||
"parserOptions": {
|
||||
"project": [
|
||||
"tsconfig.json",
|
||||
"e2e/tsconfig.json"
|
||||
"tsconfig.json"
|
||||
],
|
||||
"createDefaultProgram": true
|
||||
},
|
||||
|
||||
34
angular.json
34
angular.json
@@ -45,10 +45,6 @@
|
||||
"src/styles/shadow.less"
|
||||
],
|
||||
"scripts": [],
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"optimization": false,
|
||||
"namedChunks": true,
|
||||
"allowedCommonJsDependencies": [
|
||||
"lodash",
|
||||
"docx",
|
||||
@@ -57,6 +53,13 @@
|
||||
"browser": "src/main.ts"
|
||||
},
|
||||
"configurations": {
|
||||
"development": {
|
||||
"aot": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"optimization": false,
|
||||
"namedChunks": true
|
||||
},
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
@@ -84,18 +87,23 @@
|
||||
],
|
||||
"serviceWorker": "ngsw-config.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"buildTarget": "wgenerator:build"
|
||||
"buildTarget": "wgenerator:build:development"
|
||||
},
|
||||
"configurations": {
|
||||
"development": {
|
||||
"buildTarget": "wgenerator:build:development"
|
||||
},
|
||||
"production": {
|
||||
"buildTarget": "wgenerator:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
@@ -127,18 +135,6 @@
|
||||
"src/**/*.html"
|
||||
]
|
||||
}
|
||||
},
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "e2e/protractor.conf.js",
|
||||
"devServerTarget": "wgenerator:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "wgenerator:serve:production"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// @ts-check
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const { SpecReporter } = require('jasmine-spec-reporter');
|
||||
|
||||
/**
|
||||
* @type { import("protractor").Config }
|
||||
*/
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'./src/**/*.e2e-spec.ts'
|
||||
],
|
||||
capabilities: {
|
||||
browserName: 'chrome'
|
||||
},
|
||||
directConnect: true,
|
||||
baseUrl: 'http://localhost:4200/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {}
|
||||
},
|
||||
onPrepare() {
|
||||
require('ts-node').register({
|
||||
project: require('path').join(__dirname, './tsconfig.json')
|
||||
});
|
||||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
||||
}
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
import {AppPage} from './app.po';
|
||||
import {browser, logging} from 'protractor';
|
||||
|
||||
describe('workspace-project App', () => {
|
||||
let page: AppPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new AppPage();
|
||||
});
|
||||
|
||||
it('should display welcome message', () => {
|
||||
void page.navigateTo();
|
||||
void expect(page.getTitleText()).toEqual('wgenerator app is running!');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Assert that there are no errors emitted from the browser
|
||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
||||
void expect(logs).not.toContain(
|
||||
jasmine.objectContaining({
|
||||
level: logging.Level.SEVERE,
|
||||
} as logging.Entry)
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import { browser, by, element } from 'protractor';
|
||||
|
||||
export class AppPage {
|
||||
navigateTo() {
|
||||
return browser.get(browser.baseUrl) as Promise<any>;
|
||||
}
|
||||
|
||||
getTitleText() {
|
||||
return element(by.css('app-root .content span')).getText() as Promise<string>;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/e2e",
|
||||
"module": "commonjs",
|
||||
"target": "es2018",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"jasminewd2",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
"version": "1.6",
|
||||
"scripts": {
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"build": "ng build --configuration production",
|
||||
"build:dev": "ng build --configuration development",
|
||||
"deploy": "ng build --configuration production && firebase deploy",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint --fix",
|
||||
|
||||
@@ -43,6 +43,7 @@ export class MonitorComponent implements OnInit, OnDestroy {
|
||||
public config$: Observable<Config | null>;
|
||||
public presentationBackground: PresentationBackground = 'none';
|
||||
private destroy$ = new Subject<void>();
|
||||
private songSwitchTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
public constructor() {
|
||||
const configService = this.configService;
|
||||
@@ -97,7 +98,10 @@ export class MonitorComponent implements OnInit, OnDestroy {
|
||||
if (this.songId !== presentationSongId) {
|
||||
this.songId = 'empty';
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (this.songSwitchTimeoutId) {
|
||||
clearTimeout(this.songSwitchTimeoutId);
|
||||
}
|
||||
this.songSwitchTimeoutId = setTimeout(() => {
|
||||
this.songId = presentationSongId;
|
||||
this.cRef.markForCheck();
|
||||
}, 600);
|
||||
@@ -113,6 +117,9 @@ export class MonitorComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
if (this.songSwitchTimeoutId) {
|
||||
clearTimeout(this.songSwitchTimeoutId);
|
||||
}
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
@@ -120,9 +120,11 @@ export class RemoteComponent implements OnDestroy {
|
||||
});
|
||||
|
||||
this.presentationDynamicCaptionChanged$
|
||||
.pipe(debounceTime(1000))
|
||||
.pipe(debounceTime(1000), takeUntil(this.destroy$))
|
||||
.subscribe(_ => void this.showService.update$(_.showId, {presentationDynamicCaption: _.presentationDynamicCaption}));
|
||||
this.presentationDynamicTextChanged$.pipe(debounceTime(1000)).subscribe(_ => void this.showService.update$(_.showId, {presentationDynamicText: _.presentationDynamicText}));
|
||||
this.presentationDynamicTextChanged$
|
||||
.pipe(debounceTime(1000), takeUntil(this.destroy$))
|
||||
.subscribe(_ => void this.showService.update$(_.showId, {presentationDynamicText: _.presentationDynamicText}));
|
||||
}
|
||||
|
||||
public trackBy(index: number, item: PresentationSong): string {
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface ShareDialogData {
|
||||
export class ShareDialogComponent {
|
||||
public data = inject<ShareDialogData>(MAT_DIALOG_DATA);
|
||||
|
||||
public qrCode: string;
|
||||
public qrCode = '';
|
||||
|
||||
public constructor() {
|
||||
const data = this.data;
|
||||
@@ -35,10 +35,10 @@ export class ShareDialogComponent {
|
||||
light: '#ffffff',
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-return
|
||||
}).then(_ => (this.qrCode = _));
|
||||
}).then((qrCode: string) => (this.qrCode = qrCode));
|
||||
}
|
||||
|
||||
public async share() {
|
||||
public async share(): Promise<void> {
|
||||
if (navigator.clipboard) await navigator.clipboard.writeText(this.data.url);
|
||||
|
||||
if (navigator.share)
|
||||
|
||||
@@ -87,10 +87,15 @@ export class EditComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.showService.update$(this.form.value.id, {
|
||||
date: Timestamp.fromDate(this.form.value.date),
|
||||
showType: this.form.value.showType,
|
||||
const {id, date, showType} = this.form.getRawValue();
|
||||
if (!id || !date || !showType) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.showService.update$(id, {
|
||||
date: Timestamp.fromDate(date),
|
||||
showType,
|
||||
} as Partial<Show>);
|
||||
await this.router.navigateByUrl(`/shows/${this.form.value.id ?? ''}`);
|
||||
await this.router.navigateByUrl(`/shows/${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {Component, Input, inject} from '@angular/core';
|
||||
import {Component, DestroyRef, Input, inject} from '@angular/core';
|
||||
import {KeyValue} from '@angular/common';
|
||||
import {ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup} from '@angular/forms';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {FormBuilder, FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
|
||||
import {FilterValues} from './filter-values';
|
||||
import {Show} from '../../services/show';
|
||||
import {ShowService} from '../../services/show.service';
|
||||
@@ -24,13 +25,18 @@ export class FilterComponent {
|
||||
private showService = inject(ShowService);
|
||||
private userService = inject(UserService);
|
||||
private filterStore = inject(FilterStoreService);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
@Input() public shows: Show[] = [];
|
||||
|
||||
public showTypePublic = ShowService.SHOW_TYPE_PUBLIC;
|
||||
public showTypePrivate = ShowService.SHOW_TYPE_PRIVATE;
|
||||
|
||||
public filterFormGroup: UntypedFormGroup;
|
||||
public filterFormGroup: FormGroup<{
|
||||
time: FormControl<number>;
|
||||
owner: FormControl<string | null>;
|
||||
showType: FormControl<string | null>;
|
||||
}>;
|
||||
public times: KeyValue<number, string>[] = [
|
||||
{key: 1, value: 'letzter Monat'},
|
||||
{key: 3, value: 'letztes Quartal'},
|
||||
@@ -41,15 +47,15 @@ export class FilterComponent {
|
||||
public owners: {key: string; value: string}[] = [];
|
||||
|
||||
public constructor() {
|
||||
const fb = inject(UntypedFormBuilder);
|
||||
const fb = inject(FormBuilder);
|
||||
|
||||
this.filterFormGroup = fb.group({
|
||||
time: 1,
|
||||
owner: null,
|
||||
showType: null,
|
||||
time: fb.nonNullable.control(1),
|
||||
owner: fb.control<string | null>(null),
|
||||
showType: fb.control<string | null>(null),
|
||||
});
|
||||
|
||||
this.filterStore.showFilter$.subscribe(filterValues => {
|
||||
this.filterStore.showFilter$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(filterValues => {
|
||||
this.filterFormGroup.patchValue(
|
||||
{
|
||||
time: filterValues.time,
|
||||
@@ -60,11 +66,13 @@ export class FilterComponent {
|
||||
);
|
||||
});
|
||||
|
||||
this.filterFormGroup.controls.time.valueChanges.subscribe(_ => this.filterValueChanged('time', (_ as number) ?? 1));
|
||||
this.filterFormGroup.controls.owner.valueChanges.subscribe(_ => this.filterValueChanged('owner', (_ as string | null) ?? ''));
|
||||
this.filterFormGroup.controls.showType.valueChanges.subscribe(_ => this.filterValueChanged('showType', (_ as string | null) ?? ''));
|
||||
this.filterFormGroup.controls.time.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('time', value));
|
||||
this.filterFormGroup.controls.owner.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('owner', value ?? ''));
|
||||
this.filterFormGroup.controls.showType.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('showType', value ?? ''));
|
||||
|
||||
this.owners$().subscribe(owners => (this.owners = owners));
|
||||
this.owners$()
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe(owners => (this.owners = owners));
|
||||
}
|
||||
|
||||
public owners$ = (): Observable<{key: string; value: string}[]> => {
|
||||
@@ -85,17 +93,15 @@ export class FilterComponent {
|
||||
this.userService.getUserbyId$(ownerId).pipe(
|
||||
map(user => ({
|
||||
key: ownerId,
|
||||
value: user?.name,
|
||||
value: user?.name ?? ownerId,
|
||||
}))
|
||||
)
|
||||
)
|
||||
);
|
||||
}),
|
||||
map(owners => {
|
||||
return owners.sort(dynamicSort('value'));
|
||||
}),
|
||||
map(owners => owners.sort(dynamicSort<{key: string; value: string}>('value'))),
|
||||
distinctUntilChanged((left, right) => this.sameOwners(left, right)),
|
||||
map(_ => _ as {key: string; value: string}[])
|
||||
map(owners => owners as {key: string; value: string}[])
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import {ShowDataService} from '../services/show-data.service';
|
||||
import {Observable} from 'rxjs';
|
||||
import {Show} from '../services/show';
|
||||
import {ShowService} from '../services/show.service';
|
||||
import {ReactiveFormsModule, UntypedFormControl, UntypedFormGroup, Validators} from '@angular/forms';
|
||||
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||
import {Router} from '@angular/router';
|
||||
import {faSave} from '@fortawesome/free-solid-svg-icons';
|
||||
import {CardComponent} from '../../../widget-modules/components/card/card.component';
|
||||
@@ -46,9 +46,9 @@ export class NewComponent implements OnInit {
|
||||
public shows$: Observable<Show[]>;
|
||||
public showTypePublic = ShowService.SHOW_TYPE_PUBLIC;
|
||||
public showTypePrivate = ShowService.SHOW_TYPE_PRIVATE;
|
||||
public form: UntypedFormGroup = new UntypedFormGroup({
|
||||
date: new UntypedFormControl(null, Validators.required),
|
||||
showType: new UntypedFormControl(null, Validators.required),
|
||||
public form = new FormGroup({
|
||||
date: new FormControl<Date | null>(null, Validators.required),
|
||||
showType: new FormControl<string | null>(null, Validators.required),
|
||||
});
|
||||
public faSave = faSave;
|
||||
|
||||
@@ -68,7 +68,11 @@ export class NewComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = await this.showService.new$(this.form.value as Partial<Show>);
|
||||
const {date, showType} = this.form.getRawValue();
|
||||
const id = await this.showService.new$({
|
||||
date,
|
||||
showType,
|
||||
} as unknown as Partial<Show>);
|
||||
await this.router.navigateByUrl(`/shows/${id ?? ''}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {filter, map, shareReplay, switchMap, tap} from 'rxjs/operators';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {ShowService} from '../services/show.service';
|
||||
import {Observable, of, Subscription} from 'rxjs';
|
||||
import {take} from 'rxjs/operators';
|
||||
import {Show} from '../services/show';
|
||||
import {SongService} from '../../songs/services/song.service';
|
||||
import {Song} from '../../songs/services/song';
|
||||
@@ -112,12 +113,13 @@ export class ShowComponent implements OnInit, OnDestroy {
|
||||
public faRestore = faMinimize;
|
||||
public faMaximize = faMaximize;
|
||||
public faNextSong = faChevronRight;
|
||||
public currentTime: Date;
|
||||
public currentTime!: Date;
|
||||
private subs: Subscription[] = [];
|
||||
private clockIntervalId: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.currentTime = new Date();
|
||||
setInterval(() => {
|
||||
this.clockIntervalId = setInterval(() => {
|
||||
this.currentTime = new Date();
|
||||
}, 10000);
|
||||
this.show$ = this.activatedRoute.params.pipe(
|
||||
@@ -155,6 +157,9 @@ export class ShowComponent implements OnInit, OnDestroy {
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
this.subs.forEach(_ => _.unsubscribe());
|
||||
if (this.clockIntervalId) {
|
||||
clearInterval(this.clockIntervalId);
|
||||
}
|
||||
}
|
||||
|
||||
public onZoomIn() {
|
||||
@@ -172,7 +177,7 @@ export class ShowComponent implements OnInit, OnDestroy {
|
||||
width: '350px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((archive: boolean) => {
|
||||
dialogRef.afterClosed().pipe(take(1)).subscribe((archive: boolean) => {
|
||||
if (archive && this.showId != null) void this.showService.update$(this.showId, {archived});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import {Component, Input, OnInit, ViewChild, inject} from '@angular/core';
|
||||
import {Component, DestroyRef, Input, OnInit, ViewChild, inject} from '@angular/core';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {ShowSongService} from '../../services/show-song.service';
|
||||
import {ShowSong} from '../../services/show-song';
|
||||
import {getScale} from '../../../songs/services/key.helper';
|
||||
import {ReactiveFormsModule, UntypedFormControl} from '@angular/forms';
|
||||
import {FormControl, ReactiveFormsModule} from '@angular/forms';
|
||||
import {ChordMode, SongTextComponent} from '../../../../widget-modules/components/song-text/song-text.component';
|
||||
import {Show} from '../../services/show';
|
||||
import {faEraser, faPenToSquare, faSave, faTrash} from '@fortawesome/free-solid-svg-icons';
|
||||
@@ -42,6 +43,7 @@ import {CdkDragHandle} from '@angular/cdk/drag-drop';
|
||||
})
|
||||
export class SongComponent implements OnInit {
|
||||
private showSongService = inject(ShowSongService);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
@Input() public show: Show | null = null;
|
||||
@Input() public showId: string | null = null;
|
||||
@@ -54,11 +56,11 @@ export class SongComponent implements OnInit {
|
||||
public faEdit = faPenToSquare;
|
||||
public faSave = faSave;
|
||||
public faEraser = faEraser;
|
||||
public keyFormControl: UntypedFormControl = new UntypedFormControl();
|
||||
public keyFormControl = new FormControl<string>('', {nonNullable: true});
|
||||
public iSong: ShowSong | null = null;
|
||||
public edit = false;
|
||||
public editSongControl = new UntypedFormControl();
|
||||
@ViewChild('option') private keyOptions: MatSelect;
|
||||
public editSongControl = new FormControl<string | null>(null);
|
||||
@ViewChild('option') private keyOptions!: MatSelect;
|
||||
|
||||
@Input()
|
||||
public set showSong(song: ShowSong) {
|
||||
@@ -68,8 +70,8 @@ export class SongComponent implements OnInit {
|
||||
|
||||
public ngOnInit(): void {
|
||||
if (!this.iSong) return;
|
||||
this.keyFormControl = new UntypedFormControl(this.iSong.key);
|
||||
this.keyFormControl.valueChanges.subscribe((value: string) => {
|
||||
this.keyFormControl = new FormControl<string>(this.iSong.key, {nonNullable: true});
|
||||
this.keyFormControl.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => {
|
||||
if (!this.showId || !this.iSong) return;
|
||||
void this.showSongService.update$(this.showId, this.iSong.id, {key: value});
|
||||
});
|
||||
|
||||
@@ -96,10 +96,11 @@ export class TextRenderingService {
|
||||
return [];
|
||||
}
|
||||
|
||||
const indices = {
|
||||
const indices: Record<SectionType, number> = {
|
||||
[SectionType.Bridge]: 0,
|
||||
[SectionType.Chorus]: 0,
|
||||
[SectionType.Verse]: 0,
|
||||
[SectionType.Comment]: 0,
|
||||
};
|
||||
const sections: Section[] = [];
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {Component, Input, inject} from '@angular/core';
|
||||
import {ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup} from '@angular/forms';
|
||||
import {Component, DestroyRef, Input, inject} from '@angular/core';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {FormBuilder, FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
|
||||
import {SongService} from '../../services/song.service';
|
||||
import {FilterValues} from './filter-values';
|
||||
import {Song} from '../../services/song';
|
||||
@@ -22,17 +23,24 @@ import {SongTypePipe} from '../../../../widget-modules/pipes/song-type-translate
|
||||
})
|
||||
export class FilterComponent {
|
||||
private filterStore = inject(FilterStoreService);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
public filterFormGroup: UntypedFormGroup;
|
||||
public filterFormGroup: FormGroup<{
|
||||
q: FormControl<string>;
|
||||
type: FormControl<string>;
|
||||
key: FormControl<string>;
|
||||
legalType: FormControl<string>;
|
||||
flag: FormControl<string>;
|
||||
}>;
|
||||
@Input() public songs: Song[] = [];
|
||||
public types = SongService.TYPES;
|
||||
public legalType = SongService.LEGAL_TYPE;
|
||||
public keys = KEYS;
|
||||
|
||||
public constructor() {
|
||||
const fb = inject(UntypedFormBuilder);
|
||||
const fb = inject(FormBuilder);
|
||||
|
||||
this.filterFormGroup = fb.group({
|
||||
this.filterFormGroup = fb.nonNullable.group({
|
||||
q: '',
|
||||
type: '',
|
||||
key: '',
|
||||
@@ -40,15 +48,15 @@ export class FilterComponent {
|
||||
flag: '',
|
||||
});
|
||||
|
||||
this.filterStore.songFilter$.subscribe(filterValues => {
|
||||
this.filterStore.songFilter$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(filterValues => {
|
||||
this.filterFormGroup.patchValue(filterValues, {emitEvent: false});
|
||||
});
|
||||
|
||||
this.filterFormGroup.controls.q.valueChanges.subscribe(_ => this.filterValueChanged('q', (_ as string) ?? ''));
|
||||
this.filterFormGroup.controls.key.valueChanges.subscribe(_ => this.filterValueChanged('key', (_ as string) ?? ''));
|
||||
this.filterFormGroup.controls.type.valueChanges.subscribe(_ => this.filterValueChanged('type', (_ as string) ?? ''));
|
||||
this.filterFormGroup.controls.legalType.valueChanges.subscribe(_ => this.filterValueChanged('legalType', (_ as string) ?? ''));
|
||||
this.filterFormGroup.controls.flag.valueChanges.subscribe(_ => this.filterValueChanged('flag', (_ as string) ?? ''));
|
||||
this.filterFormGroup.controls.q.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('q', value));
|
||||
this.filterFormGroup.controls.key.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('key', value));
|
||||
this.filterFormGroup.controls.type.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('type', value));
|
||||
this.filterFormGroup.controls.legalType.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('legalType', value));
|
||||
this.filterFormGroup.controls.flag.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.filterValueChanged('flag', value));
|
||||
}
|
||||
|
||||
public getFlags(): string[] {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {Component, inject} from '@angular/core';
|
||||
import {Component, DestroyRef, inject} from '@angular/core';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {Upload} from '../../../services/upload';
|
||||
import {UploadService} from '../../../services/upload.service';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
@@ -22,6 +23,7 @@ export class EditFileComponent {
|
||||
private activatedRoute = inject(ActivatedRoute);
|
||||
private uploadService = inject(UploadService);
|
||||
private fileService = inject(FileDataService);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
public selectedFiles: FileList | null = null;
|
||||
public currentUpload: Upload | null = null;
|
||||
@@ -32,7 +34,8 @@ export class EditFileComponent {
|
||||
this.activatedRoute.params
|
||||
.pipe(
|
||||
map(param => param as {songId: string}),
|
||||
map(param => param.songId)
|
||||
map(param => param.songId),
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
)
|
||||
.subscribe(songId => {
|
||||
this.songId = songId;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import {Component, OnInit, inject} from '@angular/core';
|
||||
import {Component, DestroyRef, inject, OnInit} from '@angular/core';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {Song} from '../../../services/song';
|
||||
import {ReactiveFormsModule, UntypedFormGroup} from '@angular/forms';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
import {ActivatedRoute, Router, RouterStateSnapshot} from '@angular/router';
|
||||
import {SongService} from '../../../services/song.service';
|
||||
import {EditService} from '../edit.service';
|
||||
import {EditService, SongFormGroup} from '../edit.service';
|
||||
import {first, map, switchMap} from 'rxjs/operators';
|
||||
import {startWith} from 'rxjs';
|
||||
import {KEYS} from '../../../services/key.helper';
|
||||
@@ -62,15 +63,9 @@ import {StatusPipe} from '../../../../../widget-modules/pipes/status-translater/
|
||||
],
|
||||
})
|
||||
export class EditSongComponent implements OnInit {
|
||||
private activatedRoute = inject(ActivatedRoute);
|
||||
private songService = inject(SongService);
|
||||
private editService = inject(EditService);
|
||||
private router = inject(Router);
|
||||
private textRenderingService = inject(TextRenderingService);
|
||||
public dialog = inject(MatDialog);
|
||||
|
||||
public song: Song | null = null;
|
||||
public form: UntypedFormGroup = new UntypedFormGroup({});
|
||||
public form = {} as SongFormGroup;
|
||||
public keys = KEYS;
|
||||
public types = SongService.TYPES;
|
||||
public status = SongService.STATUS;
|
||||
@@ -83,6 +78,12 @@ export class EditSongComponent implements OnInit {
|
||||
public faLink = faExternalLinkAlt;
|
||||
public songtextFocus = false;
|
||||
public chordValidationIssues: ChordValidationIssue[] = [];
|
||||
private activatedRoute = inject(ActivatedRoute);
|
||||
private songService = inject(SongService);
|
||||
private editService = inject(EditService);
|
||||
private router = inject(Router);
|
||||
private textRenderingService = inject(TextRenderingService);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.activatedRoute.params
|
||||
@@ -90,23 +91,24 @@ export class EditSongComponent implements OnInit {
|
||||
map(param => param as {songId: string}),
|
||||
map(param => param.songId),
|
||||
switchMap(songId => this.songService.read$(songId)),
|
||||
first()
|
||||
first(),
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
)
|
||||
.subscribe(song => {
|
||||
this.song = song;
|
||||
if (!song) return;
|
||||
this.form = this.editService.createSongForm(song);
|
||||
this.form.controls.flags.valueChanges.subscribe(_ => this.onFlagsChanged(_ as string));
|
||||
this.form.controls.text.valueChanges.pipe(startWith(this.form.controls.text.value)).subscribe(text => {
|
||||
this.updateChordValidation(text as string);
|
||||
this.form.controls.flags.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => this.onFlagsChanged(value));
|
||||
this.form.controls.text.valueChanges.pipe(startWith(this.form.controls.text.value), takeUntilDestroyed(this.destroyRef)).subscribe(text => {
|
||||
this.updateChordValidation(text);
|
||||
});
|
||||
this.onFlagsChanged(this.form.controls.flags.value as string);
|
||||
this.onFlagsChanged(this.form.controls.flags.value);
|
||||
});
|
||||
}
|
||||
|
||||
public async onSave(): Promise<void> {
|
||||
if (!this.song || this.form.invalid) return;
|
||||
const data = this.form.value as Partial<Song>;
|
||||
const data = this.form.getRawValue() as Partial<Song>;
|
||||
await this.songService.update$(this.song.id, data);
|
||||
this.form.markAsPristine();
|
||||
await this.router.navigateByUrl('songs/' + this.song.id);
|
||||
@@ -121,7 +123,6 @@ export class EditSongComponent implements OnInit {
|
||||
const input = event.input;
|
||||
const value = event.value;
|
||||
|
||||
// Add our fruit
|
||||
if ((value || '').trim()) {
|
||||
const flags = [...this.flags, value.trim()];
|
||||
this.form.controls.flags.setValue(flags.join(';'));
|
||||
@@ -174,7 +175,7 @@ export class EditSongComponent implements OnInit {
|
||||
|
||||
private async onSaveDialogAfterClosed(save: boolean, url: string) {
|
||||
if (save && this.song && !this.form.invalid) {
|
||||
const data = this.form.value as Partial<Song>;
|
||||
const data = this.form.getRawValue() as Partial<Song>;
|
||||
await this.songService.update$(this.song.id, data);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +1,48 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Song} from '../../services/song';
|
||||
import {UntypedFormControl, UntypedFormGroup} from '@angular/forms';
|
||||
import {FormControl, FormGroup} from '@angular/forms';
|
||||
|
||||
export type SongFormGroup = FormGroup<{
|
||||
text: FormControl<string>;
|
||||
title: FormControl<string>;
|
||||
comment: FormControl<string>;
|
||||
flags: FormControl<string>;
|
||||
key: FormControl<string>;
|
||||
tempo: FormControl<number>;
|
||||
type: FormControl<Song['type']>;
|
||||
status: FormControl<Song['status']>;
|
||||
legalType: FormControl<Song['legalType']>;
|
||||
legalOwner: FormControl<Song['legalOwner']>;
|
||||
legalOwnerId: FormControl<string>;
|
||||
artist: FormControl<string>;
|
||||
label: FormControl<string>;
|
||||
termsOfUse: FormControl<string>;
|
||||
origin: FormControl<string>;
|
||||
}>;
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class EditService {
|
||||
public createSongForm(song: Song): UntypedFormGroup {
|
||||
return new UntypedFormGroup({
|
||||
text: new UntypedFormControl(song.text),
|
||||
title: new UntypedFormControl(song.title),
|
||||
comment: new UntypedFormControl(song.comment),
|
||||
flags: new UntypedFormControl(song.flags),
|
||||
key: new UntypedFormControl(song.key),
|
||||
tempo: new UntypedFormControl(song.tempo),
|
||||
type: new UntypedFormControl(song.type),
|
||||
status: new UntypedFormControl(song.status ?? 'draft'),
|
||||
public createSongForm(song: Song): SongFormGroup {
|
||||
return new FormGroup({
|
||||
text: new FormControl(song.text, {nonNullable: true}),
|
||||
title: new FormControl(song.title, {nonNullable: true}),
|
||||
comment: new FormControl(song.comment, {nonNullable: true}),
|
||||
flags: new FormControl(song.flags, {nonNullable: true}),
|
||||
key: new FormControl(song.key, {nonNullable: true}),
|
||||
tempo: new FormControl(song.tempo, {nonNullable: true}),
|
||||
type: new FormControl(song.type, {nonNullable: true}),
|
||||
status: new FormControl(song.status ?? 'draft', {nonNullable: true}),
|
||||
|
||||
legalType: new UntypedFormControl(song.legalType),
|
||||
legalOwner: new UntypedFormControl(song.legalOwner),
|
||||
legalOwnerId: new UntypedFormControl(song.legalOwnerId),
|
||||
legalType: new FormControl(song.legalType, {nonNullable: true}),
|
||||
legalOwner: new FormControl(song.legalOwner, {nonNullable: true}),
|
||||
legalOwnerId: new FormControl(song.legalOwnerId, {nonNullable: true}),
|
||||
|
||||
artist: new UntypedFormControl(song.artist),
|
||||
label: new UntypedFormControl(song.label),
|
||||
termsOfUse: new UntypedFormControl(song.termsOfUse),
|
||||
origin: new UntypedFormControl(song.origin),
|
||||
artist: new FormControl(song.artist, {nonNullable: true}),
|
||||
label: new FormControl(song.label, {nonNullable: true}),
|
||||
termsOfUse: new FormControl(song.termsOfUse, {nonNullable: true}),
|
||||
origin: new FormControl(song.origin, {nonNullable: true}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import {Component, OnDestroy, OnInit, inject} from '@angular/core';
|
||||
import {Component, OnInit, inject} from '@angular/core';
|
||||
import {faSave} from '@fortawesome/free-solid-svg-icons';
|
||||
import {ReactiveFormsModule, UntypedFormControl, UntypedFormGroup, Validators} from '@angular/forms';
|
||||
import {DestroyRef} from '@angular/core';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||
import {SongService} from '../../services/song.service';
|
||||
import {Song} from '../../services/song';
|
||||
import {Router} from '@angular/router';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {take} from 'rxjs/operators';
|
||||
import {CardComponent} from '../../../../widget-modules/components/card/card.component';
|
||||
import {MatFormField, MatLabel} from '@angular/material/form-field';
|
||||
@@ -18,39 +19,34 @@ import {ButtonComponent} from '../../../../widget-modules/components/button/butt
|
||||
styleUrls: ['./new.component.less'],
|
||||
imports: [CardComponent, ReactiveFormsModule, MatFormField, MatLabel, MatInput, ButtonRowComponent, ButtonComponent],
|
||||
})
|
||||
export class NewComponent implements OnInit, OnDestroy {
|
||||
export class NewComponent implements OnInit {
|
||||
private songService = inject(SongService);
|
||||
private router = inject(Router);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
public faSave = faSave;
|
||||
public form: UntypedFormGroup = new UntypedFormGroup({
|
||||
number: new UntypedFormControl(null, Validators.required),
|
||||
title: new UntypedFormControl(null, Validators.required),
|
||||
public form = new FormGroup({
|
||||
number: new FormControl<number | null>(null, Validators.required),
|
||||
title: new FormControl<string>('', {nonNullable: true, validators: [Validators.required]}),
|
||||
});
|
||||
private subs: Subscription[] = [];
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.form.reset();
|
||||
|
||||
this.subs.push(
|
||||
this.songService
|
||||
.list$()
|
||||
.pipe(take(1))
|
||||
.subscribe(songs => {
|
||||
const freeSongnumber = this.getFreeSongNumber(songs);
|
||||
this.form.controls.number.setValue(freeSongnumber);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
this.subs.forEach(_ => _.unsubscribe());
|
||||
this.songService
|
||||
.list$()
|
||||
.pipe(take(1), takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe(songs => {
|
||||
const freeSongnumber = this.getFreeSongNumber(songs);
|
||||
this.form.controls.number.setValue(freeSongnumber);
|
||||
});
|
||||
}
|
||||
|
||||
public async onSave(): Promise<void> {
|
||||
const value = this.form.value as {number: number; title: string};
|
||||
const songNumber = value.number;
|
||||
const title = value.title;
|
||||
const {number: songNumber, title} = this.form.getRawValue();
|
||||
if (songNumber == null) {
|
||||
return;
|
||||
}
|
||||
const newSongId = await this.songService.new(songNumber, title);
|
||||
await this.router.navigateByUrl('/songs/' + newSongId + '/edit');
|
||||
}
|
||||
|
||||
@@ -75,11 +75,12 @@ export class SongComponent implements OnInit {
|
||||
}
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.song$ = this.activatedRoute.params.pipe(
|
||||
const song$ = this.activatedRoute.params.pipe(
|
||||
map(param => param as {songId: string}),
|
||||
map(param => param.songId),
|
||||
switchMap(songId => this.songService.read$(songId))
|
||||
);
|
||||
this.song$ = song$;
|
||||
|
||||
this.files$ = this.activatedRoute.params.pipe(
|
||||
map(param => param as {songId: string}),
|
||||
@@ -87,7 +88,7 @@ export class SongComponent implements OnInit {
|
||||
switchMap(songId => this.fileService.read$(songId))
|
||||
);
|
||||
|
||||
this.songCount$ = combineLatest([this.user$, this.song$]).pipe(
|
||||
this.songCount$ = combineLatest([this.userService.user$, song$]).pipe(
|
||||
map(([user, song]) => {
|
||||
if (!song) {
|
||||
return 0;
|
||||
@@ -111,10 +112,9 @@ export class SongComponent implements OnInit {
|
||||
await this.router.navigateByUrl('/songs');
|
||||
}
|
||||
|
||||
public async addSongToShow(show: Show, song: Song) {
|
||||
if (!show) return;
|
||||
const newId = await this.showSongService.new$(show?.id, song.id, false);
|
||||
await this.showService.update$(show?.id, {order: [...show.order, newId ?? '']});
|
||||
public async addSongToShow(show: Show, song: Song): Promise<void> {
|
||||
const newId = await this.showSongService.new$(show.id, song.id, false);
|
||||
await this.showService.update$(show.id, {order: [...show.order, newId ?? '']});
|
||||
await this.router.navigateByUrl('/shows/' + show.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,14 @@ export class AuthMessagePipe implements PipeTransform {
|
||||
return 'Benutzer wurde nicht gefunden';
|
||||
case 'auth/wrong-password':
|
||||
return 'Passwort ist falsch';
|
||||
case 'auth/email-already-in-use':
|
||||
return 'Die E-Mail-Adresse wird bereits verwendet';
|
||||
case 'auth/invalid-email':
|
||||
return 'Die E-Mail-Adresse ist ungueltig';
|
||||
case 'auth/weak-password':
|
||||
return 'Das Passwort ist zu schwach';
|
||||
case 'unknown_error':
|
||||
return 'Unbekannter Fehler';
|
||||
default:
|
||||
return code;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {Component, OnInit, inject} from '@angular/core';
|
||||
import {ReactiveFormsModule, UntypedFormControl, UntypedFormGroup, Validators} from '@angular/forms';
|
||||
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||
import {Router, RouterLink} from '@angular/router';
|
||||
import {UserService} from '../../../services/user/user.service';
|
||||
import {faSignInAlt, faUserPlus} from '@fortawesome/free-solid-svg-icons';
|
||||
@@ -20,9 +20,9 @@ export class LoginComponent implements OnInit {
|
||||
private userService = inject(UserService);
|
||||
private router = inject(Router);
|
||||
|
||||
public form: UntypedFormGroup = new UntypedFormGroup({
|
||||
user: new UntypedFormControl(null, [Validators.required, Validators.email]),
|
||||
pass: new UntypedFormControl(null, [Validators.required]),
|
||||
public form = new FormGroup({
|
||||
user: new FormControl<string>('', {nonNullable: true, validators: [Validators.required, Validators.email]}),
|
||||
pass: new FormControl<string>('', {nonNullable: true, validators: [Validators.required]}),
|
||||
});
|
||||
public errorMessage = '';
|
||||
public faSignIn = faSignInAlt;
|
||||
@@ -36,12 +36,19 @@ export class LoginComponent implements OnInit {
|
||||
this.form.updateValueAndValidity();
|
||||
if (this.form.valid) {
|
||||
try {
|
||||
const value = this.form.value as {user: string; pass: string};
|
||||
await this.userService.login(value.user, value.pass);
|
||||
await this.userService.login(this.form.controls.user.value, this.form.controls.pass.value);
|
||||
await this.router.navigateByUrl('/');
|
||||
} catch ({code}) {
|
||||
this.errorMessage = code as string;
|
||||
} catch (error) {
|
||||
this.errorMessage = this.errorCode(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private errorCode(error: unknown): string {
|
||||
if (typeof error === 'object' && error !== null && 'code' in error && typeof error.code === 'string') {
|
||||
return error.code;
|
||||
}
|
||||
|
||||
return 'unknown_error';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
<mat-label>Passwort</mat-label>
|
||||
<input formControlName="password" matInput type="password" />
|
||||
</mat-form-field>
|
||||
@if (errorMessage) {
|
||||
<p class="error">{{ errorMessage | authMessage }}</p>
|
||||
}
|
||||
|
||||
<app-button-row>
|
||||
<app-button (click)="onCreate()" [icon]="faNewUser"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {Component, OnInit, inject} from '@angular/core';
|
||||
import {ReactiveFormsModule, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators} from '@angular/forms';
|
||||
import {FormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||
import {UserService} from '../../../services/user/user.service';
|
||||
import {faUserPlus} from '@fortawesome/free-solid-svg-icons';
|
||||
import {CardComponent} from '../../../widget-modules/components/card/card.component';
|
||||
@@ -7,23 +7,25 @@ import {MatFormField, MatLabel} from '@angular/material/form-field';
|
||||
import {MatInput} from '@angular/material/input';
|
||||
import {ButtonRowComponent} from '../../../widget-modules/components/button-row/button-row.component';
|
||||
import {ButtonComponent} from '../../../widget-modules/components/button/button.component';
|
||||
import {AuthMessagePipe} from '../login/auth-message.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'app-new',
|
||||
templateUrl: './new.component.html',
|
||||
styleUrls: ['./new.component.less'],
|
||||
imports: [CardComponent, ReactiveFormsModule, MatFormField, MatLabel, MatInput, ButtonRowComponent, ButtonComponent],
|
||||
imports: [CardComponent, ReactiveFormsModule, MatFormField, MatLabel, MatInput, ButtonRowComponent, ButtonComponent, AuthMessagePipe],
|
||||
})
|
||||
export class NewComponent implements OnInit {
|
||||
private fb = inject(UntypedFormBuilder);
|
||||
private fb = inject(FormBuilder);
|
||||
private userService = inject(UserService);
|
||||
|
||||
public form: UntypedFormGroup = this.fb.group({
|
||||
email: new UntypedFormControl(null, [Validators.required, Validators.email]),
|
||||
name: new UntypedFormControl(null, [Validators.required]),
|
||||
password: new UntypedFormControl(null, [Validators.required, Validators.minLength(6)]),
|
||||
public form = this.fb.nonNullable.group({
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
name: ['', [Validators.required]],
|
||||
password: ['', [Validators.required, Validators.minLength(6)]],
|
||||
});
|
||||
public faNewUser = faUserPlus;
|
||||
public errorMessage = '';
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.form.reset();
|
||||
@@ -33,15 +35,20 @@ export class NewComponent implements OnInit {
|
||||
this.form.updateValueAndValidity();
|
||||
if (this.form.valid) {
|
||||
try {
|
||||
const value = this.form.value as {
|
||||
email: string;
|
||||
name: string;
|
||||
password: string;
|
||||
};
|
||||
await this.userService.createNewUser(value.email, value.name, value.password);
|
||||
this.errorMessage = '';
|
||||
const {email, name, password} = this.form.getRawValue();
|
||||
await this.userService.createNewUser(email, name, password);
|
||||
} catch (ex) {
|
||||
console.error(ex);
|
||||
this.errorMessage = this.errorCode(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private errorCode(ex: unknown): string {
|
||||
if (typeof ex === 'object' && ex !== null && 'code' in ex && typeof ex.code === 'string') {
|
||||
return ex.code;
|
||||
}
|
||||
|
||||
return 'unknown_error';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {Component, OnInit, inject} from '@angular/core';
|
||||
import {ReactiveFormsModule, UntypedFormControl, UntypedFormGroup, Validators} from '@angular/forms';
|
||||
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||
import {Router} from '@angular/router';
|
||||
import {UserService} from '../../../services/user/user.service';
|
||||
import {faWindowRestore} from '@fortawesome/free-solid-svg-icons';
|
||||
@@ -21,8 +21,8 @@ export class PasswordComponent implements OnInit {
|
||||
public userService = inject(UserService);
|
||||
private router = inject(Router);
|
||||
|
||||
public form: UntypedFormGroup = new UntypedFormGroup({
|
||||
user: new UntypedFormControl(null, [Validators.required, Validators.email]),
|
||||
public form = new FormGroup({
|
||||
user: new FormControl<string>('', {nonNullable: true, validators: [Validators.required, Validators.email]}),
|
||||
});
|
||||
|
||||
public errorMessage = '';
|
||||
@@ -36,12 +36,19 @@ export class PasswordComponent implements OnInit {
|
||||
this.form.updateValueAndValidity();
|
||||
if (this.form.valid) {
|
||||
try {
|
||||
const value = this.form.value as {user: string};
|
||||
await this.userService.changePassword(value.user);
|
||||
await this.userService.changePassword(this.form.controls.user.value);
|
||||
await this.router.navigateByUrl('/user/password-send');
|
||||
} catch ({code}) {
|
||||
this.errorMessage = code as string;
|
||||
} catch (error) {
|
||||
this.errorMessage = this.errorCode(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private errorCode(error: unknown): string {
|
||||
if (typeof error === 'object' && error !== null && 'code' in error && typeof error.code === 'string') {
|
||||
return error.code;
|
||||
}
|
||||
|
||||
return 'unknown_error';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,17 +16,17 @@ function normalize(input: string): string {
|
||||
|
||||
export const onlyUnique = <T>(value: T, index: number, array: T[]) => array.indexOf(value) === index;
|
||||
|
||||
export function dynamicSort(property: string) {
|
||||
export function dynamicSort<T extends Record<string, string | number>>(property: keyof T | `-${string & keyof T}`) {
|
||||
let sortOrder = 1;
|
||||
if (property[0] === '-') {
|
||||
let resolvedProperty = property as string;
|
||||
if (resolvedProperty[0] === '-') {
|
||||
sortOrder = -1;
|
||||
property = property.substr(1);
|
||||
resolvedProperty = resolvedProperty.slice(1);
|
||||
}
|
||||
return function (a: unknown, b: unknown) {
|
||||
/* next line works with strings and numbers,
|
||||
* and you may want to customize it to your needs
|
||||
*/
|
||||
const result = a[property] < b[property] ? -1 : a[property] > b[property] ? 1 : 0;
|
||||
return function (a: T, b: T): number {
|
||||
const left = a[resolvedProperty as keyof T];
|
||||
const right = b[resolvedProperty as keyof T];
|
||||
const result = left < right ? -1 : left > right ? 1 : 0;
|
||||
return result * sortOrder;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import {Directive, ElementRef, Input, OnInit, TemplateRef, ViewContainerRef, inject} from '@angular/core';
|
||||
import {Directive, DestroyRef, Input, OnInit, TemplateRef, ViewContainerRef, inject} from '@angular/core';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {UserService} from './user.service';
|
||||
|
||||
@Directive({selector: '[appOwner]'})
|
||||
export class OwnerDirective implements OnInit {
|
||||
private element = inject(ElementRef);
|
||||
private templateRef = inject<TemplateRef<unknown>>(TemplateRef);
|
||||
private viewContainer = inject(ViewContainerRef);
|
||||
private userService = inject(UserService);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
private currentUserId: string | null = null;
|
||||
private iAppOwner: string | null = null;
|
||||
@@ -18,14 +19,14 @@ export class OwnerDirective implements OnInit {
|
||||
}
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.userService.userId$.subscribe(user => {
|
||||
this.userService.userId$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(user => {
|
||||
this.currentUserId = user;
|
||||
this.updateView();
|
||||
});
|
||||
this.updateView();
|
||||
}
|
||||
|
||||
private updateView() {
|
||||
private updateView(): void {
|
||||
this.viewContainer.clear();
|
||||
if (this.currentUserId === this.iAppOwner) {
|
||||
this.viewContainer.createEmbeddedView(this.templateRef);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {ChangeDetectorRef, Directive, ElementRef, Input, OnInit, TemplateRef, ViewContainerRef, inject} from '@angular/core';
|
||||
import {ChangeDetectorRef, DestroyRef, Directive, Input, OnInit, TemplateRef, ViewContainerRef, inject} from '@angular/core';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {roles} from './roles';
|
||||
import {UserService} from './user.service';
|
||||
import {User} from './user';
|
||||
@@ -6,11 +7,11 @@ import {combineLatest} from 'rxjs';
|
||||
|
||||
@Directive({selector: '[appRole]'})
|
||||
export class RoleDirective implements OnInit {
|
||||
private element = inject(ElementRef);
|
||||
private templateRef = inject<TemplateRef<unknown>>(TemplateRef);
|
||||
private viewContainer = inject(ViewContainerRef);
|
||||
private userService = inject(UserService);
|
||||
private changeDetection = inject(ChangeDetectorRef);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
@Input() public appRole: roles[] = [];
|
||||
private currentUser: User | null = null;
|
||||
@@ -18,14 +19,16 @@ export class RoleDirective implements OnInit {
|
||||
private currentViewState = false;
|
||||
|
||||
public ngOnInit(): void {
|
||||
combineLatest([this.userService.user$, this.userService.loggedIn$()]).subscribe(_ => {
|
||||
this.currentUser = _[0];
|
||||
this.loggedIn = _[1];
|
||||
this.updateView();
|
||||
});
|
||||
combineLatest([this.userService.user$, this.userService.loggedIn$()])
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe(([user, loggedIn]) => {
|
||||
this.currentUser = user;
|
||||
this.loggedIn = loggedIn;
|
||||
this.updateView();
|
||||
});
|
||||
}
|
||||
|
||||
private updateView() {
|
||||
private updateView(): void {
|
||||
const viewState = this.loggedIn && this.checkPermission();
|
||||
if (this.currentViewState !== viewState) {
|
||||
if (!viewState) this.viewContainer.clear();
|
||||
@@ -35,7 +38,7 @@ export class RoleDirective implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
private checkPermission() {
|
||||
private checkPermission(): boolean {
|
||||
if (this.currentUser && this.currentUser.role) {
|
||||
if (this.currentUser.role === 'admin') {
|
||||
return true;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {ChangeDetectionStrategy, Component, Input, inject} from '@angular/core';
|
||||
import {ReactiveFormsModule, UntypedFormControl} from '@angular/forms';
|
||||
import {FormControl, ReactiveFormsModule} from '@angular/forms';
|
||||
import {filterSong} from '../../../services/filter.helper';
|
||||
import {MatFormField, MatLabel, MatOption, MatSelect, MatSelectChange} from '@angular/material/select';
|
||||
import {Song} from '../../../modules/songs/services/song';
|
||||
@@ -25,7 +25,7 @@ export class AddSongComponent {
|
||||
@Input() public showSongs: ShowSong[] | null = null;
|
||||
@Input() public show: Show | null = null;
|
||||
@Input() public addedLive = false;
|
||||
public filteredSongsControl = new UntypedFormControl();
|
||||
public filteredSongsControl = new FormControl<string>('', {nonNullable: true});
|
||||
|
||||
public filteredSongs(): Song[] {
|
||||
if (!this.songs) return [];
|
||||
@@ -44,7 +44,7 @@ export class AddSongComponent {
|
||||
return 0;
|
||||
});
|
||||
|
||||
const filterValue = this.filteredSongsControl.value as string;
|
||||
const filterValue = this.filteredSongsControl.value;
|
||||
return filterValue ? songs.filter(_ => filterSong(_, filterValue)) : songs;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {Component, inject} from '@angular/core';
|
||||
import {Component, DestroyRef, inject} from '@angular/core';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {ActivatedRoute, Params, Router} from '@angular/router';
|
||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
|
||||
@@ -10,13 +11,14 @@ import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
})
|
||||
export class FilterComponent {
|
||||
private router = inject(Router);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
public value = '';
|
||||
|
||||
public constructor() {
|
||||
const activatedRoute = inject(ActivatedRoute);
|
||||
|
||||
activatedRoute.queryParams.subscribe((params: Params) => {
|
||||
activatedRoute.queryParams.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params: Params) => {
|
||||
const typedParams = params as {q: string};
|
||||
if (typedParams.q) this.value = typedParams.q;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnInit, Output, QueryList, ViewChildren, inject} from '@angular/core';
|
||||
import {ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, QueryList, ViewChildren, inject} from '@angular/core';
|
||||
import {TextRenderingService} from '../../../modules/songs/services/text-rendering.service';
|
||||
import {faGripLines} from '@fortawesome/free-solid-svg-icons';
|
||||
import {songSwitch} from './animation';
|
||||
@@ -27,7 +27,7 @@ interface DisplaySegment {
|
||||
animations: [songSwitch],
|
||||
imports: [MatIconButton, FaIconComponent],
|
||||
})
|
||||
export class SongTextComponent implements OnInit {
|
||||
export class SongTextComponent implements OnInit, OnDestroy {
|
||||
private textRenderingService = inject(TextRenderingService);
|
||||
private elRef = inject<ElementRef<HTMLElement>>(ElementRef);
|
||||
private cRef = inject(ChangeDetectorRef);
|
||||
@@ -47,6 +47,7 @@ export class SongTextComponent implements OnInit {
|
||||
private invalidChordIssuesByLine = new Map<number, ChordValidationIssue[]>();
|
||||
private iText = '';
|
||||
private iTranspose: TransposeMode | null = null;
|
||||
private offsetIntervalId: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
@Input()
|
||||
public set chordMode(value: ChordMode) {
|
||||
@@ -73,7 +74,7 @@ export class SongTextComponent implements OnInit {
|
||||
}
|
||||
|
||||
public ngOnInit(): void {
|
||||
setInterval(() => {
|
||||
this.offsetIntervalId = setInterval(() => {
|
||||
if (!this.fullscreen || this.index === -1 || !this.viewSections?.toArray()[this.index]) {
|
||||
this.offset = 0;
|
||||
this.cRef.markForCheck();
|
||||
@@ -84,6 +85,12 @@ export class SongTextComponent implements OnInit {
|
||||
}, 100);
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
if (this.offsetIntervalId) {
|
||||
clearInterval(this.offsetIntervalId);
|
||||
}
|
||||
}
|
||||
|
||||
public getLines(section: Section): Line[] {
|
||||
return section.lines.filter(_ => {
|
||||
if (_.type !== LineType.chord) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import {Injectable, inject} from '@angular/core';
|
||||
import {ActivatedRouteSnapshot, Router, UrlTree} from '@angular/router';
|
||||
import {Observable} from 'rxjs';
|
||||
import {UserService} from '../../services/user/user.service';
|
||||
import {map} from 'rxjs/operators';
|
||||
import {map, take} from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -18,8 +18,11 @@ export class RoleGuard {
|
||||
}
|
||||
|
||||
return this.userService.user$.pipe(
|
||||
take(1),
|
||||
map(user => {
|
||||
if (!user) return false;
|
||||
if (!user) {
|
||||
return this.router.createUrlTree(['brand', 'new-user']);
|
||||
}
|
||||
const roles = user.role?.split(';') ?? [];
|
||||
if (roles.indexOf('admin') !== -1) {
|
||||
return true;
|
||||
|
||||
@@ -13,6 +13,8 @@ export class SectionTypePipe implements PipeTransform {
|
||||
return 'Refrain';
|
||||
case SectionType.Bridge:
|
||||
return 'Bridge';
|
||||
case SectionType.Comment:
|
||||
return 'Kommentar';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"moduleResolution": "bundler",
|
||||
"importHelpers": true,
|
||||
"target": "ES2022",
|
||||
"strict": false,
|
||||
"strict": true,
|
||||
"typeRoots": ["node_modules/@types"],
|
||||
"useDefineForClassFields": false
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user