ui enhancements and song state

This commit is contained in:
2020-04-25 22:29:34 +02:00
committed by smuddy
parent 01d13ccea9
commit 4c5a8c972c
43 changed files with 297 additions and 141 deletions

View File

@@ -32,7 +32,7 @@ export class MonitorComponent implements OnInit {
switchMap(_ => this.showService.read$(_)),
tap(_ => this.index = _.presentationSection),
tap(_ => this.zoom = _.presentationZoom ?? 30),
switchMap(_ => this.songService.read(_.presentationSongId))
switchMap(_ => this.songService.read$(_.presentationSongId))
).subscribe(_ => {
this.song = _;
this.sections = this.textRenderingService.parse(_.text);

View File

@@ -141,11 +141,11 @@ export class DocxService {
private async prepareData(showId: string): Promise<{ songs: ({ showSong: ShowSong, song: Song, sections: Section[] })[]; show: Show, user: User }> {
const show = await this.showService.read$(showId).pipe(first()).toPromise();
const user = await this.userService.getUserbyId$(show.owner).pipe(first()).toPromise();
const user = await this.userService.getUserbyId(show.owner);
const showSongs = await this.showSongService.list$(showId).pipe(first()).toPromise();
const showSongs = await this.showSongService.list(showId);
const songsAsync = await showSongs.map(async showSong => {
const song = await this.songService.read(showSong.songId).pipe(first()).toPromise();
const song = await this.songService.read(showSong.songId);
const sections = this.textRenderingService.parse(song.text);
return {
showSong,

View File

@@ -3,7 +3,7 @@ import {ShowSongDataService} from './show-song-data.service';
import {Observable} from 'rxjs';
import {ShowSong} from './show-song';
import {SongDataService} from '../../songs/services/song-data.service';
import {take} from 'rxjs/operators';
import {first, take} from 'rxjs/operators';
import {UserService} from '../../../services/user/user.service';
@Injectable({
@@ -33,6 +33,7 @@ export class ShowSongService {
}
public list$ = (showId: string): Observable<ShowSong[]> => this.showSongDataService.list$(showId, _ => _.orderBy('order'));
public list = (showId: string): Promise<ShowSong[]> => this.list$(showId).pipe(first()).toPromise();
public delete$ = (showId: string, songId: string): Promise<void> => this.showSongDataService.delete(showId, songId);
public update$ = async (showId: string, songId: string, data: Partial<ShowSong>): Promise<void> => await this.showSongDataService.update$(showId, songId, data);
}

View File

@@ -1,5 +1,6 @@
<div *ngIf="(show$|async) as show">
<app-card
closeLink="../"
heading="{{show.showType|showType}}, {{show.date.toDate()|date:'dd.MM.yyyy'}} - {{getStatus(show)}}">
<i *ngIf="show.public">öffentliche Veranstaltung</i>
<i *ngIf="!show.public">geschlossene Veranstaltung</i>

View File

@@ -2,7 +2,7 @@ import {Injectable} from '@angular/core';
import {Observable} from 'rxjs';
import {Song} from './song';
import {SongDataService} from './song-data.service';
import {tap} from 'rxjs/operators';
import {first, tap} from 'rxjs/operators';
declare var importCCLI: any;
@@ -12,6 +12,7 @@ declare var importCCLI: any;
export class SongService {
public static TYPES = ['Praise', 'Worship'];
public static STATUS = ['draft', 'set', 'final'];
public static LEGAL_OWNER = ['CCLI', 'other'];
public static LEGAL_TYPE = ['open', 'allowed'];
@@ -24,7 +25,8 @@ export class SongService {
}
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 async update$(songId: string, data: Partial<Song>): Promise<void> {
await this.songDataService.update$(songId, data);

View File

@@ -9,6 +9,7 @@ export interface Song {
title: string;
type: string;
flags: string;
status: string;
legalType: string;
legalLink: string;

View File

@@ -1,4 +1,4 @@
<app-card *ngIf="song" [heading]="song.number + ' bearbeiten'">
<app-card *ngIf="song" [heading]="song.number + ' bearbeiten'" closeLink="../">
<form [formGroup]="form" class="form">
<mat-form-field appearance="outline">
@@ -6,7 +6,7 @@
<input formControlName="title" matInput>
</mat-form-field>
<div class="third">
<div class="fourth">
<mat-form-field appearance="outline">
<mat-label>Typ</mat-label>
<mat-select formControlName="type">
@@ -23,6 +23,12 @@
<mat-label>Tempo</mat-label>
<input formControlName="tempo" matInput>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Status</mat-label>
<mat-select formControlName="status">
<mat-option *ngFor="let status of status" [value]="status">{{status | status}}</mat-option>
</mat-select>
</mat-form-field>
</div>
<mat-form-field appearance="outline">
@@ -50,60 +56,61 @@
</mat-chip-list>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Rechtlicher Status</mat-label>
<mat-select formControlName="legalType">
<mat-option *ngFor="let key of legalType" [value]="key">{{key|legalType}}</mat-option>
</mat-select>
</mat-form-field>
<div class="half">
<mat-form-field appearance="outline">
<mat-label>Rechtlicher Status</mat-label>
<mat-select formControlName="legalType">
<mat-option *ngFor="let key of legalType" [value]="key">{{key|legalType}}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Rechteinhaber</mat-label>
<mat-select formControlName="legalOwner">
<mat-option *ngFor="let key of legalOwner" [value]="key">{{key|legalOwner}}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Rechteinhaber</mat-label>
<mat-select formControlName="legalOwner">
<mat-option *ngFor="let key of legalOwner" [value]="key">{{key|legalOwner}}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Rechteinhaber Link</mat-label>
<input formControlName="legalLink" matInput>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Rechteinhaber Link</mat-label>
<input formControlName="legalLink" matInput>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Rechteinhaber ID (z.B. CCLI Liednummer)</mat-label>
<input formControlName="legalOwnerId" matInput>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Rechteinhaber ID (z.B. CCLI Liednummer)</mat-label>
<input formControlName="legalOwnerId" matInput>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Lizenznummer</mat-label>
<input formControlName="legalLicenseId" matInput>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Lizenznummer</mat-label>
<input formControlName="legalLicenseId" matInput>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Künstler</mat-label>
<input formControlName="artist" matInput>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Künstler</mat-label>
<input formControlName="artist" matInput>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Verlag</mat-label>
<input formControlName="label" matInput>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Verlag</mat-label>
<input formControlName="label" matInput>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Nutzungsbedingungen</mat-label>
<input formControlName="termsOfUse" matInput>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Nutzungsbedingungen</mat-label>
<input formControlName="termsOfUse" matInput>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>abweichende Quelle</mat-label>
<input formControlName="origin" matInput>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>abweichende Quelle</mat-label>
<input formControlName="origin" matInput>
</mat-form-field>
</div>
</form>
<app-button-row>
<button (click)="onSave()" color="primary" mat-flat-button>Speichern</button>
<button mat-stroked-button routerLink="../">Abbrechen</button>
<button (click)="onSave()" mat-button>Speichern</button>
</app-button-row>
</app-card>

View File

@@ -6,9 +6,18 @@
width: 100%;
}
.third {
.fourth {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-columns: 1fr 1fr 1fr 1fr;
@media screen and (max-width: 860px) {
grid-template-columns: 1fr 1fr;
}
column-gap: 20px;
}
.half {
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: 20px;
}

View File

@@ -20,6 +20,7 @@ export class EditSongComponent implements OnInit {
public form: FormGroup;
public keys = KEYS;
public types = SongService.TYPES;
public status = SongService.STATUS;
public legalOwner = SongService.LEGAL_OWNER;
public legalType = SongService.LEGAL_TYPE;
public flags: string[] = [];
@@ -37,7 +38,7 @@ export class EditSongComponent implements OnInit {
public ngOnInit(): void {
this.activatedRoute.params.pipe(
map(param => param.songId),
switchMap(songId => this.songService.read(songId)),
switchMap(songId => this.songService.read$(songId)),
first()
).subscribe(song => {
this.song = song;
@@ -55,7 +56,7 @@ export class EditSongComponent implements OnInit {
public removeFlag(flag: string): void {
const flags = this.flags.filter(_ => _ !== flag);
this.form.controls.flags.setValue(flags.reduce((a, b) => `${a};${b}`, ''));
this.form.controls.flags.setValue(flags.join(';'));
}
public addFlag(event: MatChipInputEvent): void {
@@ -65,14 +66,13 @@ export class EditSongComponent implements OnInit {
// Add our fruit
if ((value || '').trim()) {
const flags = [...this.flags, value.trim()];
this.form.controls.flags.setValue(flags.reduce((a, b) => `${a};${b}`, ''));
this.form.controls.flags.setValue(flags.join(';'));
}
if (input) input.value = '';
}
private onFlagsChanged(flagArray: string): void {
console.log(flagArray);
if (!flagArray) {
this.flags = [];
return;

View File

@@ -19,6 +19,7 @@ import {LegalTypeTranslatorModule} from '../../../../widget-modules/pipes/legal-
import {KeyTranslatorModule} from '../../../../widget-modules/pipes/key-translator/key-translator.module';
import {MatChipsModule} from '@angular/material/chips';
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {StatusTranslaterModule} from '../../../../widget-modules/pipes/status-translater/status-translater.module';
@NgModule({
@@ -43,6 +44,7 @@ import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
KeyTranslatorModule,
MatChipsModule,
FontAwesomeModule,
StatusTranslaterModule,
]
})

View File

@@ -19,6 +19,7 @@ export class EditService {
key: new FormControl(song.key),
tempo: new FormControl(song.tempo),
type: new FormControl(song.type),
status: new FormControl(song.status ?? 'draft'),
legalType: new FormControl(song.legalType),
legalLink: new FormControl(song.legalLink),

View File

@@ -1,17 +1,19 @@
<div class="split">
<app-card *ngIf="song$ | async as song" [heading]="song.number + ' - ' + song.title">
<app-card *ngIf="song$ | async as song" [heading]="song.number + ' - ' + song.title" closeLink="../">
<div class="song">
<div>
<div class="detail">
<div *appRole="['leader', 'contributor']" class="detail">
<div>Typ: {{song.type | songType}}</div>
<div>Tonart: {{song.key}}</div>
<div>Tempo: {{song.tempo}}</div>
<div>Status: {{(song.status|status) || 'entwurf'}}</div>
<div *ngIf="song.legalOwner">Rechteinhaber: {{song.legalOwner|legalOwner}}</div>
<div *ngIf="song.legalOwnerId">Rechteinhaber ID: {{song.legalOwnerId}}</div>
<div *ngIf="song.legalLicenseId">Lizenznummer: {{song.legalLicenseId}}</div>
<div *ngIf="song.artist">Künstler: {{song.artist}}</div>
<div *ngIf="song.label">Verlag: {{song.label}}</div>
<div *ngIf="song.origin">Quelle: {{song.origin}}</div>
<div *ngIf="song.origin">Quelle: {{song.origin}}</div>
</div>
</div>
@@ -19,24 +21,20 @@
<app-song-text *ngIf="user$|async as user" [chordMode]="user.chordMode" [showSwitch]="true"
[text]="song.text"></app-song-text>
<mat-chip-list aria-label="Attribute">
<mat-chip-list *appRole="['leader', 'contributor']" aria-label="Attribute">
<mat-chip *ngFor="let flag of getFlags(song.flags)">{{flag}}</mat-chip>
</mat-chip-list>
<div class="text">{{song.comment}}</div>
<div>
<h3>Anhänge</h3>
<div *ngFor="let file of (files$|async)">{{file.name}}</div>
</div>
<div *appRole="['leader', 'contributor']" class="text">{{song.comment}}</div>
</div>
<app-button-row>
<button color="primary" mat-flat-button routerLink="edit">Bearbeiten</button>
<button mat-button routerLink="../">Schließen</button>
<button *appRole="['contributor']" mat-button routerLink="edit">Bearbeiten</button>
</app-button-row>
</app-card>
<app-card>
<app-card *ngIf="!!(files$|async)" heading="Anhänge">
<div *ngFor="let file of (files$|async)">{{file.name}}</div>
</app-card>
</div>

View File

@@ -8,10 +8,11 @@
.text {
white-space: pre-wrap;
font-family: 'Ubuntu Mono', monospace;
font-style: italic;
}
.detail {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
font-style: italic;
}

View File

@@ -31,7 +31,7 @@ export class SongComponent implements OnInit {
public ngOnInit(): void {
this.song$ = this.activatedRoute.params.pipe(
map(param => param.songId),
switchMap(songId => this.songService.read(songId))
switchMap(songId => this.songService.read$(songId))
);
this.files$ = this.activatedRoute.params.pipe(

View File

@@ -9,6 +9,8 @@ import {RouterModule} from '@angular/router';
import {LegalOwnerTranslatorModule} from '../../../widget-modules/pipes/legal-owner-translator/legal-owner-translator.module';
import {SongTextModule} from '../../../widget-modules/components/song-text/song-text.module';
import {MatChipsModule} from '@angular/material/chips';
import {RoleModule} from '../../../services/user/role.module';
import {StatusTranslaterModule} from '../../../widget-modules/pipes/status-translater/status-translater.module';
@NgModule({
@@ -25,6 +27,8 @@ import {MatChipsModule} from '@angular/material/chips';
LegalOwnerTranslatorModule,
SongTextModule,
MatChipsModule,
RoleModule,
StatusTranslaterModule,
]
})
export class SongModule {

View File

@@ -1,5 +1,4 @@
<app-card *ngIf="user$|async as user">
<h2>Hallo {{user.name}}</h2>
<app-card *ngIf="user$|async as user" heading="Hallo {{user.name}}">
<p>{{user.role|role}}</p>
<mat-form-field appearance="outline">

View File

@@ -9,7 +9,7 @@ export class RolePipe implements PipeTransform {
transform(role: roles): string {
switch (role) {
case 'distributor':
case 'contributor':
return 'Mitarbeiter';
case 'none':
return 'keine Berechtigung';

View File

@@ -1,4 +1,4 @@
import {Component, Input, OnInit} from '@angular/core';
import {Component, Input} from '@angular/core';
import {User} from '../../../../../services/user/user';
import {UserService} from '../../../../../services/user/user.service';
import {ROLE_TYPES} from '../../../../../services/user/roles';
@@ -8,7 +8,7 @@ import {ROLE_TYPES} from '../../../../../services/user/roles';
templateUrl: './user.component.html',
styleUrls: ['./user.component.less']
})
export class UserComponent implements OnInit {
export class UserComponent {
public id: string;
public name: string
public roles: string[];
@@ -23,10 +23,6 @@ export class UserComponent implements OnInit {
this.roles = this.getRoleArray(value.role);
};
ngOnInit(): void {
}
public async onRoleChanged(id: string, roles: string[]): Promise<void> {
const role = roles.join(';');
await this.userService.update$(id, {role});

View File

@@ -11,7 +11,8 @@
</mat-form-field>
<app-button-row>
<button (click)="onLogin()" mat-button>Anmelden</button>
<button mat-button routerLink="/user/password">neues Passwort anfordern</button>
<button mat-button routerLink="/user/password">Passwort zurücksetzen</button>
<button mat-button routerLink="/user/new">neuen Benutzer anlegen</button>
<p *ngIf="errorMessage" class="error">{{errorMessage|authMessage}}</p>
</app-button-row>

View File

@@ -1 +0,0 @@
<p>logout works!</p>

View File

@@ -0,0 +1,18 @@
<app-card [formGroup]="form" closeLink="../" heading="neuen Benutzer anlegen">
<mat-form-field appearance="outline">
<mat-label>Name</mat-label>
<input formControlName="name" matInput>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>E-Mail Adresse</mat-label>
<input formControlName="email" matInput>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Passwort</mat-label>
<input formControlName="password" matInput>
</mat-form-field>
<app-button-row>
<button (click)="onCreate()" mat-button>Benutzer anlegen</button>
</app-button-row>
</app-card>

View File

@@ -0,0 +1,5 @@
.users {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 20px;
}

View File

@@ -0,0 +1,25 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {NewComponent} from './new.component';
describe('NewComponent', () => {
let component: NewComponent;
let fixture: ComponentFixture<NewComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [NewComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,35 @@
import {Component, OnInit} from '@angular/core';
import {FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms';
import {UserService} from '../../../services/user/user.service';
@Component({
selector: 'app-new',
templateUrl: './new.component.html',
styleUrls: ['./new.component.less']
})
export class NewComponent implements OnInit {
public form: FormGroup;
constructor(private fb: FormBuilder, private userService: UserService) {
}
ngOnInit(): void {
this.form = this.fb.group({
email: new FormControl(null, [Validators.required, Validators.email]),
name: new FormControl(null, [Validators.required]),
password: new FormControl(null, [Validators.required, Validators.minLength(6)]),
})
}
public async onCreate(): Promise<void> {
this.form.updateValueAndValidity();
console.log(this.form);
if (this.form.valid) {
try {
await this.userService.createNewUser(this.form.value.email, this.form.value.name, this.form.value.password);
} catch (ex) {
console.error(ex);
}
}
}
}

View File

@@ -1,4 +1,4 @@
<app-card>
<app-card closeLink="../" heading="Passwort zurücksetzen">
<div [formGroup]="form" class="form">
<mat-form-field appearance="outline">
<mat-label>E-Mail Addresse</mat-label>

View File

@@ -6,6 +6,7 @@ import {LogoutComponent} from './logout/logout.component';
import {AngularFireAuthGuard, redirectUnauthorizedTo} from '@angular/fire/auth-guard';
import {PasswordComponent} from './password/password.component';
import {PasswordSendComponent} from './password-send/password-send.component';
import {NewComponent} from './new/new.component';
const routes: Routes = [
@@ -26,6 +27,10 @@ const routes: Routes = [
path: 'password',
component: PasswordComponent
},
{
path: 'new',
component: NewComponent
},
{
path: 'password-send',
component: PasswordSendComponent

View File

@@ -18,10 +18,11 @@ import {PasswordSendComponent} from './password-send/password-send.component';
import {UsersComponent} from './info/users/users.component';
import {RoleModule} from '../../services/user/role.module';
import {UserComponent} from './info/users/user/user.component';
import {NewComponent} from './new/new.component';
@NgModule({
declarations: [LoginComponent, AuthMessagePipe, InfoComponent, LogoutComponent, RolePipe, PasswordComponent, PasswordSendComponent, UsersComponent, UserComponent],
declarations: [LoginComponent, AuthMessagePipe, InfoComponent, LogoutComponent, RolePipe, PasswordComponent, PasswordSendComponent, UsersComponent, UserComponent, NewComponent],
imports: [
CommonModule,
UserRoutingModule,