migrate angular 21

This commit is contained in:
2026-03-09 22:43:40 +01:00
parent 0203d4ea9d
commit 26c99a0dae
65 changed files with 19188 additions and 16946 deletions

View File

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

View File

@@ -1,13 +1,12 @@
import {Component, Input} from '@angular/core';
import {Song} from '../../../songs/services/song';
import {Config} from '../../../../services/config';
import {NgIf} from '@angular/common';
@Component({
selector: 'app-legal',
templateUrl: './legal.component.html',
styleUrls: ['./legal.component.less'],
imports: [NgIf],
imports: [],
})
export class LegalComponent {
@Input() public song: Song | null = null;

View File

@@ -1,48 +1,50 @@
<div class="fullscreen background"></div>
<div *ngIf="showType" [style.font-size.px]="zoom" class="fullscreen background">
<div [class.visible]="presentationBackground==='blue'" class="bg-blue fullscreen bg-image"></div>
<div [class.visible]="presentationBackground==='green'" class="bg-green fullscreen bg-image"></div>
<div [class.visible]="presentationBackground==='leder'" class="bg-leder fullscreen bg-image"></div>
<div [class.visible]="presentationBackground==='praise'" class="bg-praise fullscreen bg-image"></div>
<div [class.visible]="presentationBackground==='bible'" class="bg-bible fullscreen bg-image"></div>
<div
[@songSwitch]="songId"
[class.blur]="songId === 'title' || songId === 'dynamicText'"
[class.hide]="songId !== 'title' && songId !== 'empty' && songId !== 'dynamicText'"
[class.no-logo]="presentationBackground!=='none'"
class="start fullscreen logo"
>
<app-logo></app-logo>
@if (showType) {
<div [style.font-size.px]="zoom" class="fullscreen background">
<div [class.visible]="presentationBackground==='blue'" class="bg-blue fullscreen bg-image"></div>
<div [class.visible]="presentationBackground==='green'" class="bg-green fullscreen bg-image"></div>
<div [class.visible]="presentationBackground==='leder'" class="bg-leder fullscreen bg-image"></div>
<div [class.visible]="presentationBackground==='praise'" class="bg-praise fullscreen bg-image"></div>
<div [class.visible]="presentationBackground==='bible'" class="bg-bible fullscreen bg-image"></div>
<div
[@songSwitch]="songId"
[class.blur]="songId === 'title' || songId === 'dynamicText'"
[class.hide]="songId !== 'title' && songId !== 'empty' && songId !== 'dynamicText'"
[class.no-logo]="presentationBackground!=='none'"
class="start fullscreen logo"
>
<app-logo></app-logo>
</div>
@if (songId === 'title') {
<div @songSwitch class="start fullscreen">
<div>{{ showType | showType }}</div>
<div class="date">{{ date | date: "dd.MM.yyyy" }}</div>
</div>
}
@if (songId === 'dynamicText') {
<div @songSwitch class="start fullscreen dynamic-text">
<div>{{ presentationDynamicCaption }}</div>
<div class="date">{{ presentationDynamicText }}</div>
</div>
}
@if (song && songId !== 'title' && songId !== 'empty' && songId !== 'dynamicText') {
<app-song-text
[@songSwitch]="songId"
[fullscreen]="true"
[header]="song.title"
[index]="index??0"
[showComments]="false"
[showSwitch]="false"
[text]="song.text"
chordMode="hide"
></app-song-text>
}
@if (song && songId !== 'title' && songId !== 'empty' && songId !== 'dynamicText') {
<app-legal
[@songSwitch]="songId"
[config]="config$ | async"
[song]="song"
></app-legal>
}
</div>
<div *ngIf="songId === 'title'" @songSwitch class="start fullscreen">
<div>{{ showType | showType }}</div>
<div class="date">{{ date | date: "dd.MM.yyyy" }}</div>
</div>
<div *ngIf="songId === 'dynamicText'" @songSwitch class="start fullscreen dynamic-text">
<div>{{ presentationDynamicCaption }}</div>
<div class="date">{{ presentationDynamicText }}</div>
</div>
<app-song-text
*ngIf="song && songId !== 'title' && songId !== 'empty' && songId !== 'dynamicText'"
[@songSwitch]="songId"
[fullscreen]="true"
[header]="song.title"
[index]="index??0"
[showComments]="false"
[showSwitch]="false"
[text]="song.text"
chordMode="hide"
></app-song-text>
<app-legal
*ngIf="song && songId !== 'title' && songId !== 'empty' && songId !== 'dynamicText'"
[@songSwitch]="songId"
[config]="config$ | async"
[song]="song"
></app-legal>
</div>
}

View File

@@ -11,7 +11,7 @@ import {PresentationBackground, Show} from '../../shows/services/show';
import {ShowSong} from '../../shows/services/show-song';
import {ShowSongService} from '../../shows/services/show-song.service';
import {openFullscreen} from '../../../services/fullscreen';
import {AsyncPipe, DatePipe, NgIf} from '@angular/common';
import {AsyncPipe, DatePipe} from '@angular/common';
import {LogoComponent} from './logo/logo.component';
import {SongTextComponent} from '../../../widget-modules/components/song-text/song-text.component';
import {LegalComponent} from './legal/legal.component';
@@ -22,7 +22,7 @@ import {ShowTypePipe} from '../../../widget-modules/pipes/show-type-translater/s
templateUrl: './monitor.component.html',
styleUrls: ['./monitor.component.less'],
animations: [songSwitch],
imports: [NgIf, LogoComponent, SongTextComponent, LegalComponent, AsyncPipe, DatePipe, ShowTypePipe],
imports: [LogoComponent, SongTextComponent, LegalComponent, AsyncPipe, DatePipe, ShowTypePipe],
})
export class MonitorComponent implements OnInit, OnDestroy {
public song: Song | null = null;
@@ -50,26 +50,24 @@ export class MonitorComponent implements OnInit, OnDestroy {
public ngOnInit(): void {
openFullscreen();
const currentShowId$ = this.globalSettingsService.get$
.pipe(
filter((settings): settings is NonNullable<typeof settings> => !!settings),
map(settings => settings.currentShow),
filter((showId): showId is string => !!showId),
distinctUntilChanged(),
tap(_ => (this.currentShowId = _)),
takeUntil(this.destroy$)
);
const currentShowId$ = this.globalSettingsService.get$.pipe(
filter((settings): settings is NonNullable<typeof settings> => !!settings),
map(settings => settings.currentShow),
filter((showId): showId is string => !!showId),
distinctUntilChanged(),
tap(_ => (this.currentShowId = _)),
takeUntil(this.destroy$)
);
const show$ = currentShowId$
.pipe(
switchMap(showId => this.showService.read$(showId)),
filter((show): show is Show => !!show),
shareReplay({
bufferSize: 1,
refCount: true,
}),
takeUntil(this.destroy$)
);
const show$ = currentShowId$.pipe(
switchMap(showId => this.showService.read$(showId)),
filter((show): show is Show => !!show),
shareReplay({
bufferSize: 1,
refCount: true,
}),
takeUntil(this.destroy$)
);
show$
.pipe(

View File

@@ -1,126 +1,128 @@
<div *ngIf="show" @fade>
<app-card [closeIcon]="faIcon" [heading]="show.showType | showType"
[subheading]="show.date.toDate() | date:'dd.MM.yyyy'" closeLink="/presentation/select">
<ng-container *ngIf="!progress">
<div class="song">
<div *ngIf="show" class="song-parts">
<div
(click)="onSectionClick('title', -1, show.id)"
[class.active]="show.presentationSongId === 'title'"
class="song-part"
>
<div class="head">Veranstaltung</div>
</div>
<div
(click)="onSectionClick('empty', -1, show.id)"
[class.active]="show.presentationSongId === 'empty'"
class="song-part"
>
<div class="head">Leer</div>
</div>
@if (show) {
<div @fade>
<app-card [closeIcon]="faIcon" [heading]="show.showType | showType"
[subheading]="show.date.toDate() | date:'dd.MM.yyyy'" closeLink="/presentation/select">
@if (!progress) {
<div class="song">
@if (show) {
<div class="song-parts">
<div
(click)="onSectionClick('title', -1, show.id)"
[class.active]="show.presentationSongId === 'title'"
class="song-part"
>
<div class="head">Veranstaltung</div>
</div>
<div
(click)="onSectionClick('empty', -1, show.id)"
[class.active]="show.presentationSongId === 'empty'"
class="song-part"
>
<div class="head">Leer</div>
</div>
</div>
}
</div>
</div>
<div *ngFor="let song of presentationSongs; trackBy: trackBy" class="song">
<div *ngIf="show"
[class.active]="show.presentationSongId === song.id"
class="title song-part"
>
<div (click)="onSectionClick(song.id, -1, show.id)" class="head">
{{ song.title }}
</div>
</div>
<div *ngIf="show" class="song-parts">
<div
(click)="onSectionClick(song.id, i, show.id)"
*ngFor="let section of song.sections; index as i"
@for (song of presentationSongs; track trackBy($index, song)) {
<div class="song">
@if (show) {
<div
[class.active]="show.presentationSongId === song.id"
class="title song-part"
>
<div (click)="onSectionClick(song.id, -1, show.id)" class="head">
{{ song.title }}
</div>
</div>
}
@if (show) {
<div class="song-parts">
@for (section of song.sections; track section; let i = $index) {
<div
(click)="onSectionClick(song.id, i, show.id)"
[class.active]="
show.presentationSongId === song.id &&
show.presentationSection === i
"
class="song-part"
>
<div class="head">
{{ section.type | sectionType }} {{ section.number + 1 }}
class="song-part"
>
<div class="head">
{{ section.type | sectionType }} {{ section.number + 1 }}
</div>
<div class="fragment">{{ getFirstLine(section) }}</div>
</div>
}
</div>
}
</div>
}
<div class="song">
@if (show) {
<div
[class.active]="show.presentationSongId === 'dynamicText'"
class="title song-part"
>
<div (click)="onSectionClick('dynamicText', -1, show.id)" class="head">
Freier Text
</div>
</div>
<div class="fragment">{{ getFirstLine(section) }}</div>
</div>
}
<mat-form-field appearance="outline">
<mat-label>Überschrift</mat-label>
<input (ngModelChange)="onDynamicCaption($event, show.id)" [ngModel]="show.presentationDynamicCaption"
autocomplete="off" id="dynamic-caption"
matInput
type="text">
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Text</mat-label>
<textarea (ngModelChange)="onDynamicText($event, show.id)" [ngModel]="show.presentationDynamicText"
autocomplete="off" id="dynamic-text"
matInput></textarea>
</mat-form-field>
</div>
</div>
<div class="song">
<div *ngIf="show"
[class.active]="show.presentationSongId === 'dynamicText'"
class="title song-part"
>
<div (click)="onSectionClick('dynamicText', -1, show.id)" class="head">
Freier Text
@if (show) {
<div class="div-bottom">
<button class="btn-start-presentation" mat-button routerLink="/presentation/monitor">
<fa-icon [icon]="faDesktop"></fa-icon>
Präsentation starten
</button>
<mat-form-field appearance="outline">
<mat-label>Hintergrund</mat-label>
<mat-select
(ngModelChange)="onBackground($event, show.id)"
[ngModel]="show.presentationBackground">
<mat-option value="none">kein Hintergrund</mat-option>
<mat-option value="blue">Sternenhimmel</mat-option>
<mat-option value="green">Blätter</mat-option>
<mat-option value="leder">Leder</mat-option>
<mat-option value="praise">Lobpreis</mat-option>
<mat-option value="bible">Bibel</mat-option>
</mat-select>
</mat-form-field>
<mat-slider
#slider
[max]="100"
[min]="10"
[step]="2"
class="zoom-slider"
color="primary"
ngDefaultControl
><input (ngModelChange)="onZoom($event, show.id)"
[ngModel]="show.presentationZoom"
matSliderThumb>
</mat-slider>
</div>
</div>
<mat-form-field appearance="outline">
<mat-label>Überschrift</mat-label>
<input (ngModelChange)="onDynamicCaption($event, show.id)" [ngModel]="show.presentationDynamicCaption"
autocomplete="off" id="dynamic-caption"
matInput
type="text">
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Text</mat-label>
<textarea (ngModelChange)="onDynamicText($event, show.id)" [ngModel]="show.presentationDynamicText"
autocomplete="off" id="dynamic-text"
matInput></textarea>
</mat-form-field>
</div>
<div *ngIf="show" class="div-bottom">
<button class="btn-start-presentation" mat-button routerLink="/presentation/monitor">
<fa-icon [icon]="faDesktop"></fa-icon>
Präsentation starten
</button>
<mat-form-field appearance="outline">
<mat-label>Hintergrund</mat-label>
<mat-select
(ngModelChange)="onBackground($event, show.id)"
[ngModel]="show.presentationBackground">
<mat-option value="none">kein Hintergrund</mat-option>
<mat-option value="blue">Sternenhimmel</mat-option>
<mat-option value="green">Blätter</mat-option>
<mat-option value="leder">Leder</mat-option>
<mat-option value="praise">Lobpreis</mat-option>
<mat-option value="bible">Bibel</mat-option>
</mat-select>
</mat-form-field>
<mat-slider
#slider
[max]="100"
[min]="10"
[step]="2"
class="zoom-slider"
color="primary"
ngDefaultControl
><input (ngModelChange)="onZoom($event, show.id)"
[ngModel]="show.presentationZoom"
matSliderThumb>
</mat-slider>
</div>
<app-add-song
*ngIf="show"
[addedLive]="true"
[showSongs]="showSongs"
[show]="show"
[songs]="songs$|async"
></app-add-song>
</ng-container>
</app-card>
</div>
}
@if (show) {
<app-add-song
[addedLive]="true"
[showSongs]="showSongs"
[show]="show"
[songs]="songs$|async"
></app-add-song>
}
}
</app-card>
</div>
}

View File

@@ -12,7 +12,7 @@ import {fade} from '../../../animations';
import {TextRenderingService} from '../../songs/services/text-rendering.service';
import {Section} from '../../songs/services/section';
import {LineType} from '../../songs/services/line-type';
import {AsyncPipe, DatePipe, NgFor, NgIf} from '@angular/common';
import {AsyncPipe, DatePipe} from '@angular/common';
import {CardComponent} from '../../../widget-modules/components/card/card.component';
import {MatFormField, MatLabel} from '@angular/material/form-field';
import {MatInput} from '@angular/material/input';
@@ -40,9 +40,7 @@ export interface PresentationSong {
animations: [fade],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
NgIf,
CardComponent,
NgFor,
MatFormField,
MatLabel,
MatInput,

View File

@@ -1,16 +1,25 @@
<div *ngIf="shows$ | async as shows" @fade>
<app-card *ngIf="visible" heading="Bitte eine Veranstaltung auswählen">
<p *ngIf="!shows.length">
Es ist derzeit keine Veranstaltung vorhanden
</p>
<div *ngIf="shows.length>0" class="list">
<button (click)="selectShow(show)" *ngFor="let show of shows" mat-stroked-button>
<app-user-name [userId]="show.owner"></app-user-name>
,
{{ show.showType | showType }},
{{ show.date.toDate() | date: "dd.MM.yyyy" }}
</button>
</div>
</app-card>
</div>
@if (shows$ | async; as shows) {
<div @fade>
@if (visible) {
<app-card heading="Bitte eine Veranstaltung auswählen">
@if (!shows.length) {
<p>
Es ist derzeit keine Veranstaltung vorhanden
</p>
}
@if (shows.length>0) {
<div class="list">
@for (show of shows; track show) {
<button (click)="selectShow(show)" mat-stroked-button>
<app-user-name [userId]="show.owner"></app-user-name>
,
{{ show.showType | showType }},
{{ show.date.toDate() | date: "dd.MM.yyyy" }}
</button>
}
</div>
}
</app-card>
}
</div>
}

View File

@@ -5,7 +5,7 @@ import {Show} from '../../shows/services/show';
import {GlobalSettingsService} from '../../../services/global-settings.service';
import {Router} from '@angular/router';
import {fade} from '../../../animations';
import {AsyncPipe, DatePipe, NgFor, NgIf} from '@angular/common';
import {AsyncPipe, DatePipe} from '@angular/common';
import {CardComponent} from '../../../widget-modules/components/card/card.component';
import {MatButton} from '@angular/material/button';
import {UserNameComponent} from '../../../services/user/user-name/user-name.component';
@@ -16,7 +16,7 @@ import {ShowTypePipe} from '../../../widget-modules/pipes/show-type-translater/s
templateUrl: './select.component.html',
styleUrls: ['./select.component.less'],
animations: [fade],
imports: [NgIf, CardComponent, NgFor, MatButton, UserNameComponent, AsyncPipe, DatePipe, ShowTypePipe],
imports: [CardComponent, MatButton, UserNameComponent, AsyncPipe, DatePipe, ShowTypePipe],
})
export class SelectComponent implements OnInit {
public visible = false;