presenter function base

This commit is contained in:
2020-04-01 14:19:56 +02:00
committed by smuddy
parent 49e51f015e
commit 643f3aff43
20 changed files with 382 additions and 3 deletions

View File

@@ -20,6 +20,12 @@ const routes: Routes = [
canActivate: [AngularFireAuthGuard],
data: {authGuardPipe: () => redirectUnauthorizedTo(['user', 'login'])}
},
{
path: 'presentation',
loadChildren: () => import('./modules/presentation/presentation.module').then(m => m.PresentationModule),
canActivate: [AngularFireAuthGuard],
data: {authGuardPipe: () => redirectUnauthorizedTo(['user', 'login'])}
},
{
path: 'user',
loadChildren: () => import('./modules/user/user.module').then(m => m.UserModule)

View File

@@ -0,0 +1,5 @@
<div class="fullscreen">
<app-song-text [showSwitch]="false" [text]="song.text" chordMode="hide"></app-song-text>
</div>

View File

@@ -0,0 +1,14 @@
.fullscreen {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: black;
z-index: 1;
padding: 50px;
color: white;
font-size: 30px;
}

View File

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

View File

@@ -0,0 +1,39 @@
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {map, switchMap, tap} from 'rxjs/operators';
import {ShowService} from '../../shows/services/show.service';
import {SongService} from '../../songs/services/song.service';
import {Section, TextRenderingService} from '../../songs/services/text-rendering.service';
import {Song} from '../../songs/services/song';
@Component({
selector: 'app-monitor',
templateUrl: './monitor.component.html',
styleUrls: ['./monitor.component.less']
})
export class MonitorComponent implements OnInit {
public song: Song;
private index: number;
private sections: Section[];
constructor(
private activatedRoute: ActivatedRoute,
private showService: ShowService,
private songService: SongService,
private textRenderingService: TextRenderingService,
) {
}
ngOnInit(): void {
this.activatedRoute.params.pipe(
map(_ => _.showId),
switchMap(_ => this.showService.read$(_)),
tap(_ => this.index = _.presentationSection),
switchMap(_ => this.songService.read(_.presentationSongId))
).subscribe(_ => {
this.song = _;
this.sections = this.textRenderingService.parse(_.text);
});
}
}

View File

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

View File

@@ -0,0 +1,29 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {PresentationRoutingModule} from './presentation-routing.module';
import {MonitorComponent} from './monitor/monitor.component';
import {RemoteComponent} from './remote/remote.component';
import {CardModule} from '../../widget-modules/components/card/card.module';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatSelectModule} from '@angular/material/select';
import {ShowTypeTranslaterModule} from '../../widget-modules/pipes/show-type-translater/show-type-translater.module';
import {SectionTypeTranslatorModule} from '../../widget-modules/pipes/section-type-translator/section-type-translator.module';
import {SongTextModule} from '../../widget-modules/components/song-text/song-text.module';
@NgModule({
declarations: [MonitorComponent, RemoteComponent],
imports: [
CommonModule,
PresentationRoutingModule,
CardModule,
MatFormFieldModule,
MatSelectModule,
ShowTypeTranslaterModule,
SectionTypeTranslatorModule,
SongTextModule
]
})
export class PresentationModule {
}

View File

@@ -0,0 +1,29 @@
<div *ngIf="shows$|async as shows">
<app-card>
<mat-form-field appearance="outline">
<mat-label>Veranstaltung</mat-label>
<mat-select (selectionChange)="onShowChanged($event)">
<mat-option *ngFor="let show of shows" [value]="show.id">
{{show.showType|showType}}, {{show.date.toDate()|date:'dd.MM.yyyy'}}
</mat-option>
</mat-select>
</mat-form-field>
<div *ngFor="let song of presentationSongs" class="song">
<div class="title">{{song.title}}</div>
<div *ngIf="show$|async as show" class="song-parts">
<div (click)="onSectionClick(song.id, i)" *ngFor="let section of song.sections; index as i"
[class.active]="show.presentationSongId===song.id && show.presentationSection===i"
class="song-part">
<div class="head">{{section.type|sectionType}} {{section.number + 1}}</div>
<div class="fragment">{{getFirstLine(section)}}</div>
</div>
</div>
</div>
<a [routerLink]="'/presentation/monitor/' + currentShowId">Presenter öffnen</a>
</app-card>
</div>

View File

@@ -0,0 +1,67 @@
@import "../../../../styles/shadow";
.song {
background: #fffa;
width: 100%;
padding: 10px;
border-radius: 8px;
margin-bottom: 10px;
box-sizing: border-box;
}
.title {
font-weight: bold;
padding: 0 10px 10px 10px;
}
.song-parts {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
@media screen and (max-width: 860px) {
grid-template-columns: 1fr 1fr 1fr 1fr;
}
@media screen and (max-width: 660px) {
grid-template-columns: 1fr 1fr 1fr;
}
@media screen and (max-width: 460px) {
grid-template-columns: 1fr 1fr;
}
grid-gap: 10px;
}
.song-part {
background: #fff;
border-radius: 8px;
overflow: hidden;
transition: 300ms all ease-in-out;
cursor: pointer;
.card-1;
&:hover {
.card-2;
}
&.active {
.card-5;
.head {
background: #4286f4;
color: white;
border-bottom: 0.5px solid #4286f4;
}
}
}
.head {
transition: 300ms all ease-in-out;
background: #f4f4f4;
border-bottom: 0.5px solid #ddd;
padding: 10px;
font-weight: bold;
}
.fragment {
padding: 10px;
}

View File

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

View File

@@ -0,0 +1,63 @@
import {Component} from '@angular/core';
import {ShowDataService} from '../../shows/services/show-data.service';
import {Observable} from 'rxjs';
import {Show} from '../../shows/services/show';
import {MatSelectChange} from '@angular/material/select';
import {ShowSongService} from '../../shows/services/show-song.service';
import {SongService} from '../../songs/services/song.service';
import {Song} from '../../songs/services/song';
import {Section, TextRenderingService} from '../../songs/services/text-rendering.service';
export interface PresentationSong {
id: string;
title: string;
sections: Section[];
}
@Component({
selector: 'app-remote',
templateUrl: './remote.component.html',
styleUrls: ['./remote.component.less']
})
export class RemoteComponent {
public shows$: Observable<Show[]>;
public show$: Observable<Show>;
public songs: Song[];
public presentationSongs: PresentationSong[];
public currentShowId: string;
constructor(
private showDataService: ShowDataService,
private showSongService: ShowSongService,
private songService: SongService,
private textRenderingService: TextRenderingService
) {
this.shows$ = showDataService.list$();
songService.list$().subscribe(_ => this.songs = _);
}
public onShowChanged(change: MatSelectChange): void {
this.currentShowId = change.value;
this.show$ = this.showDataService.read$(change.value);
this.showSongService.list$(change.value).subscribe(_ => {
this.presentationSongs = _
.map(song => this.songs.filter(f => f.id == song.songId)[0])
.map(song => ({
id: song.id,
title: song.title,
sections: this.textRenderingService.parse(song.text)
}))
});
}
public getFirstLine(section: Section): string {
return section.lines.filter(_ => _.type === 1)[0].text;
}
public async onSectionClick(id: string, index: number): Promise<void> {
await this.showDataService.update(this.currentShowId, {
presentationSongId: id,
presentationSection: index
})
}
}

View File

@@ -0,0 +1,16 @@
import {TestBed} from '@angular/core/testing';
import {PresentationService} from './presentation.service';
describe('PresentationService', () => {
let service: PresentationService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(PresentationService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

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

View File

@@ -1,7 +1,6 @@
import * as firebase from 'firebase';
import Timestamp = firebase.firestore.Timestamp;
export interface Show {
id: string;
showType: string;
@@ -9,5 +8,9 @@ export interface Show {
owner: string;
public: boolean;
reported: boolean;
presentationSongId: string;
presentationSection: number;
}

View File

@@ -2,6 +2,7 @@
<div class="links">
<app-link [icon]="faSongs" link="/songs" text="Lieder"></app-link>
<app-link [icon]="faShows" link="/shows" text="Veranstaltungen"></app-link>
<app-link [icon]="faPresentation" link="/presentation" text="Präsentation"></app-link>
<app-link [icon]="faUser" link="/user" text="Benutzer"></app-link>
</div>
<div class="actions">

View File

@@ -4,6 +4,7 @@ import {faPersonBooth} from '@fortawesome/free-solid-svg-icons/faPersonBooth';
import {faUserCog} from '@fortawesome/free-solid-svg-icons/faUserCog';
import {fromEvent} from 'rxjs';
import {distinctUntilChanged, map, shareReplay, startWith} from 'rxjs/operators';
import {faChalkboard} from '@fortawesome/free-solid-svg-icons/faChalkboard';
@Component({
selector: 'app-navigation',
@@ -15,6 +16,7 @@ export class NavigationComponent {
public faSongs = faMusic;
public faShows = faPersonBooth;
public faUser = faUserCog;
public faPresentation = faChalkboard;
public readonly windowScroll$ = fromEvent(window, 'scroll').pipe(map(x => window.scrollY), startWith(0), distinctUntilChanged(), shareReplay(1));

View File

@@ -4,7 +4,7 @@
.card-3;
margin: 20px;
border-radius: 8px;
background: #fffe;
background: #fffb;
overflow: hidden;
width: 800px;
transition: 300ms all ease-in-out;

View File

@@ -1,6 +1,6 @@
<div (click)="onClick()" [class.chords]="_chordMode!=='hide'" class="song-text">
<div (click)="onChordClick()" class="menu">
<div (click)="onChordClick()" *ngIf="showSwitch" class="menu">
<fa-icon [icon]="faLines"></fa-icon>
</div>

View File

@@ -18,6 +18,7 @@ export type ChordMode = 'show' | 'hide' | 'onlyFirst'
export class SongTextComponent implements OnInit {
public sections: Section[];
public _chordMode: ChordMode = 'hide';
@Input() showSwitch = false;
@Input()
public set chordMode(value: ChordMode) {
this._chordMode = value ?? 'hide';

View File

@@ -0,0 +1,16 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {SectionTypePipe} from './section-type.pipe';
@NgModule({
declarations: [SectionTypePipe],
exports: [
SectionTypePipe
],
imports: [
CommonModule
]
})
export class SectionTypeTranslatorModule {
}