fix linting

This commit is contained in:
2026-03-15 22:23:58 +01:00
parent 67884e4638
commit 2d4f1ee314
50 changed files with 986 additions and 1430 deletions

View File

@@ -3,4 +3,3 @@
<div [@fader]="o.isActivated ? o.activatedRoute : ''" class="content"> <div [@fader]="o.isActivated ? o.activatedRoute : ''" class="content">
<router-outlet #o="outlet"></router-outlet> <router-outlet #o="outlet"></router-outlet>
</div> </div>

View File

@@ -1,13 +1,10 @@
<div class="frame"> <div class="frame">
<app-brand class="brand"></app-brand> <app-brand class="brand"></app-brand>
@if (user$ | async; as user) { @if (user$ | async; as user) {
<div class="text"> <div class="text">
<div class="welcome">WILLKOMMEN</div> <div class="welcome">WILLKOMMEN</div>
<div class="name">{{ user.name }}</div> <div class="name">{{ user.name }}</div>
<div class="roles"> <div class="roles">Es wurden noch keine Berechtigungen zugeteilt, bitte wende Dich an den Administrator!</div>
Es wurden noch keine Berechtigungen zugeteilt, bitte wende Dich an den </div>
Administrator!
</div>
</div>
} }
</div> </div>

View File

@@ -1,28 +1,23 @@
@if (show$|async; as show) { @if (show$|async; as show) {
<div class="page"> <div class="page">
<div class="title"> <div class="title">
<div class="left">{{ show.showType|showType }}</div> <div class="left">{{ show.showType|showType }}</div>
<div class="right">{{ show.date.toDate() | date: 'dd.MM.yyyy' }}</div> <div class="right">{{ show.date.toDate() | date: 'dd.MM.yyyy' }}</div>
</div>
<div class="view">
<swiper-container scrollbar="true">
@for (song of show.songs; track trackBy(i, song); let i = $index) {
<swiper-slide
class="song-swipe">
<div class="song-title">{{ song.title }}</div>
<div class="legal">
@if (song.artist) {
<p>{{ song.artist }}</p>
}
</div>
<app-song-text
[text]="song.text"
></app-song-text>
</swiper-slide>
}
</swiper-container>
</div>
</div> </div>
<div class="view">
<swiper-container scrollbar="true">
@for (song of show.songs; track trackBy(i, song); let i = $index) {
<swiper-slide class="song-swipe">
<div class="song-title">{{ song.title }}</div>
<div class="legal">
@if (song.artist) {
<p>{{ song.artist }}</p>
}
</div>
<app-song-text [text]="song.text"></app-song-text>
</swiper-slide>
}
</swiper-container>
</div>
</div>
} }

View File

@@ -1,27 +1,17 @@
@if (song) { @if (song) { @if (song.artist) {
@if (song.artist) { <p>{{ song.artist }}</p>
<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>
} }
@if (song.label) { </div>
<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>
}
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,50 +1,43 @@
<div class="fullscreen background"></div> <div class="fullscreen background"></div>
@if (showType) { @if (showType) {
<div [style.font-size.px]="zoom" class="fullscreen background"> <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==='blue'" class="bg-blue fullscreen bg-image"></div>
<div [class.visible]="presentationBackground==='green'" class="bg-green 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==='leder'" class="bg-leder fullscreen bg-image"></div>
<div [class.visible]="presentationBackground==='praise'" class="bg-praise 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 [class.visible]="presentationBackground==='bible'" class="bg-bible fullscreen bg-image"></div>
<div <div
[@songSwitch]="songId" [@songSwitch]="songId"
[class.blur]="songId === 'title' || songId === 'dynamicText'" [class.blur]="songId === 'title' || songId === 'dynamicText'"
[class.hide]="songId !== 'title' && songId !== 'empty' && songId !== 'dynamicText'" [class.hide]="songId !== 'title' && songId !== 'empty' && songId !== 'dynamicText'"
[class.no-logo]="presentationBackground!=='none'" [class.no-logo]="presentationBackground!=='none'"
class="start fullscreen logo" class="start fullscreen logo"
> >
<app-logo></app-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>
@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>
} }

View File

@@ -1,128 +1,83 @@
@if (show) { @if (show) {
<div @fade> <div @fade>
<app-card [closeIcon]="faIcon" [heading]="show.showType | showType" <app-card [closeIcon]="faIcon" [heading]="show.showType | showType" [subheading]="show.date.toDate() | date:'dd.MM.yyyy'" closeLink="/presentation/select">
[subheading]="show.date.toDate() | date:'dd.MM.yyyy'" closeLink="/presentation/select"> @if (!progress) {
@if (!progress) { <div class="song">
<div class="song"> @if (show) {
@if (show) { <div class="song-parts">
<div class="song-parts"> <div (click)="onSectionClick('title', -1, show.id)" [class.active]="show.presentationSongId === 'title'" class="song-part">
<div <div class="head">Veranstaltung</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>
@for (song of presentationSongs; track trackBy($index, song)) { <div (click)="onSectionClick('empty', -1, show.id)" [class.active]="show.presentationSongId === 'empty'" class="song-part">
<div class="song"> <div class="head">Leer</div>
@if (show) { </div>
<div </div>
[class.active]="show.presentationSongId === song.id" }
class="title song-part" </div>
> @for (song of presentationSongs; track trackBy($index, song)) {
<div (click)="onSectionClick(song.id, -1, show.id)" class="head"> <div class="song">
{{ song.title }} @if (show) {
</div> <div [class.active]="show.presentationSongId === song.id" class="title song-part">
</div> <div (click)="onSectionClick(song.id, -1, show.id)" class="head">{{ song.title }}</div>
} </div>
@if (show) { } @if (show) {
<div class="song-parts"> <div class="song-parts">
@for (section of song.sections; track section.type + '-' + section.number + '-' + $index; let i = $index) { @for (section of song.sections; track section.type + '-' + section.number + '-' + $index; let i = $index) {
<div <div
(click)="onSectionClick(song.id, i, show.id)" (click)="onSectionClick(song.id, i, show.id)"
[class.active]=" [class.active]="
show.presentationSongId === song.id && show.presentationSongId === song.id &&
show.presentationSection === i show.presentationSection === i
" "
class="song-part" class="song-part"
> >
<div class="head"> <div class="head">{{ section.type | sectionType }} {{ section.number + 1 }}</div>
{{ section.type | sectionType }} {{ section.number + 1 }} <div class="fragment">{{ getFirstLine(section) }}</div>
</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>
}
<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>
@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>
}
@if (show) {
<app-add-song
[addedLive]="true"
[showSongs]="showSongs"
[show]="show"
[songs]="songs$|async"
></app-add-song>
} }
</div>
} }
</app-card> </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>
}
<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>
@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>
} @if (show) {
<app-add-song [addedLive]="true" [showSongs]="showSongs" [show]="show" [songs]="songs$|async"></app-add-song>
} }
</app-card>
</div>
} }

View File

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

View File

@@ -3,7 +3,5 @@
</div> </div>
<div mat-dialog-actions> <div mat-dialog-actions>
<button [mat-dialog-close]="false" mat-button>Abbrechen</button> <button [mat-dialog-close]="false" mat-button>Abbrechen</button>
<button [mat-dialog-close]="true" cdkFocusInitial mat-button> <button [mat-dialog-close]="true" cdkFocusInitial mat-button>Archivieren</button>
Archivieren
</button>
</div> </div>

View File

@@ -1,7 +1,5 @@
<div mat-dialog-content> <div mat-dialog-content>
<p> <p>Bitte melde die in dieser Veranstaltung verwendeten CCLI-Titel. Die Meldung ist Teil der CCLI-Lizenz und sorgt dafür, dass Songwriter und Verlage korrekt vergütet werden.</p>
Bitte melde die in dieser Veranstaltung verwendeten CCLI-Titel. Die Meldung ist Teil der CCLI-Lizenz und sorgt dafür, dass Songwriter und Verlage korrekt vergütet werden.
</p>
<p> <p>
Die Meldung erfolgt über Die Meldung erfolgt über
<a [href]="reportingUrl" rel="noreferrer" target="_blank">{{ reportingUrl }}</a>. <a [href]="reportingUrl" rel="noreferrer" target="_blank">{{ reportingUrl }}</a>.
@@ -14,31 +12,29 @@
</div> </div>
@for (song of data.songs; track song.title + song.ccliNumber) { @for (song of data.songs; track song.title + song.ccliNumber) {
<div class="list-item"> <div class="list-item">
<div>{{ song.title }}</div> <div>{{ song.title }}</div>
<div class="number-cell"> <div class="number-cell">
<span>{{ song.ccliNumber }}</span> <span>{{ song.ccliNumber }}</span>
<a <a
(click)="markOpened(song.ccliNumber)" (click)="markOpened(song.ccliNumber)"
[attr.aria-label]="'CCLI-Titel melden: ' + song.title" [attr.aria-label]="'CCLI-Titel melden: ' + song.title"
[href]="getSongReportingUrl(song.ccliNumber)" [href]="getSongReportingUrl(song.ccliNumber)"
rel="noreferrer" rel="noreferrer"
target="_blank" target="_blank"
class="btn-icon report-link" class="btn-icon report-link"
> >
<fa-icon [icon]="faOpen"></fa-icon> <fa-icon [icon]="faOpen"></fa-icon>
</a> </a>
@if (wasOpened(song.ccliNumber)) { @if (wasOpened(song.ccliNumber)) {
<fa-icon [icon]="faCheck" class="opened-check"></fa-icon> <fa-icon [icon]="faCheck" class="opened-check"></fa-icon>
} }
</div> </div>
</div> </div>
} }
</div> </div>
</div> </div>
<div mat-dialog-actions> <div mat-dialog-actions>
<button [mat-dialog-close]="false" mat-button>Abbrechen</button> <button [mat-dialog-close]="false" mat-button>Abbrechen</button>
<button [mat-dialog-close]="true" cdkFocusInitial mat-button> <button [mat-dialog-close]="true" cdkFocusInitial mat-button>Alle CCLI-Titel wurden gemeldet</button>
Alle CCLI-Titel wurden gemeldet
</button>
</div> </div>

View File

@@ -1,13 +1,8 @@
<div mat-dialog-content> <div mat-dialog-content>
<a [href]="data.url">{{ data.url }}</a> <a [href]="data.url">{{ data.url }}</a>
<div [style.background-image]="'url('+qrCode+')'" alt="qrcode" class="qrcode"> <div [style.background-image]="'url('+qrCode+')'" alt="qrcode" class="qrcode"></div>
</div>
</div> </div>
<div mat-dialog-actions> <div mat-dialog-actions>
<button [mat-dialog-close]="true" cdkFocusInitial mat-button> <button [mat-dialog-close]="true" cdkFocusInitial mat-button>Schließen</button>
Schließen <button (click)="share()" mat-button>Teilen</button>
</button>
<button (click)="share()" mat-button>
Teilen
</button>
</div> </div>

View File

@@ -25,7 +25,7 @@ export class ShareDialogComponent {
const data = this.data; const data = this.data;
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
QRCode.toDataURL(data.url, { void QRCode.toDataURL(data.url, {
type: 'image/jpeg', type: 'image/jpeg',
quality: 0.92, quality: 0.92,
width: 1280, width: 1280,

View File

@@ -6,18 +6,12 @@
<mat-select formControlName="showType"> <mat-select formControlName="showType">
<mat-optgroup label="öffentlich"> <mat-optgroup label="öffentlich">
@for (key of showTypePublic; track key) { @for (key of showTypePublic; track key) {
<mat-option [value]="key">{{ <mat-option [value]="key">{{ key | showType }} </mat-option>
key | showType
}}
</mat-option>
} }
</mat-optgroup> </mat-optgroup>
<mat-optgroup label="privat"> <mat-optgroup label="privat">
@for (key of showTypePrivate; track key) { @for (key of showTypePrivate; track key) {
<mat-option [value]="key">{{ <mat-option [value]="key">{{ key | showType }} </mat-option>
key | showType
}}
</mat-option>
} }
</mat-optgroup> </mat-optgroup>
</mat-select> </mat-select>

View File

@@ -1,14 +1,10 @@
<div [formGroup]="filterFormGroup"> <div [formGroup]="filterFormGroup">
<div class="third"> <div class="third">
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Zeitraum</mat-label> <mat-label>Zeitraum</mat-label>
<mat-select formControlName="time"> <mat-select formControlName="time">
@for (time of times; track time) { @for (time of times; track time) {
<mat-option [value]="time.key">{{ <mat-option [value]="time.key">{{ time.value }} </mat-option>
time.value
}}
</mat-option>
} }
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
@@ -18,10 +14,7 @@
<mat-select formControlName="owner"> <mat-select formControlName="owner">
<mat-option value="">Alle</mat-option> <mat-option value="">Alle</mat-option>
@for (owner of owners; track owner) { @for (owner of owners; track owner) {
<mat-option [value]="owner.key">{{ <mat-option [value]="owner.key">{{ owner.value }} </mat-option>
owner.value
}}
</mat-option>
} }
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
@@ -32,23 +25,16 @@
<mat-option value="">Alle</mat-option> <mat-option value="">Alle</mat-option>
<mat-optgroup label="öffentlich"> <mat-optgroup label="öffentlich">
@for (key of showTypePublic; track key) { @for (key of showTypePublic; track key) {
<mat-option [value]="key">{{ <mat-option [value]="key">{{ key | showType }} </mat-option>
key | showType
}}
</mat-option>
} }
</mat-optgroup> </mat-optgroup>
<mat-optgroup label="privat"> <mat-optgroup label="privat">
@for (key of showTypePrivate; track key) { @for (key of showTypePrivate; track key) {
<mat-option [value]="key">{{ <mat-option [value]="key">{{ key | showType }} </mat-option>
key | showType
}}
</mat-option>
} }
</mat-optgroup> </mat-optgroup>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
<i>Anzahl der Suchergebnisse: {{ shows?.length ?? 0 }}</i> <i>Anzahl der Suchergebnisse: {{ shows?.length ?? 0 }}</i>

View File

@@ -1,14 +1,14 @@
@if (show) { @if (show) {
<div class="list-item"> <div class="list-item">
<div>{{ show.date.toDate() | date: "dd.MM.yyyy" }}</div> <div>{{ show.date.toDate() | date: "dd.MM.yyyy" }}</div>
<div> <div>
<app-user-name [userId]="show.owner"></app-user-name> <app-user-name [userId]="show.owner"></app-user-name>
</div>
<div>{{ show.showType | showType }}</div>
<div>
@if (showStatusBadge) {
<app-badge [type]="showStatusBadgeType">{{ showStatusBadge }}</app-badge>
}
</div>
</div> </div>
<div>{{ show.showType | showType }}</div>
<div>
@if (showStatusBadge) {
<app-badge [type]="showStatusBadgeType">{{ showStatusBadge }}</app-badge>
}
</div>
</div>
} }

View File

@@ -2,43 +2,30 @@
<!-- <app-list-header *appRole="['leader']"></app-list-header>--> <!-- <app-list-header *appRole="['leader']"></app-list-header>-->
<app-list-header *appRole="['leader']"> <app-list-header *appRole="['leader']">
@if (shows$ | async; as shows) { @if (shows$ | async; as shows) {
<app-filter [shows]="publicShows$ | async"></app-filter> <app-filter [shows]="publicShows$ | async"></app-filter>
} }
</app-list-header> </app-list-header>
<ng-container *appRole="['leader']"> <ng-container *appRole="['leader']">
@if (privateShows$ | async; as shows) { @if (privateShows$ | async; as shows) { @if (shows.length > 0) {
@if (shows.length > 0) { <app-card [padding]="false" heading="Meine Veranstaltungen">
<app-card @for (show of shows | sortBy: 'desc':'date'; track trackBy($index, show)) {
[padding]="false" <app-list-item
heading="Meine Veranstaltungen" [routerLink]="show.id"
> [showStatusBadge]="show.published ? 'nicht gemeldet' : 'unveröffentlicht'"
@for (show of shows | sortBy: 'desc':'date'; track trackBy($index, show)) { [showStatusBadgeType]="show.published ? 'error' : 'none'"
<app-list-item [show]="show"
[routerLink]="show.id" ></app-list-item>
[showStatusBadge]="show.published ? 'nicht gemeldet' : 'unveröffentlicht'"
[showStatusBadgeType]="show.published ? 'error' : 'none'"
[show]="show"
></app-list-item>
}
</app-card>
} }
} </app-card>
} }
</ng-container> </ng-container>
@if (publicShows$ | async; as shows) { @if (publicShows$ | async; as shows) { @if (shows.length > 0) {
@if (shows.length > 0) { <app-card [padding]="false" heading="Veröffentlichte Veranstaltungen">
<app-card @for (show of shows | sortBy: 'desc':'date'; track trackBy($index, show)) {
[padding]="false" <app-list-item [routerLink]="show.id" [show]="show"></app-list-item>
heading="Veröffentlichte Veranstaltungen"
>
@for (show of shows | sortBy: 'desc':'date'; track trackBy($index, show)) {
<app-list-item
[routerLink]="show.id"
[show]="show"
></app-list-item>
}
</app-card>
} }
} </app-card>
} }
</div> </div>

View File

@@ -34,11 +34,7 @@ export class ListComponent {
public showType$ = this.filter$.pipe(map((filterValues: FilterValues) => filterValues.showType)); public showType$ = this.filter$.pipe(map((filterValues: FilterValues) => filterValues.showType));
public shows$ = this.showService.list$(); public shows$ = this.showService.list$();
public privateShows$ = combineLatest([this.shows$, this.userService.user$]).pipe( public privateShows$ = combineLatest([this.shows$, this.userService.user$]).pipe(
map(([shows, user]) => map(([shows, user]) => shows.filter(show => show.owner === user?.id).filter(show => !show.published || show.reportedType === 'pending'))
shows
.filter(show => show.owner === user?.id)
.filter(show => !show.published || show.reportedType === 'pending')
)
); );
public queriedPublicShows$ = this.lastMonths$.pipe(switchMap(lastMonths => this.showService.listPublicSince$(lastMonths))); public queriedPublicShows$ = this.lastMonths$.pipe(switchMap(lastMonths => this.showService.listPublicSince$(lastMonths)));
public fallbackPublicShows$ = combineLatest([this.shows$, this.lastMonths$]).pipe( public fallbackPublicShows$ = combineLatest([this.shows$, this.lastMonths$]).pipe(

View File

@@ -6,18 +6,12 @@
<mat-select formControlName="showType"> <mat-select formControlName="showType">
<mat-optgroup label="öffentlich"> <mat-optgroup label="öffentlich">
@for (key of showTypePublic; track key) { @for (key of showTypePublic; track key) {
<mat-option [value]="key">{{ <mat-option [value]="key">{{ key | showType }} </mat-option>
key | showType
}}
</mat-option>
} }
</mat-optgroup> </mat-optgroup>
<mat-optgroup label="privat"> <mat-optgroup label="privat">
@for (key of showTypePrivate; track key) { @for (key of showTypePrivate; track key) {
<mat-option [value]="key">{{ <mat-option [value]="key">{{ key | showType }} </mat-option>
key | showType
}}
</mat-option>
} }
</mat-optgroup> </mat-optgroup>
</mat-select> </mat-select>

View File

@@ -16,11 +16,9 @@ describe('ShowService', () => {
beforeEach(async () => { beforeEach(async () => {
user$ = new BehaviorSubject<unknown>({id: 'user-1'}); user$ = new BehaviorSubject<unknown>({id: 'user-1'});
showDataServiceSpy = jasmine.createSpyObj<ShowDataService>( showDataServiceSpy = jasmine.createSpyObj<ShowDataService>('ShowDataService', ['read$', 'listPublicSince$', 'update', 'add'], {
'ShowDataService', list$: of(shows) as unknown as ShowDataService['list$'],
['read$', 'listPublicSince$', 'update', 'add'], });
{list$: of(shows) as unknown as ShowDataService['list$']}
);
showDataServiceSpy.read$.and.returnValue(of(shows[0])); showDataServiceSpy.read$.and.returnValue(of(shows[0]));
showDataServiceSpy.listPublicSince$.and.returnValue(of([shows[1]])); showDataServiceSpy.listPublicSince$.and.returnValue(of([shows[1]]));
showDataServiceSpy.update.and.resolveTo(); showDataServiceSpy.update.and.resolveTo();

View File

@@ -1,148 +1,110 @@
@if (show$ | async; as show) { @if (show$ | async; as show) {
<div> <div>
<app-card <app-card
[fullscreen]="useSwiper" [fullscreen]="useSwiper"
closeLink="../" closeLink="../"
heading="{{ show.showType | showType }}, {{ heading="{{ show.showType | showType }}, {{
show.date.toDate() | date: 'dd.MM.yyyy' show.date.toDate() | date: 'dd.MM.yyyy'
}} - {{ getStatus(show) }}" }} - {{ getStatus(show) }}"
> >
@if (!useSwiper) { @if (!useSwiper) {
<p class="show-meta">{{ show.public ? 'öffentliche' : 'geschlossene' }} Veranstaltung von <p class="show-meta">
<app-user-name [userId]="show.owner"></app-user-name> {{ show.public ? 'öffentliche' : 'geschlossene' }} Veranstaltung von
<ng-container *appOwner="show.owner"> <app-user-name [userId]="show.owner"></app-user-name>
<app-badge [type]="getPublishedBadgeType(show)">{{ show.published | publishedType }}</app-badge> <ng-container *appOwner="show.owner">
@if (show.reportedType) { <app-badge [type]="getPublishedBadgeType(show)">{{ show.published | publishedType }}</app-badge>
<app-badge [type]="getReportedTypeBadgeType(show)">{{ show.reportedType | reportedType }}</app-badge> @if (show.reportedType) {
} <app-badge [type]="getReportedTypeBadgeType(show)">{{ show.reportedType | reportedType }}</app-badge>
</ng-container>
</p>
}
<div class="head">
<div>
@if (!useSwiper) {
<mat-checkbox [(ngModel)]="showText">Text anzeigen</mat-checkbox>
}
</div>
<div [class.floating]="useSwiper">
<app-menu-button (click)="onZoomOut()" @fade [icon]="faZoomOut" class="btn-delete btn-icon"
matTooltip="Verkleinern"></app-menu-button>
<app-menu-button (click)="onZoomIn()" @fade [icon]="faZoomIn" class="btn-delete btn-icon"
matTooltip="Vergrößern"></app-menu-button>
<app-menu-button (click)="useSwiper=!useSwiper;fullscreen(useSwiper)" @fade
[icon]="useSwiper ? faRestore : faMaximize" class="btn-delete btn-icon"
matTooltip="Vollbild"></app-menu-button>
</div>
</div>
@if (showSongs && !useSwiper) {
<div (cdkDropListDropped)="drop($event, show)"
[cdkDropListDisabled]="show.published || showText"
[style.--song-key-column-width]="getSongKeyColumnWidth(show)"
[style.font-size]="textSize + 'em'"
cdkDropList
class="song-list">
@for (song of orderedShowSongs(show); track trackBy(i, song); let i = $index) {
<div cdkDrag class="song-row">
<app-song
[dragHandle]="!(show.published || showText)"
[fullscreen]="useSwiper"
[index]="i"
[showId]="showId"
[showSong]="song"
[showText]="showText"
[show]="show"
></app-song>
</div>
}
</div>
}
@if (useSwiper) {
<swiper-container scrollbar="true">
@for (song of orderedShowSongs(show); track trackBy(i, song); let i = $index) {
<swiper-slide
[style.font-size]="textSize + 'em'"
class="song-swipe">
<app-song
[fullscreen]="true"
[index]="i"
[showId]="showId"
[showSong]="song"
[showText]="true"
[show]="show"
></app-song>
<div class="time">{{ currentTime | date: 'HH:mm' }}</div>
@if (getNextSong(orderedShowSongs(show), i); as next) {
<div class="next-song">{{ next }}
<fa-icon [icon]="faNextSong"></fa-icon>
</div>
}
</swiper-slide>
}
</swiper-container>
}
@if (songs$ | async; as songs) {
@if (songs && !show.published && !useSwiper) {
<app-add-song
[showSongs]="showSongs"
[show]="show"
[songs]="songs"
></app-add-song>
} }
</ng-container>
</p>
}
<div class="head">
<div>
@if (!useSwiper) {
<mat-checkbox [(ngModel)]="showText">Text anzeigen</mat-checkbox>
}
</div>
<div [class.floating]="useSwiper">
<app-menu-button (click)="onZoomOut()" @fade [icon]="faZoomOut" class="btn-delete btn-icon" matTooltip="Verkleinern"></app-menu-button>
<app-menu-button (click)="onZoomIn()" @fade [icon]="faZoomIn" class="btn-delete btn-icon" matTooltip="Vergrößern"></app-menu-button>
<app-menu-button
(click)="useSwiper=!useSwiper;fullscreen(useSwiper)"
@fade
[icon]="useSwiper ? faRestore : faMaximize"
class="btn-delete btn-icon"
matTooltip="Vollbild"
></app-menu-button>
</div>
</div>
@if (showSongs && !useSwiper) {
<div
(cdkDropListDropped)="drop($event, show)"
[cdkDropListDisabled]="show.published || showText"
[style.--song-key-column-width]="getSongKeyColumnWidth(show)"
[style.font-size]="textSize + 'em'"
cdkDropList
class="song-list"
>
@for (song of orderedShowSongs(show); track trackBy(i, song); let i = $index) {
<div cdkDrag class="song-row">
<app-song
[dragHandle]="!(show.published || showText)"
[fullscreen]="useSwiper"
[index]="i"
[showId]="showId"
[showSong]="song"
[showText]="showText"
[show]="show"
></app-song>
</div>
} }
@if (!useSwiper) { </div>
<app-button-row> } @if (useSwiper) {
<ng-container *appRole="['leader']"> <swiper-container scrollbar="true">
<ng-container *appOwner="show.owner"> @for (song of orderedShowSongs(show); track trackBy(i, song); let i = $index) {
@if (!show.archived) { <swiper-slide [style.font-size]="textSize + 'em'" class="song-swipe">
<app-button (click)="onArchive(true)" [icon]="faBox"> <app-song [fullscreen]="true" [index]="i" [showId]="showId" [showSong]="song" [showText]="true" [show]="show"></app-song>
Archivieren <div class="time">{{ currentTime | date: 'HH:mm' }}</div>
</app-button> @if (getNextSong(orderedShowSongs(show), i); as next) {
} <div class="next-song">
@if (show.archived) { {{ next }}
<app-button (click)="onArchive(false)" [icon]="faBoxOpen"> <fa-icon [icon]="faNextSong"></fa-icon>
Wiederherstellen </div>
</app-button> }
} </swiper-slide>
@if (!show.published) {
<app-button (click)="onPublish(show, true)" [icon]="faPublish">
Veröffentlichen
</app-button>
}
@if (show.published) {
<app-button (click)="onPublish(show, false)" [icon]="faUnpublish">
Veröffentlichung zurückziehen
</app-button>
}
@if (show.published) {
<app-button (click)="onShare(show)" [icon]="faShare">
Teilen
</app-button>
}
@if (show.published && show.reportedType === 'pending') {
<app-button (click)="onReport(show)" [icon]="faReport">
Melden
</app-button>
}
@if (!show.published) {
<app-button (click)="onChange(show.id)" [icon]="faSliders">
Ändern
</app-button>
}
</ng-container>
</ng-container>
<app-button [icon]="faDownload" [matMenuTriggerFor]="menu">
Herunterladen
</app-button>
<mat-menu #menu="matMenu">
<app-button (click)="onDownload()" [icon]="faUser">
Ablauf für Lobpreisgruppe
</app-button>
<app-button (click)="onDownloadHandout()" [icon]="faUsers">
Handout mit Copyright Infos
</app-button>
</mat-menu>
</app-button-row>
} }
</app-card> </swiper-container>
</div> } @if (songs$ | async; as songs) { @if (songs && !show.published && !useSwiper) {
<app-add-song [showSongs]="showSongs" [show]="show" [songs]="songs"></app-add-song>
} } @if (!useSwiper) {
<app-button-row>
<ng-container *appRole="['leader']">
<ng-container *appOwner="show.owner">
@if (!show.archived) {
<app-button (click)="onArchive(true)" [icon]="faBox"> Archivieren </app-button>
} @if (show.archived) {
<app-button (click)="onArchive(false)" [icon]="faBoxOpen"> Wiederherstellen </app-button>
} @if (!show.published) {
<app-button (click)="onPublish(show, true)" [icon]="faPublish"> Veröffentlichen </app-button>
} @if (show.published) {
<app-button (click)="onPublish(show, false)" [icon]="faUnpublish"> Veröffentlichung zurückziehen </app-button>
} @if (show.published) {
<app-button (click)="onShare(show)" [icon]="faShare"> Teilen </app-button>
} @if (show.published && show.reportedType === 'pending') {
<app-button (click)="onReport(show)" [icon]="faReport"> Melden </app-button>
} @if (!show.published) {
<app-button (click)="onChange(show.id)" [icon]="faSliders"> Ändern </app-button>
}
</ng-container>
</ng-container>
<app-button [icon]="faDownload" [matMenuTriggerFor]="menu"> Herunterladen </app-button>
<mat-menu #menu="matMenu">
<app-button (click)="onDownload()" [icon]="faUser"> Ablauf für Lobpreisgruppe </app-button>
<app-button (click)="onDownloadHandout()" [icon]="faUsers"> Handout mit Copyright Infos </app-button>
</mat-menu>
</app-button-row>
}
</app-card>
</div>
} }

View File

@@ -188,9 +188,12 @@ export class ShowComponent implements OnInit, OnDestroy {
width: '350px', width: '350px',
}); });
dialogRef.afterClosed().pipe(take(1)).subscribe((archive: boolean) => { dialogRef
if (archive && this.showId != null) void this.setArchiveState(true); .afterClosed()
}); .pipe(take(1))
.subscribe((archive: boolean) => {
if (archive && this.showId != null) void this.setArchiveState(true);
});
} }
} }
@@ -230,11 +233,14 @@ export class ShowComponent implements OnInit, OnDestroy {
data: {songs}, data: {songs},
}); });
dialogRef.afterClosed().pipe(take(1)).subscribe((reported: boolean) => { dialogRef
if (reported) { .afterClosed()
void this.showService.update$(show.id, {reportedType: 'reported'}); .pipe(take(1))
} .subscribe((reported: boolean) => {
}); if (reported) {
void this.showService.update$(show.id, {reportedType: 'reported'});
}
});
} }
public getStatus(show: Show): string { public getStatus(show: Show): string {

View File

@@ -1,113 +1,81 @@
@if (iSong && iSong && show) { @if (iSong && iSong && show) {
<div> <div>
@if (show.published || fullscreen) { @if (show.published || fullscreen) {
<div class="title published"> <div class="title published">
<div class="key">{{ iSong.key }}</div> <div class="key">{{ iSong.key }}</div>
<div>{{ iSong.title }}</div> <div>{{ iSong.title }}</div>
</div>
} @if (!show.published && !fullscreen) {
<div class="song" [class.show-text-layout]="!!showText" [class.compact-layout]="!showText" [class.with-drag]="dragHandle && !edit">
@if (dragHandle && !edit) {
<button aria-label="Lied verschieben" cdkDragHandle class="drag-handle" type="button"></button>
}
<span class="title">{{ iSong.title }}</span>
@if (!edit) {
<div class="keys-container">
<div (click)="openKeySelect()" class="keys">
@if (iSong.keyOriginal !== iSong.key) {
<span>{{ iSong.keyOriginal }}&nbsp;&nbsp;</span>
}
<span>{{ iSong.key }}</span>
</div> </div>
} </div>
@if (!show.published && !fullscreen) { } @if (!edit) {
<div <app-menu-button (click)="onEdit()" [icon]="faEdit" class="btn-edit btn-icon" matTooltip="Lied für diese Veranstaltung bearbeiten"></app-menu-button>
class="song" } @if (!edit) {
[class.show-text-layout]="!!showText" <app-menu-button (click)="onDelete()" [icon]="faDelete" class="btn-delete btn-icon" matTooltip="Lied aus Veranstaltung entfernen"></app-menu-button>
[class.compact-layout]="!showText"
[class.with-drag]="dragHandle && !edit"
>
@if (dragHandle && !edit) {
<button
aria-label="Lied verschieben"
cdkDragHandle
class="drag-handle"
type="button"
></button>
}
<span class="title">{{ iSong.title }}</span>
@if (!edit) {
<div class="keys-container">
<div (click)="openKeySelect()" class="keys">
@if (iSong.keyOriginal !== iSong.key) {
<span>{{ iSong.keyOriginal }}&nbsp;&nbsp;</span>
}
<span>{{ iSong.key }}</span>
</div>
</div>
}
@if (!edit) {
<app-menu-button (click)="onEdit()" [icon]="faEdit" class="btn-edit btn-icon"
matTooltip="Lied für diese Veranstaltung bearbeiten"></app-menu-button>
}
@if (!edit) {
<app-menu-button (click)="onDelete()" [icon]="faDelete" class="btn-delete btn-icon"
matTooltip="Lied aus Veranstaltung entfernen"></app-menu-button>
}
</div>
@if (!edit) {
<div
aria-hidden="true"
class="song select"
[class.show-text-layout]="!!showText"
[class.compact-layout]="!showText"
[class.with-drag]="dragHandle"
>
@if (dragHandle) {
<span class="drag-handle-placeholder"></span>
}
@if (!showText) {
<span class="keys">
<mat-form-field class="keys-select">
<mat-select #option [formControl]="keyFormControl" tabIndex="-1">
@for (key of keys; track key) {
<mat-option [value]="key">{{ key }}</mat-option>
}
</mat-select>
</mat-form-field>
</span>
<span class="title"></span>
} @else {
<span class="title"></span>
<span class="keys">
<mat-form-field class="keys-select">
<mat-select #option [formControl]="keyFormControl" tabIndex="-1">
@for (key of keys; track key) {
<mat-option [value]="key">{{ key }}</mat-option>
}
</mat-select>
</mat-form-field>
</span>
}
<span class="btn-edit"></span>
<span class="btn-delete"></span>
</div>
}
}
@if (edit) {
<mat-form-field appearance="outline">
<mat-label>Songtext</mat-label>
<textarea [cdkTextareaAutosize]="true"
[formControl]="editSongControl"
class="edit"
matInput
matTooltip="Tonart ändern"
></textarea>
</mat-form-field>
}
@if (edit) {
<div>Es wird nur der Liedtext für dieser Veranstaltung geändert.</div>
}
@if (edit) {
<app-button-row>
<app-button (click)="onSave()" [icon]="faSave">Speichern</app-button>
<app-button (click)="onDiscard()" [icon]="faEraser">Verwerfen</app-button>
</app-button-row>
}
@if (!edit && (showText)) {
<app-song-text
(chordModeChanged)="onChordModeChanged($event)"
[chordMode]="iSong.chordMode"
[showSwitch]="!show.published"
[text]="iSong.text"
[transpose]="{ baseKey: iSong.keyOriginal, targetKey: iSong.key }"
></app-song-text>
} }
</div> </div>
@if (!edit) {
<div aria-hidden="true" class="song select" [class.show-text-layout]="!!showText" [class.compact-layout]="!showText" [class.with-drag]="dragHandle">
@if (dragHandle) {
<span class="drag-handle-placeholder"></span>
} @if (!showText) {
<span class="keys">
<mat-form-field class="keys-select">
<mat-select #option [formControl]="keyFormControl" tabIndex="-1">
@for (key of keys; track key) {
<mat-option [value]="key">{{ key }}</mat-option>
}
</mat-select>
</mat-form-field>
</span>
<span class="title"></span>
} @else {
<span class="title"></span>
<span class="keys">
<mat-form-field class="keys-select">
<mat-select #option [formControl]="keyFormControl" tabIndex="-1">
@for (key of keys; track key) {
<mat-option [value]="key">{{ key }}</mat-option>
}
</mat-select>
</mat-form-field>
</span>
}
<span class="btn-edit"></span>
<span class="btn-delete"></span>
</div>
} } @if (edit) {
<mat-form-field appearance="outline">
<mat-label>Songtext</mat-label>
<textarea [cdkTextareaAutosize]="true" [formControl]="editSongControl" class="edit" matInput matTooltip="Tonart ändern"></textarea>
</mat-form-field>
} @if (edit) {
<div>Es wird nur der Liedtext für dieser Veranstaltung geändert.</div>
} @if (edit) {
<app-button-row>
<app-button (click)="onSave()" [icon]="faSave">Speichern</app-button>
<app-button (click)="onDiscard()" [icon]="faEraser">Verwerfen</app-button>
</app-button-row>
} @if (!edit && (showText)) {
<app-song-text
(chordModeChanged)="onChordModeChanged($event)"
[chordMode]="iSong.chordMode"
[showSwitch]="!show.published"
[text]="iSong.text"
[transpose]="{ baseKey: iSong.keyOriginal, targetKey: iSong.key }"
></app-song-text>
}
</div>
} }

View File

@@ -10,10 +10,7 @@
<mat-select formControlName="type"> <mat-select formControlName="type">
<mat-option [value]="null">- kein Filter -</mat-option> <mat-option [value]="null">- kein Filter -</mat-option>
@for (type of types; track type) { @for (type of types; track type) {
<mat-option [value]="type">{{ <mat-option [value]="type">{{ type | songType }} </mat-option>
type | songType
}}
</mat-option>
} }
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
@@ -23,10 +20,7 @@
<mat-select formControlName="key"> <mat-select formControlName="key">
<mat-option [value]="null">- kein Filter -</mat-option> <mat-option [value]="null">- kein Filter -</mat-option>
@for (key of keys; track key) { @for (key of keys; track key) {
<mat-option [value]="key">{{ <mat-option [value]="key">{{ key | key }} </mat-option>
key | key
}}
</mat-option>
} }
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
@@ -36,10 +30,7 @@
<mat-select formControlName="legalType"> <mat-select formControlName="legalType">
<mat-option [value]="null">- kein Filter -</mat-option> <mat-option [value]="null">- kein Filter -</mat-option>
@for (key of legalType; track key) { @for (key of legalType; track key) {
<mat-option [value]="key">{{ <mat-option [value]="key">{{ key | legalType }} </mat-option>
key | legalType
}}
</mat-option>
} }
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
@@ -49,10 +40,7 @@
<mat-select formControlName="flag"> <mat-select formControlName="flag">
<mat-option [value]="null">- kein Filter -</mat-option> <mat-option [value]="null">- kein Filter -</mat-option>
@for (flag of getFlags(); track flag) { @for (flag of getFlags(); track flag) {
<mat-option [value]="flag">{{ <mat-option [value]="flag">{{ flag }} </mat-option>
flag
}}
</mat-option>
} }
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>

View File

@@ -1,45 +1,43 @@
@if (songs$ | async; as songs) { @if (songs$ | async; as songs) {
<div> <div>
<app-list-header [anyFilterActive]="anyFilterActive"> <app-list-header [anyFilterActive]="anyFilterActive">
<app-filter [songs]="songs"></app-filter> <app-filter [songs]="songs"></app-filter>
</app-list-header> </app-list-header>
<app-card [padding]="false"> <app-card [padding]="false">
@for (song of songs; track trackBy($index, song)) { @for (song of songs; track trackBy($index, song)) {
<div [routerLink]="song.id" class="list-item"> <div [routerLink]="song.id" class="list-item">
<div class="number">{{ song.number }}</div> <div class="number">{{ song.number }}</div>
<div class="title"> <div class="title">
<span>{{ song.title }}</span> <span>{{ song.title }}</span>
@if (song.hasChordValidationIssues) { @if (song.hasChordValidationIssues) {
<span class="validation-star" title="Akkord-Validierungsfehler vorhanden">*</span> <span class="validation-star" title="Akkord-Validierungsfehler vorhanden">*</span>
} }
</div>
<div>
<ng-container *appRole="['contributor']">
@if (song.status === 'draft') {
<div class="warning">
<fa-icon [icon]="faDraft"></fa-icon>
</div> </div>
<div> } @if (song.status === 'set') {
<ng-container *appRole="['contributor']"> <div class="neutral">
@if (song.status === 'draft') { <fa-icon [icon]="faDraft"></fa-icon>
<div class="warning">
<fa-icon [icon]="faDraft"></fa-icon>
</div>
}
@if (song.status === 'set') {
<div class="neutral">
<fa-icon [icon]="faDraft"></fa-icon>
</div>
}
@if (song.status === 'final') {
<div class="success">
<fa-icon [icon]="faFinal"></fa-icon>
</div>
}
</ng-container>
@if (song.legalType === 'open') {
<div class="warning">
<fa-icon [icon]="faLegal"></fa-icon>
</div>
}
</div> </div>
<div>{{ song.key }}</div> } @if (song.status === 'final') {
<div class="success">
<fa-icon [icon]="faFinal"></fa-icon>
</div>
}
</ng-container>
@if (song.legalType === 'open') {
<div class="warning">
<fa-icon [icon]="faLegal"></fa-icon>
</div> </div>
} }
</app-card> </div>
</div> <div>{{ song.key }}</div>
</div>
}
</app-card>
</div>
} }

View File

@@ -1,33 +1,25 @@
<app-card heading="Angehängte Dateien"> <app-card heading="Angehängte Dateien">
@if (currentUpload) { @if (currentUpload) {
<div> <div>
<div class="progress"> <div class="progress">
<div <div [ngStyle]="{ width: currentUpload?.progress + '%' }" class="progress-bar progress-bar-animated"></div>
[ngStyle]="{ width: currentUpload?.progress + '%' }"
class="progress-bar progress-bar-animated"
></div>
</div>
Progress: {{ currentUpload?.name }} | {{ currentUpload?.progress }}%
Complete
</div> </div>
Progress: {{ currentUpload?.name }} | {{ currentUpload?.progress }}% Complete
</div>
} }
<div class="upload"> <div class="upload">
<label> <label>
<input (change)="detectFiles($event)" type="file" /> <input (change)="detectFiles($event)" type="file" />
</label> </label>
<button <button (click)="uploadSingle()" [disabled]="!selectedFiles" mat-icon-button>
(click)="uploadSingle()"
[disabled]="!selectedFiles"
mat-icon-button
>
<mat-icon>cloud_upload</mat-icon> <mat-icon>cloud_upload</mat-icon>
</button> </button>
</div> </div>
@for (file of files$ | async; track file.id) { @for (file of files$ | async; track file.id) {
<p> <p>
<app-file [file]="file" [songId]="songId"></app-file> <app-file [file]="file" [songId]="songId"></app-file>
</p> </p>
} }
</app-card> </app-card>

View File

@@ -1,202 +1,173 @@
@if (song) { @if (song) {
<app-card [heading]="song.number + ' bearbeiten'" closeLink="../"> <app-card [heading]="song.number + ' bearbeiten'" closeLink="../">
<form [formGroup]="form" class="form"> <form [formGroup]="form" class="form">
<mat-form-field appearance="outline">
<mat-label>Titel</mat-label>
<input formControlName="title" matInput />
</mat-form-field>
<div class="fourth">
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Titel</mat-label> <mat-label>Typ</mat-label>
<input formControlName="title" matInput /> <mat-select formControlName="type">
</mat-form-field> @for (type of types; track type) {
<div class="fourth"> <mat-option [value]="type">{{ type | songType }} </mat-option>
<mat-form-field appearance="outline">
<mat-label>Typ</mat-label>
<mat-select formControlName="type">
@for (type of types; track type) {
<mat-option [value]="type">{{
type | songType
}}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Tonart</mat-label>
<mat-select formControlName="key">
@for (key of keys; track key) {
<mat-option [value]="key">{{
key | key
}}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline">
<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">
@for (status of status; track status) {
<mat-option [value]="status">{{
status | status
}}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<mat-form-field appearance="outline">
<mat-label>Songtext</mat-label>
<textarea
(focus)="songtextFocus = true"
(focusout)="songtextFocus = false"
[cdkTextareaAutosize]="true"
formControlName="text"
matInput
></textarea>
</mat-form-field>
@if (chordValidationIssues.length > 0) {
<div class="song-text-validation">
<h3>Akkordschreibweise korrigieren</h3>
@for (issue of chordValidationIssues; track issue.lineNumber + '-' + issue.token) {
<div class="issue">
<strong>Zeile {{ issue.lineNumber }}:</strong>
<span>{{ issue.message }}</span>
<code>{{ issue.token }}</code>
@if (issue.suggestion) {
<span>-></span>
<code>{{ issue.suggestion }}</code>
}
</div>
} }
</div> </mat-select>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Tonart</mat-label>
<mat-select formControlName="key">
@for (key of keys; track key) {
<mat-option [value]="key">{{ key | key }} </mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline">
<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">
@for (status of status; track status) {
<mat-option [value]="status">{{ status | status }} </mat-option>
}
</mat-select>
</mat-form-field>
</div>
<mat-form-field appearance="outline">
<mat-label>Songtext</mat-label>
<textarea (focus)="songtextFocus = true" (focusout)="songtextFocus = false" [cdkTextareaAutosize]="true" formControlName="text" matInput></textarea>
</mat-form-field>
@if (chordValidationIssues.length > 0) {
<div class="song-text-validation">
<h3>Akkordschreibweise korrigieren</h3>
@for (issue of chordValidationIssues; track issue.lineNumber + '-' + issue.token) {
<div class="issue">
<strong>Zeile {{ issue.lineNumber }}:</strong>
<span>{{ issue.message }}</span>
<code>{{ issue.token }}</code>
@if (issue.suggestion) {
<span>-></span>
<code>{{ issue.suggestion }}</code>
}
</div>
} }
@if (songtextFocus) { </div>
<div class="song-text-help"> } @if (songtextFocus) {
<h3>Vorschau</h3> <div class="song-text-help">
<app-song-text [text]="form.value.text" [validateChordNotation]="true" chordMode="show"></app-song-text> <h3>Vorschau</h3>
<h3>Hinweise zur Bearbeitung</h3> <app-song-text [text]="form.value.text" [validateChordNotation]="true" chordMode="show"></app-song-text>
<h4>Aufbau</h4> <h3>Hinweise zur Bearbeitung</h3>
Der Liedtext wird hintereinander weg geschrieben. Dabei werden Strophen, <h4>Aufbau</h4>
Refrain und Bridge jeweils durch eine zusätzliche Zeile Markiert. z.B. Der Liedtext wird hintereinander weg geschrieben. Dabei werden Strophen, Refrain und Bridge jeweils durch eine zusätzliche Zeile Markiert. z.B.
<pre> <pre>
Strophe Strophe
Text der ersten Strophe Text der ersten Strophe
Strophe Strophe
Text der zweiten Strophe Text der zweiten Strophe
Refrain Refrain
Und hier der Refrain Und hier der Refrain
</pre> </pre
<h3>Akkorde</h3> >
Die Akktorde werden jeweils in der Zeile über dem jeweiligen Liedtext <h3>Akkorde</h3>
geschrieben. Sie werden jeweils durch Leerzeichen an die entsprechende Die Akktorde werden jeweils in der Zeile über dem jeweiligen Liedtext geschrieben. Sie werden jeweils durch Leerzeichen an die entsprechende Position gebracht. Bitte keine
Position gebracht. Bitte keine Tabulatoren verwenden! Folgende Tabulatoren verwenden! Folgende Schreibweisen sind erlaubt:
Schreibweisen sind erlaubt: <pre>
<pre>
Dur: C D E Dur: C D E
Moll: c d e Moll: c d e
Kreuz/B-Tonarten: C# f# Eb (Hb muss als B angegeben werden) Kreuz/B-Tonarten: C# f# Eb (Hb muss als B angegeben werden)
Basstöne: C/E D/C Basstöne: C/E D/C
Obertöne: c7 E9 f#maj7 Obertöne: c7 E9 f#maj7
</pre> </pre
Beispiel: >
<pre> Beispiel:
<pre>
Strophe Strophe
e C/E H7 a D C/E H7/E e C/E H7 a D C/E H7/E
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
F# B Eb Cmaj7 C9 e F# B Eb Cmaj7 C9 e
sed diam nonumy eirmod tempor invidunt ut labore et dolore sed diam nonumy eirmod tempor invidunt ut labore et dolore
</pre> </pre
</div> >
} </div>
}
<mat-form-field appearance="outline">
<mat-label>Kommentar</mat-label>
<textarea [cdkTextareaAutosize]="true" formControlName="comment" matInput></textarea>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-chip-grid #chipList>
@for (flag of flags; track flag) {
<mat-chip-row (removed)="removeFlag(flag)" [removable]="true">
{{ flag }}&nbsp;
<fa-icon (click)="removeFlag(flag)" [icon]="faRemove"></fa-icon>
</mat-chip-row>
}
<input
(matChipInputTokenEnd)="addFlag($event)"
[matChipInputAddOnBlur]="true"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
placeholder="Attribute"
/>
</mat-chip-grid>
</mat-form-field>
<div class="half">
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Kommentar</mat-label> <mat-label>Rechtlicher Status</mat-label>
<textarea <mat-select formControlName="legalType">
[cdkTextareaAutosize]="true" @for (key of legalType; track key) {
formControlName="comment" <mat-option [value]="key">{{ key | legalType }} </mat-option>
matInput
></textarea>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-chip-grid #chipList>
@for (flag of flags; track flag) {
<mat-chip-row
(removed)="removeFlag(flag)"
[removable]="true"
>
{{ flag }}&nbsp;
<fa-icon (click)="removeFlag(flag)" [icon]="faRemove"></fa-icon>
</mat-chip-row>
} }
<input </mat-select>
(matChipInputTokenEnd)="addFlag($event)"
[matChipInputAddOnBlur]="true"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
placeholder="Attribute"
/>
</mat-chip-grid>
</mat-form-field> </mat-form-field>
<div class="half"> <mat-form-field appearance="outline">
<mat-form-field appearance="outline"> <mat-label>Rechteinhaber</mat-label>
<mat-label>Rechtlicher Status</mat-label> <mat-select formControlName="legalOwner">
<mat-select formControlName="legalType"> @for (key of legalOwner; track key) {
@for (key of legalType; track key) { <mat-option [value]="key">{{ key | legalOwner }} </mat-option>
<mat-option [value]="key">{{ }
key | legalType </mat-select>
}} </mat-form-field>
</mat-option> <mat-form-field appearance="outline">
} <mat-label>Rechteinhaber ID (z.B. CCLI Liednummer)</mat-label>
</mat-select> <input formControlName="legalOwnerId" matInput />
</mat-form-field> @if (form.value.legalOwner === 'CCLI') {
<mat-form-field appearance="outline"> <a
<mat-label>Rechteinhaber</mat-label> class="link-ccli"
<mat-select formControlName="legalOwner"> href="https://songselect.ccli.com/Songs/{{ form.value.legalOwnerId }}"
@for (key of legalOwner; track key) { matSuffix
<mat-option [value]="key">{{
key | legalOwner
}}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Rechteinhaber ID (z.B. CCLI Liednummer)</mat-label>
<input formControlName="legalOwnerId" matInput />
@if (form.value.legalOwner === 'CCLI') {
<a
class="link-ccli"
href="https://songselect.ccli.com/Songs/{{ form.value.legalOwnerId }}"
matSuffix
matTooltip="CCLI Link: https://songselect.ccli.com/Songs/{{ matTooltip="CCLI Link: https://songselect.ccli.com/Songs/{{
form.value.legalOwnerId form.value.legalOwnerId
}}" }}"
matTooltipPosition="before" matTooltipPosition="before"
target="_blank" target="_blank"
> >
<fa-icon [icon]="faLink"></fa-icon> <fa-icon [icon]="faLink"></fa-icon>
</a> </a>
} }
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Künstler</mat-label> <mat-label>Künstler</mat-label>
<input formControlName="artist" matInput /> <input formControlName="artist" matInput />
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Verlag / Copyright</mat-label> <mat-label>Verlag / Copyright</mat-label>
<input formControlName="label" matInput /> <input formControlName="label" matInput />
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Nutzungsbedingungen</mat-label> <mat-label>Nutzungsbedingungen</mat-label>
<input formControlName="termsOfUse" matInput /> <input formControlName="termsOfUse" matInput />
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>abweichende Quelle</mat-label> <mat-label>abweichende Quelle</mat-label>
<input formControlName="origin" matInput /> <input formControlName="origin" matInput />
</mat-form-field> </mat-form-field>
</div> </div>
</form> </form>
<app-button-row> <app-button-row>
<app-button (click)="onSave()" [disabled]="form.invalid" [icon]="faSave">Speichern</app-button> <app-button (click)="onSave()" [disabled]="form.invalid" [icon]="faSave">Speichern</app-button>
</app-button-row> </app-button-row>
</app-card> </app-card>
} }

View File

@@ -4,7 +4,5 @@
</div> </div>
<div mat-dialog-actions> <div mat-dialog-actions>
<button [mat-dialog-close]="false" mat-button>Änderungen verwerfen</button> <button [mat-dialog-close]="false" mat-button>Änderungen verwerfen</button>
<button [mat-dialog-close]="true" cdkFocusInitial mat-button> <button [mat-dialog-close]="true" cdkFocusInitial mat-button>Speichern</button>
Speichern
</button>
</div> </div>

View File

@@ -1,10 +1,10 @@
@if (song && song.edits) { @if (song && song.edits) {
<app-card heading="letzte Änderungen"> <app-card heading="letzte Änderungen">
@for (edit of song.edits; track edit.username + '-' + edit.timestamp.toMillis()) { @for (edit of song.edits; track edit.username + '-' + edit.timestamp.toMillis()) {
<div class="list"> <div class="list">
<div>{{ edit.username }}</div> <div>{{ edit.username }}</div>
<div>{{ edit.timestamp.toDate() | date: "dd.MM.yyyy" }}</div> <div>{{ edit.timestamp.toDate() | date: "dd.MM.yyyy" }}</div>
</div> </div>
} }
</app-card> </app-card>
} }

View File

@@ -1,3 +1 @@
<a [href]="url$ | async" target="_blank"> <a [href]="url$ | async" target="_blank"> {{ name }} </a>
{{ name }}
</a>

View File

@@ -1,110 +1,61 @@
<div class="split"> <div class="split">
@if (song$ | async; as song) { @if (song$ | async; as song) {
<app-card <app-card [heading]="song.number + ' - ' + song.title" closeLink="../">
[heading]="song.number + ' - ' + song.title" <div class="song">
closeLink="../" <div>
> <div *appRole="['leader', 'contributor']" class="detail">
<div class="song"> <div>Typ: {{ song.type | songType }}</div>
<div> <div>Tonart: {{ song.key }}</div>
<div *appRole="['leader', 'contributor']" class="detail"> <div>Tempo: {{ song.tempo }}</div>
<div>Typ: {{ song.type | songType }}</div> <div>Status: {{ (song.status | status) || "entwurf" }}</div>
<div>Tonart: {{ song.key }}</div> @if (song.legalOwner) {
<div>Tempo: {{ song.tempo }}</div> <div>Rechteinhaber: {{ song.legalOwner | legalOwner }}</div>
<div>Status: {{ (song.status | status) || "entwurf" }}</div> } @if (song.legalOwnerId && song.legalOwner === 'CCLI') {
@if (song.legalOwner) { <div>
<div> <a href="https://songselect.ccli.com/Songs/{{ song.legalOwnerId }}" target="_blank"> CCLI Nummer: {{ song.legalOwnerId }} </a>
Rechteinhaber: {{ song.legalOwner | legalOwner }}
</div>
}
@if (song.legalOwnerId && song.legalOwner === 'CCLI') {
<div>
<a
href="https://songselect.ccli.com/Songs/{{ song.legalOwnerId }}"
target="_blank"
>
CCLI Nummer: {{ song.legalOwnerId }}
</a>
</div>
}
@if (song.legalOwnerId && song.legalOwner !== 'CCLI') {
<div>
Rechteinhaber ID: {{ song.legalOwnerId }}
</div>
}
@if (song.artist) {
<div>Künstler: {{ song.artist }}</div>
}
@if (song.label) {
<div>Verlag: {{ song.label }}</div>
}
@if (song.origin) {
<div>Quelle: {{ song.origin }}</div>
}
<div
[matTooltip]="songUsageTooltip$ | async"
matTooltipPosition="above"
>
Wie oft verwendet: {{ songCount$ | async }}
</div>
</div> </div>
</div> } @if (song.legalOwnerId && song.legalOwner !== 'CCLI') {
@if (user$ | async; as user) { <div>Rechteinhaber ID: {{ song.legalOwnerId }}</div>
<app-song-text } @if (song.artist) {
[chordMode]="user.chordMode" <div>Künstler: {{ song.artist }}</div>
[showSwitch]="true" } @if (song.label) {
[text]="song.text" <div>Verlag: {{ song.label }}</div>
[validateChordNotation]="true" } @if (song.origin) {
></app-song-text> <div>Quelle: {{ song.origin }}</div>
}
<mat-chip-listbox
*appRole="['leader', 'contributor']"
aria-label="Attribute"
>
@for (flag of getFlags(song.flags); track flag) {
<mat-chip-option>{{
flag
}}
</mat-chip-option>
} }
</mat-chip-listbox> <div [matTooltip]="songUsageTooltip$ | async" matTooltipPosition="above">Wie oft verwendet: {{ songCount$ | async }}</div>
<div *appRole="['leader', 'contributor']" class="text">
{{ song.comment }}
</div> </div>
</div> </div>
<app-button-row> @if (user$ | async; as user) {
<app-button <app-song-text [chordMode]="user.chordMode" [showSwitch]="true" [text]="song.text" [validateChordNotation]="true"></app-song-text>
(click)="onDelete(song.id)" }
*appRole="['admin']" <mat-chip-listbox *appRole="['leader', 'contributor']" aria-label="Attribute">
[icon]="faDelete" @for (flag of getFlags(song.flags); track flag) {
>Löschen <mat-chip-option>{{ flag }} </mat-chip-option>
</app-button>
<app-button *appRole="['contributor']" [icon]="faEdit" routerLink="edit"
>Bearbeiten
</app-button>
<ng-container *appRole="['leader']">
<app-button [icon]="faFileCirclePlus" [matMenuTriggerFor]="menu">
Zu Veranstaltung hinzufügen
</app-button>
<mat-menu #menu="matMenu">
@for (show of privateShows$|async; track show.id) {
<app-button (click)="addSongToShow(show, song)">
{{ show.date.toDate() | date: "dd.MM.yyyy" }} {{ show.showType | showType }}
</app-button>
}
</mat-menu>
</ng-container>
</app-button-row>
</app-card>
}
@if (files$ | async; as files) {
@if (files.length > 0) {
<app-card heading="Anhänge">
@for (file of files$ | async; track file.id) {
<p>
<app-file [file]="file"></app-file>
</p>
} }
</app-card> </mat-chip-listbox>
<div *appRole="['leader', 'contributor']" class="text">{{ song.comment }}</div>
</div>
<app-button-row>
<app-button (click)="onDelete(song.id)" *appRole="['admin']" [icon]="faDelete">Löschen </app-button>
<app-button *appRole="['contributor']" [icon]="faEdit" routerLink="edit">Bearbeiten </app-button>
<ng-container *appRole="['leader']">
<app-button [icon]="faFileCirclePlus" [matMenuTriggerFor]="menu"> Zu Veranstaltung hinzufügen </app-button>
<mat-menu #menu="matMenu">
@for (show of privateShows$|async; track show.id) {
<app-button (click)="addSongToShow(show, song)"> {{ show.date.toDate() | date: "dd.MM.yyyy" }} {{ show.showType | showType }} </app-button>
}
</mat-menu>
</ng-container>
</app-button-row>
</app-card>
} @if (files$ | async; as files) { @if (files.length > 0) {
<app-card heading="Anhänge">
@for (file of files$ | async; track file.id) {
<p>
<app-file [file]="file"></app-file>
</p>
} }
} </app-card>
} }
</div> </div>

View File

@@ -1,36 +1,25 @@
@if (user$ | async; as user) { @if (user$ | async; as user) {
<app-card heading="Hallo {{ user.name }}"> <app-card heading="Hallo {{ user.name }}">
<p> <p>
@if (getUserRoles(user.role).length === 0) { @if (getUserRoles(user.role).length === 0) {
<span class="warn" <span class="warn">Es wurden noch keine Berechtigungen zugeteilt, bitte wende Dich an den Administrator!</span>
>Es wurden noch keine Berechtigungen zugeteilt, bitte wende Dich an den }
Administrator!</span <span>{{ transdormUserRoles(user.role) }}</span>
> </p>
} <mat-form-field appearance="outline">
<span>{{ transdormUserRoles(user.role) }}</span> <mat-label>bevorzugte Anzeige der Akkorde</mat-label>
</p> <mat-select (ngModelChange)="onChordModeChanged(user.id, $event)" [ngModel]="user.chordMode">
<mat-form-field appearance="outline"> <mat-option [value]="null"></mat-option>
<mat-label>bevorzugte Anzeige der Akkorde</mat-label> <mat-option value="hide">nur den Liedtext anzeigen</mat-option>
<mat-select <mat-option value="onlyFirst">in Strophen die Akkorde nur für die erste anzeigen </mat-option>
(ngModelChange)="onChordModeChanged(user.id, $event)" <mat-option value="show">alle anzeigen</mat-option>
[ngModel]="user.chordMode" </mat-select>
> <mat-hint>Das ist nur die Voreinstellung, die Anzeige kann für jedes Lied geändert werden. </mat-hint>
<mat-option [value]="null"></mat-option> </mat-form-field>
<mat-option value="hide">nur den Liedtext anzeigen</mat-option> <app-button-row>
<mat-option value="onlyFirst" <app-button [icon]="faSignOut" routerLink="../logout">Abmelden</app-button>
>in Strophen die Akkorde nur für die erste anzeigen </app-button-row>
</mat-option> </app-card>
<mat-option value="show">alle anzeigen</mat-option> }
</mat-select>
<mat-hint
>Das ist nur die Voreinstellung, die Anzeige kann für jedes Lied geändert
werden.
</mat-hint>
</mat-form-field>
<app-button-row>
<app-button [icon]="faSignOut" routerLink="../logout">Abmelden</app-button>
</app-button-row>
</app-card>
}
<app-users *appRole="['admin']"></app-users> <app-users *appRole="['admin']"></app-users>

View File

@@ -1,37 +1,28 @@
@if (edit) { @if (edit) {
<div class="users"> <div class="users">
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Name</mat-label> <mat-label>Name</mat-label>
<input (change)="onNameChanged(id, $event)" [ngModel]="name" matInput /> <input (change)="onNameChanged(id, $event)" [ngModel]="name" matInput />
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Rolle</mat-label> <mat-label>Rolle</mat-label>
<mat-select <mat-select (ngModelChange)="onRoleChanged(id, $event)" [ngModel]="roles" multiple>
(ngModelChange)="onRoleChanged(id, $event)" @for (role of ROLE_TYPES; track role) {
[ngModel]="roles" <mat-option [value]="role">{{ role | role }} </mat-option>
multiple }
> </mat-select>
@for (role of ROLE_TYPES; track role) { </mat-form-field>
<mat-option [value]="role">{{ <button (click)="edit = false" mat-icon-button>
role | role <fa-icon [icon]="faClose"></fa-icon>
}} </button>
</mat-option> </div>
} } @if (!edit) {
</mat-select> <div (click)="edit = true" class="users list-item">
</mat-form-field> <span>{{ name }}</span>
<button (click)="edit = false" mat-icon-button> <span
<fa-icon [icon]="faClose"></fa-icon> >@for (role of roles; track role) {
</button> <span>{{ role | role }}, </span>
</div> }</span
>
</div>
} }
@if (!edit) {
<div (click)="edit = true" class="users list-item">
<span>{{ name }}</span>
<span
>@for (role of roles; track role) {
<span>{{ role | role }}, </span>
}</span
>
</div>
}

View File

@@ -1,5 +1,5 @@
<app-card heading="registrierte Benutzer"> <app-card heading="registrierte Benutzer">
@for (user of users$ | async | sortBy: 'asc':'name'; track user.id) { @for (user of users$ | async | sortBy: 'asc':'name'; track user.id) {
<app-user [user]="user"></app-user> <app-user [user]="user"></app-user>
} }
</app-card> </app-card>

View File

@@ -9,34 +9,14 @@
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Passwort</mat-label> <mat-label>Passwort</mat-label>
<input <input (keyup.enter)="onLogin()" formControlName="pass" matInput type="password" />
(keyup.enter)="onLogin()"
formControlName="pass"
matInput
type="password"
/>
</mat-form-field> </mat-form-field>
@if (errorMessage) { @if (errorMessage) {
<p class="error">{{ errorMessage | authMessage }}</p> <p class="error">{{ errorMessage | authMessage }}</p>
} }
<button <button (click)="onLogin()" class="btn-login" color="primary" mat-stroked-button>Anmelden</button>
(click)="onLogin()" <button class="btn-password" mat-stroked-button routerLink="/user/password">Passwort zurücksetzen</button>
class="btn-login" <button class="btn-user" mat-stroked-button routerLink="/user/new">neuen Benutzer anlegen</button>
color="primary"
mat-stroked-button
>
Anmelden
</button>
<button
class="btn-password"
mat-stroked-button
routerLink="/user/password"
>
Passwort zurücksetzen
</button>
<button class="btn-user" mat-stroked-button routerLink="/user/new">
neuen Benutzer anlegen
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -12,12 +12,10 @@
<input formControlName="password" matInput type="password" /> <input formControlName="password" matInput type="password" />
</mat-form-field> </mat-form-field>
@if (errorMessage) { @if (errorMessage) {
<p class="error">{{ errorMessage | authMessage }}</p> <p class="error">{{ errorMessage | authMessage }}</p>
} }
<app-button-row> <app-button-row>
<app-button (click)="onCreate()" [icon]="faNewUser" <app-button (click)="onCreate()" [icon]="faNewUser">Benutzer anlegen </app-button>
>Benutzer anlegen
</app-button>
</app-button-row> </app-button-row>
</app-card> </app-card>

View File

@@ -1,4 +1 @@
<app-card> <app-card> Eine E-Mail mit dem neuen Passwort wurde gesendet. Darin ist der link enthalten, über den das neue Passwort eingegeben werden kann. </app-card>
Eine E-Mail mit dem neuen Passwort wurde gesendet. Darin ist der link
enthalten, über den das neue Passwort eingegeben werden kann.
</app-card>

View File

@@ -2,19 +2,13 @@
<div [formGroup]="form" class="form"> <div [formGroup]="form" class="form">
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>E-Mail Addresse</mat-label> <mat-label>E-Mail Addresse</mat-label>
<input <input (keyup.enter)="onResetPassword()" formControlName="user" matInput />
(keyup.enter)="onResetPassword()"
formControlName="user"
matInput
/>
</mat-form-field> </mat-form-field>
<app-button-row> <app-button-row>
<app-button (click)="onResetPassword()" [icon]="faNewPassword" <app-button (click)="onResetPassword()" [icon]="faNewPassword">neues Passwort anfordern </app-button>
>neues Passwort anfordern
</app-button>
@if (errorMessage) { @if (errorMessage) {
<p class="error">{{ errorMessage | authMessage }}</p> <p class="error">{{ errorMessage | authMessage }}</p>
} }
</app-button-row> </app-button-row>
</div> </div>

View File

@@ -36,6 +36,5 @@ export class UserService {
public incSongCount = (songId: string): Promise<void | null> => this.songUsage.incSongCount(songId); public incSongCount = (songId: string): Promise<void | null> => this.songUsage.incSongCount(songId);
public decSongCount = (songId: string): Promise<void | null> => this.songUsage.decSongCount(songId); public decSongCount = (songId: string): Promise<void | null> => this.songUsage.decSongCount(songId);
public rebuildSongUsage = (): Promise<SongUsageMigrationResult> => this.songUsage.rebuildSongUsage(); public rebuildSongUsage = (): Promise<SongUsageMigrationResult> => this.songUsage.rebuildSongUsage();
public rebuildShowSongIds = (onProgress?: (progress: MigrationProgress) => void): Promise<ShowSongIndexMigrationResult> => public rebuildShowSongIds = (onProgress?: (progress: MigrationProgress) => void): Promise<ShowSongIndexMigrationResult> => this.showSongIndex.rebuildShowSongIds(onProgress);
this.showSongIndex.rebuildShowSongIds(onProgress);
} }

View File

@@ -1,15 +1,15 @@
@if (songs) { @if (songs) {
<div class="add-row"> <div class="add-row">
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Lied hinzufügen...</mat-label> <mat-label>Lied hinzufügen...</mat-label>
<mat-select (selectionChange)="onAddSongSelectionChanged($event)"> <mat-select (selectionChange)="onAddSongSelectionChanged($event)">
<mat-option> <mat-option>
<ngx-mat-select-search [formControl]="filteredSongsControl"></ngx-mat-select-search> <ngx-mat-select-search [formControl]="filteredSongsControl"></ngx-mat-select-search>
</mat-option> </mat-option>
@for (song of filteredSongs(); track song.id) { @for (song of filteredSongs(); track song.id) {
<mat-option [value]="song.id">{{ song.title }}</mat-option> <mat-option [value]="song.id">{{ song.title }}</mat-option>
} }
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
} }

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1 @@
<input <input (ngModelChange)="valueChange($event)" [ngModel]="value" placeholder="Suche" />
(ngModelChange)="valueChange($event)"
[ngModel]="value"
placeholder="Suche"
/>

View File

@@ -1,24 +1,9 @@
<nav [class.hidden]="isNavigationHidden(windowScroll$|async)" class="head"> <nav [class.hidden]="isNavigationHidden(windowScroll$|async)" class="head">
<div class="links"> <div class="links">
<app-brand routerLink="/brand"></app-brand> <app-brand routerLink="/brand"></app-brand>
<app-link <app-link *appRole="['user', 'presenter', 'leader']" [icon]="faSongs" link="/songs" text="Lieder"></app-link>
*appRole="['user', 'presenter', 'leader']" <app-link *appRole="['leader', 'member']" [icon]="faShows" link="/shows" text="Veranstaltungen"></app-link>
[icon]="faSongs" <app-link *appRole="['presenter']" [icon]="faPresentation" link="/presentation" text="Präsentation"></app-link>
link="/songs"
text="Lieder"
></app-link>
<app-link
*appRole="['leader', 'member']"
[icon]="faShows"
link="/shows"
text="Veranstaltungen"
></app-link>
<app-link
*appRole="['presenter']"
[icon]="faPresentation"
link="/presentation"
text="Präsentation"
></app-link>
<app-link [icon]="faUser" link="/user" text="Benutzer"></app-link> <app-link [icon]="faUser" link="/user" text="Benutzer"></app-link>
</div> </div>
<div *appRole="['user', 'presenter', 'leader']" class="actions"> <div *appRole="['user', 'presenter', 'leader']" class="actions">

View File

@@ -1,10 +1,8 @@
<button [disabled]="disabled" mat-button> <button [disabled]="disabled" mat-button>
@if (icon) { @if (icon) {
<span <span><fa-icon [icon]="icon"></fa-icon><span class="content">&nbsp;</span></span>
><fa-icon [icon]="icon"></fa-icon><span class="content">&nbsp;</span></span }
> <span class="button-content">
} <ng-content></ng-content>
<span class="button-content"> </span>
<ng-content></ng-content> </button>
</span>
</button>

View File

@@ -1,18 +1,12 @@
<div [class.fullscreen]="fullscreen" [class.padding]="padding" class="card"> <div [class.fullscreen]="fullscreen" [class.padding]="padding" class="card">
@if (closeLink && !fullscreen) { @if (closeLink && !fullscreen) {
<button <button [routerLink]="closeLink" class="btn-close" mat-icon-button>
[routerLink]="closeLink" <fa-icon [icon]="closeIcon"></fa-icon>
class="btn-close" </button>
mat-icon-button } @if (heading && !fullscreen) {
> <div class="heading">{{ heading }}</div>
<fa-icon [icon]="closeIcon"></fa-icon> } @if (subheading && !fullscreen) {
</button> <div class="subheading">{{ subheading }}</div>
}
@if (heading && !fullscreen) {
<div class="heading">{{ heading }}</div>
}
@if (subheading && !fullscreen) {
<div class="subheading">{{ subheading }}</div>
} }
<ng-content></ng-content> <ng-content></ng-content>
</div> </div>

View File

@@ -1,9 +1,5 @@
<div class="header"> <div class="header">
<button <button (click)="onFilterClick()" [class.filter-active]="anyFilterActive" mat-icon-button>
(click)="onFilterClick()"
[class.filter-active]="anyFilterActive"
mat-icon-button
>
<fa-icon [icon]="faFilter"></fa-icon> <fa-icon [icon]="faFilter"></fa-icon>
</button> </button>
<button mat-icon-button routerLink="new"> <button mat-icon-button routerLink="new">
@@ -12,9 +8,9 @@
</div> </div>
@if (filterVisible || anyFilterActive) { @if (filterVisible || anyFilterActive) {
<div @fade> <div @fade>
<app-card> <app-card>
<ng-content></ng-content> <ng-content></ng-content>
</app-card> </app-card>
</div> </div>
} }

View File

@@ -1,9 +1,4 @@
<svg <svg height="1024" viewBox="0 0 270.93 270.93" width="1024" xmlns="http://www.w3.org/2000/svg">
height="1024"
viewBox="0 0 270.93 270.93"
width="1024"
xmlns="http://www.w3.org/2000/svg"
>
<defs></defs> <defs></defs>
<g fill="#fff"> <g fill="#fff">
<path <path
@@ -13,10 +8,7 @@
d="m116.87 10.157-1.8882 4.7801-1.1823 4.7845-4.4402 0.29048-1.7967-4.5884-2.4969-4.4939-3.6268 0.47781-1.2476 4.9864-0.54906 4.8977-4.3633 0.86861-2.3806-4.3153-3.0618-4.1295-3.5339 0.94706-0.58683 5.1069 0.09583 4.9268-4.2121 1.4307-2.9239-3.9681-3.5745-3.6937-3.3799 1.4002 0.08404 5.1388 0.73936 4.8731-3.9914 1.9681-3.4163-3.5528-4.0248-3.1955-3.1693 1.8287 0.75527 5.0851 1.3683 4.7336-3.6995 2.4722-3.8506-3.0749-4.4083-2.6435-2.9035 2.2281 1.4118 4.9413 1.9739 4.5158-3.345 2.934-4.218-2.5462-4.7162-2.0466-2.5869 2.5883 2.0451 4.7147 2.5462 4.2195-2.934 3.345-4.5143-1.9739-4.9428-1.4118-2.2281 2.902 2.6435 4.4083 3.0763 3.8505-2.4721 3.6995-4.7351-1.3668-5.0837-0.75527-1.8301 3.1679 3.1969 4.0262 3.5513 3.4163-1.9681 3.9899-4.8716-0.73787-5.1403-0.08569-1.4002 3.3814 3.6951 3.5745 3.9667 2.9224-1.4307 4.2136-4.9268-0.09583-5.1069 0.58683-0.94699 3.5338 4.1294 3.0619 4.3153 2.3806-0.86853 4.3633-4.8963 0.54756-4.9864 1.2476-0.47788 3.6283 4.4925 2.4968 4.5899 1.7968-0.29196 4.4388-4.783 1.1838-4.7816 1.8882v3.6588l4.7816 1.8883 4.783 1.1823 0.29196 4.4402-4.5899 1.7967-4.4925 2.4954 0.47788 3.6283 4.9864 1.2476 4.8963 0.54905 0.86853 4.3632-4.3153 2.3806-4.1294 3.0618 0.94699 3.5338 5.1069 0.58686 4.9268-0.0958 1.4307 4.2122-3.9667 2.9239-3.6951 3.5745 1.4002 3.3799 5.1403-0.0857 4.8716-0.73787 1.9681 3.9914-3.5513 3.4162-3.1969 4.0249 1.8301 3.1693 5.0837-0.75535 4.7351-1.3682 2.4721 3.6994-3.0763 3.8505-2.6435 4.4083 2.2281 2.9035 4.9428-1.4133 4.5143-1.9739 2.934 3.345-2.5462 4.2195-2.0451 4.7162 2.5869 2.5868 4.7162-2.0451 4.218-2.5463 3.345 2.9326-1.9739 4.5158-1.4118 4.9428 2.9035 2.2281 4.4083-2.645 3.8506-3.0749 3.6995 2.4722-1.3683 4.7336-0.75527 5.0851 3.1693 1.8301 4.0248-3.1969 3.4163-3.5513 3.9914 1.9681-0.73936 4.8717-0.08404 5.1403 3.3799 1.4002 3.5745-3.6952 2.9239-3.9666 4.2121 1.4292-0.09583 4.9283 0.58683 5.1069 3.5339 0.94699 3.0618-4.1309 2.3806-4.3139 4.3633 0.8686 0.54906 4.8963 1.2476 4.9864 3.6268 0.47787 2.4969-4.494 1.7967-4.5884 4.4402 0.29189 1.1823 4.7831 1.8882 4.7816h3.6588l1.8882-4.7816 1.1823-4.7831 4.4402-0.29189 1.7967 4.5884 2.4969 4.494 3.6268-0.47787 1.2491-4.9864 0.54757-4.8963 4.3633-0.8686 2.3806 4.3139 3.0619 4.1309 3.5338-0.94699 0.58682-5.1069-0.0958-4.9283 4.2136-1.4292 2.9224 3.9666 3.5745 3.6952 3.3799-1.4002-0.0843-5.1403-0.73787-4.8717 3.9899-1.9681 3.4163 3.5513 4.0262 3.1969 3.1679-1.8301-0.75528-5.0851-1.3668-4.7336 3.6995-2.4722 3.8505 3.0749 4.4083 2.645 2.902-2.2281-1.4118-4.9428-1.9739-4.5158 3.345-2.9326 4.2194 2.5463 4.7148 2.0451 2.5883-2.5868-2.0465-4.7162-2.5462-4.2195 2.934-3.345 4.5157 1.9739 4.9414 1.4133 2.2281-2.9035-2.6435-4.4083-3.0749-3.8505 2.4707-3.6994 4.7351 1.3682 5.0837 0.75535 1.8301-3.1693-3.1969-4.0249-3.5513-3.4162 1.9681-3.9914 4.8716 0.73787 5.1403 0.0857 1.4002-3.3799-3.6951-3.5745-3.9668-2.9239 1.4307-4.2122 4.9268 0.0958 5.107-0.58686 0.94698-3.5338-4.1294-3.0618-4.3153-2.3806 0.86852-4.3632 4.8978-0.54905 4.9864-1.2476 0.47781-3.6283-4.4939-2.4954-4.5884-1.7967 0.29058-4.4402 4.7845-1.1823 4.7801-1.8883v-3.6588l-4.7801-1.8882-4.7845-1.1838-0.29058-4.4388 4.5884-1.7968 4.4939-2.4968-0.47781-3.6283-4.9864-1.2476-4.8978-0.54757-0.86852-4.3633 4.3153-2.3806 4.1294-3.0619-0.94698-3.5338-5.107-0.58683-4.9268 0.09583-1.4307-4.2136 3.9668-2.9224 3.6951-3.5745-1.4002-3.3814-5.1403 0.08569-4.8716 0.73787-1.9681-3.9899 3.5513-3.4163 3.1969-4.0262-1.8301-3.1679-5.0837 0.75527-4.7351 1.3668-2.4707-3.6995 3.0749-3.8505 2.6435-4.4083-2.2281-2.902-4.9414 1.4118-4.5157 1.9739-2.934-3.345 2.5462-4.2195 2.0465-4.7147-2.5883-2.5883-4.7148 2.0466-4.2194 2.5462-3.345-2.934 1.9739-4.5158 1.4118-4.9413-2.902-2.2281-4.4083 2.6435-3.8505 3.0749-3.6995-2.4722 1.3668-4.7336 0.75528-5.0851-3.1679-1.8287-4.0262 3.1955-3.4163 3.5528-3.9899-1.9681 0.73787-4.8731 0.0843-5.1388-3.3799-1.4002-3.5745 3.6937-2.9224 3.9681-4.2136-1.4307 0.0958-4.9268-0.58682-5.1069-3.5338-0.94706-3.0619 4.1295-2.3806 4.3153-4.3633-0.86861-0.54757-4.8977-1.2491-4.9864-3.6268-0.47781-2.4969 4.4939-1.7967 4.5884-4.4402-0.29048-1.1823-4.7845-1.8882-4.7801zm1.8301 21.667a96.677 96.677 0 0 1 96.677 96.677 96.677 96.677 0 0 1-96.677 96.677 96.677 96.677 0 0 1-96.677-96.677 96.677 96.677 0 0 1 96.677-96.677z" d="m116.87 10.157-1.8882 4.7801-1.1823 4.7845-4.4402 0.29048-1.7967-4.5884-2.4969-4.4939-3.6268 0.47781-1.2476 4.9864-0.54906 4.8977-4.3633 0.86861-2.3806-4.3153-3.0618-4.1295-3.5339 0.94706-0.58683 5.1069 0.09583 4.9268-4.2121 1.4307-2.9239-3.9681-3.5745-3.6937-3.3799 1.4002 0.08404 5.1388 0.73936 4.8731-3.9914 1.9681-3.4163-3.5528-4.0248-3.1955-3.1693 1.8287 0.75527 5.0851 1.3683 4.7336-3.6995 2.4722-3.8506-3.0749-4.4083-2.6435-2.9035 2.2281 1.4118 4.9413 1.9739 4.5158-3.345 2.934-4.218-2.5462-4.7162-2.0466-2.5869 2.5883 2.0451 4.7147 2.5462 4.2195-2.934 3.345-4.5143-1.9739-4.9428-1.4118-2.2281 2.902 2.6435 4.4083 3.0763 3.8505-2.4721 3.6995-4.7351-1.3668-5.0837-0.75527-1.8301 3.1679 3.1969 4.0262 3.5513 3.4163-1.9681 3.9899-4.8716-0.73787-5.1403-0.08569-1.4002 3.3814 3.6951 3.5745 3.9667 2.9224-1.4307 4.2136-4.9268-0.09583-5.1069 0.58683-0.94699 3.5338 4.1294 3.0619 4.3153 2.3806-0.86853 4.3633-4.8963 0.54756-4.9864 1.2476-0.47788 3.6283 4.4925 2.4968 4.5899 1.7968-0.29196 4.4388-4.783 1.1838-4.7816 1.8882v3.6588l4.7816 1.8883 4.783 1.1823 0.29196 4.4402-4.5899 1.7967-4.4925 2.4954 0.47788 3.6283 4.9864 1.2476 4.8963 0.54905 0.86853 4.3632-4.3153 2.3806-4.1294 3.0618 0.94699 3.5338 5.1069 0.58686 4.9268-0.0958 1.4307 4.2122-3.9667 2.9239-3.6951 3.5745 1.4002 3.3799 5.1403-0.0857 4.8716-0.73787 1.9681 3.9914-3.5513 3.4162-3.1969 4.0249 1.8301 3.1693 5.0837-0.75535 4.7351-1.3682 2.4721 3.6994-3.0763 3.8505-2.6435 4.4083 2.2281 2.9035 4.9428-1.4133 4.5143-1.9739 2.934 3.345-2.5462 4.2195-2.0451 4.7162 2.5869 2.5868 4.7162-2.0451 4.218-2.5463 3.345 2.9326-1.9739 4.5158-1.4118 4.9428 2.9035 2.2281 4.4083-2.645 3.8506-3.0749 3.6995 2.4722-1.3683 4.7336-0.75527 5.0851 3.1693 1.8301 4.0248-3.1969 3.4163-3.5513 3.9914 1.9681-0.73936 4.8717-0.08404 5.1403 3.3799 1.4002 3.5745-3.6952 2.9239-3.9666 4.2121 1.4292-0.09583 4.9283 0.58683 5.1069 3.5339 0.94699 3.0618-4.1309 2.3806-4.3139 4.3633 0.8686 0.54906 4.8963 1.2476 4.9864 3.6268 0.47787 2.4969-4.494 1.7967-4.5884 4.4402 0.29189 1.1823 4.7831 1.8882 4.7816h3.6588l1.8882-4.7816 1.1823-4.7831 4.4402-0.29189 1.7967 4.5884 2.4969 4.494 3.6268-0.47787 1.2491-4.9864 0.54757-4.8963 4.3633-0.8686 2.3806 4.3139 3.0619 4.1309 3.5338-0.94699 0.58682-5.1069-0.0958-4.9283 4.2136-1.4292 2.9224 3.9666 3.5745 3.6952 3.3799-1.4002-0.0843-5.1403-0.73787-4.8717 3.9899-1.9681 3.4163 3.5513 4.0262 3.1969 3.1679-1.8301-0.75528-5.0851-1.3668-4.7336 3.6995-2.4722 3.8505 3.0749 4.4083 2.645 2.902-2.2281-1.4118-4.9428-1.9739-4.5158 3.345-2.9326 4.2194 2.5463 4.7148 2.0451 2.5883-2.5868-2.0465-4.7162-2.5462-4.2195 2.934-3.345 4.5157 1.9739 4.9414 1.4133 2.2281-2.9035-2.6435-4.4083-3.0749-3.8505 2.4707-3.6994 4.7351 1.3682 5.0837 0.75535 1.8301-3.1693-3.1969-4.0249-3.5513-3.4162 1.9681-3.9914 4.8716 0.73787 5.1403 0.0857 1.4002-3.3799-3.6951-3.5745-3.9668-2.9239 1.4307-4.2122 4.9268 0.0958 5.107-0.58686 0.94698-3.5338-4.1294-3.0618-4.3153-2.3806 0.86852-4.3632 4.8978-0.54905 4.9864-1.2476 0.47781-3.6283-4.4939-2.4954-4.5884-1.7967 0.29058-4.4402 4.7845-1.1823 4.7801-1.8883v-3.6588l-4.7801-1.8882-4.7845-1.1838-0.29058-4.4388 4.5884-1.7968 4.4939-2.4968-0.47781-3.6283-4.9864-1.2476-4.8978-0.54757-0.86852-4.3633 4.3153-2.3806 4.1294-3.0619-0.94698-3.5338-5.107-0.58683-4.9268 0.09583-1.4307-4.2136 3.9668-2.9224 3.6951-3.5745-1.4002-3.3814-5.1403 0.08569-4.8716 0.73787-1.9681-3.9899 3.5513-3.4163 3.1969-4.0262-1.8301-3.1679-5.0837 0.75527-4.7351 1.3668-2.4707-3.6995 3.0749-3.8505 2.6435-4.4083-2.2281-2.902-4.9414 1.4118-4.5157 1.9739-2.934-3.345 2.5462-4.2195 2.0465-4.7147-2.5883-2.5883-4.7148 2.0466-4.2194 2.5462-3.345-2.934 1.9739-4.5158 1.4118-4.9413-2.902-2.2281-4.4083 2.6435-3.8505 3.0749-3.6995-2.4722 1.3668-4.7336 0.75528-5.0851-3.1679-1.8287-4.0262 3.1955-3.4163 3.5528-3.9899-1.9681 0.73787-4.8731 0.0843-5.1388-3.3799-1.4002-3.5745 3.6937-2.9224 3.9681-4.2136-1.4307 0.0958-4.9268-0.58682-5.1069-3.5338-0.94706-3.0619 4.1295-2.3806 4.3153-4.3633-0.86861-0.54757-4.8977-1.2491-4.9864-3.6268-0.47781-2.4969 4.4939-1.7967 4.5884-4.4402-0.29048-1.1823-4.7845-1.8882-4.7801zm1.8301 21.667a96.677 96.677 0 0 1 96.677 96.677 96.677 96.677 0 0 1-96.677 96.677 96.677 96.677 0 0 1-96.677-96.677 96.677 96.677 0 0 1 96.677-96.677z"
id="gear-lg" id="gear-lg"
/> />
<g <g aria-label="WORSHIP GENERATOR" transform="matrix(.92405 0 0 1.164 -6.4481 5.9862)">
aria-label="WORSHIP GENERATOR"
transform="matrix(.92405 0 0 1.164 -6.4481 5.9862)"
>
<path <path
d="m26.155 213.81q-1.5697 4.1147-2.9258 8.0808-1.3468 3.9568-1.4118 4.1333h-1.2911q-0.04644-0.17647-1.0031-2.9537-0.9474-2.7865-2.5078-6.9291h-0.01858q-1.3654 3.4274-2.5171 6.5575-1.1425 3.1209-1.2168 3.3252h-1.3189q-0.07431-0.26936-1.1982-3.641-1.1146-3.3809-2.9815-8.4523l1.5233-0.59445q0.037153 0.13004 1.0867 3.2695 1.0496 3.1394 2.4057 7.0498h0.01858q1.4304-3.6038 2.4893-6.604 1.0682-3.0001 1.1146-3.1394h1.4397q0.03715 0.15791 1.1239 3.3995 1.096 3.2416 2.2199 6.3439h0.01858q1.4861-4.059 2.5078-7.1148 1.0217-3.0558 1.0589-3.2045z" d="m26.155 213.81q-1.5697 4.1147-2.9258 8.0808-1.3468 3.9568-1.4118 4.1333h-1.2911q-0.04644-0.17647-1.0031-2.9537-0.9474-2.7865-2.5078-6.9291h-0.01858q-1.3654 3.4274-2.5171 6.5575-1.1425 3.1209-1.2168 3.3252h-1.3189q-0.07431-0.26936-1.1982-3.641-1.1146-3.3809-2.9815-8.4523l1.5233-0.59445q0.037153 0.13004 1.0867 3.2695 1.0496 3.1394 2.4057 7.0498h0.01858q1.4304-3.6038 2.4893-6.604 1.0682-3.0001 1.1146-3.1394h1.4397q0.03715 0.15791 1.1239 3.3995 1.096 3.2416 2.2199 6.3439h0.01858q1.4861-4.059 2.5078-7.1148 1.0217-3.0558 1.0589-3.2045z"
/> />

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -1,89 +1,49 @@
@if (sections && !fullscreen) { @if (sections && !fullscreen) {
<div <div (click)="onClick()" [class.chords]="iChordMode !== 'hide'" class="song-text">
(click)="onClick()" @if (showSwitch) {
[class.chords]="iChordMode !== 'hide'" <button (click)="onChordClick()" class="menu" mat-icon-button>
class="song-text" <fa-icon [icon]="faLines"></fa-icon>
> </button>
@if (showSwitch) { }
<button <div [class.offset]="fullscreen" [style.top.px]="offset + 50">
(click)="onChordClick()" @for (section of sections; track section.type + '-' + section.number + '-' + $index; let i = $index) {
class="menu" <div #section [class.chorus]="section.type === 1" class="section">
mat-icon-button @for (line of getLines(section); track (line.lineNumber ?? $index) + '-' + line.type + '-' + $index) {
> <div [class.chord]="line.type === 0" [class.comment]="isComment(line.text)" [class.disabled]="checkDisabled(i)" class="line">
<fa-icon [icon]="faLines"></fa-icon> @for (segment of getDisplaySegments(line); track $index) {
</button> <span [class.invalid-chord-token]="segment.invalid" [class.invalid-tab-token]="segment.isTab">{{ segment.text }}</span>
} }
<div [class.offset]="fullscreen" [style.top.px]="offset + 50"> </div>
@for (section of sections; track section.type + '-' + section.number + '-' + $index; let i = $index) {
<div
#section
[class.chorus]="section.type === 1"
class="section"
>
@for (line of getLines(section); track (line.lineNumber ?? $index) + '-' + line.type + '-' + $index) {
<div
[class.chord]="line.type === 0"
[class.comment]="isComment(line.text)"
[class.disabled]="checkDisabled(i)"
class="line"
>
@for (segment of getDisplaySegments(line); track $index) {
<span [class.invalid-chord-token]="segment.invalid" [class.invalid-tab-token]="segment.isTab">{{ segment.text }}</span>
}
</div>
}
</div>
}
@if (sections.length===0) {
<div class="error">
Es wurden keine Liedabschnitte gefunden! Bitte mindestens einen Abschnitt festlegen!
</div>
} }
</div> </div>
<ng-content></ng-content> } @if (sections.length===0) {
</div> <div class="error">Es wurden keine Liedabschnitte gefunden! Bitte mindestens einen Abschnitt festlegen!</div>
}
@if (sections && fullscreen) {
<div
(click)="onClick()"
[@songSwitch]="sections"
[class.chords]="iChordMode !== 'hide'"
class="song-text"
>
@if (showSwitch) {
<button
(click)="onChordClick()"
class="menu"
mat-icon-button
>
<fa-icon [icon]="faLines"></fa-icon>
</button>
} }
<div [class.offset]="fullscreen" [style.top.px]="offset + 50"> </div>
@if (header) { <ng-content></ng-content>
<h1>{{ header }}</h1> </div>
} } @if (sections && fullscreen) {
@for (section of sections; track section.type + '-' + section.number + '-' + $index; let i = $index) { <div (click)="onClick()" [@songSwitch]="sections" [class.chords]="iChordMode !== 'hide'" class="song-text">
<div @if (showSwitch) {
#section <button (click)="onChordClick()" class="menu" mat-icon-button>
[class.chorus]="section.type === 1" <fa-icon [icon]="faLines"></fa-icon>
class="section" </button>
> }
@for (line of getLines(section); track (line.lineNumber ?? $index) + '-' + line.type + '-' + $index) { <div [class.offset]="fullscreen" [style.top.px]="offset + 50">
<div @if (header) {
[class.chord]="line.type === 0" <h1>{{ header }}</h1>
[class.disabled]="checkDisabled(i)" } @for (section of sections; track section.type + '-' + section.number + '-' + $index; let i = $index) {
class="line" <div #section [class.chorus]="section.type === 1" class="section">
> @for (line of getLines(section); track (line.lineNumber ?? $index) + '-' + line.type + '-' + $index) {
@for (segment of getDisplaySegments(line, true); track $index) { <div [class.chord]="line.type === 0" [class.disabled]="checkDisabled(i)" class="line">
<span [class.invalid-chord-token]="segment.invalid" [class.invalid-tab-token]="segment.isTab">{{ segment.text }}</span> @for (segment of getDisplaySegments(line, true); track $index) {
} <span [class.invalid-chord-token]="segment.invalid" [class.invalid-tab-token]="segment.isTab">{{ segment.text }}</span>
</div> }
} </div>
</div>
} }
</div> </div>
<ng-content></ng-content> }
</div> </div>
<ng-content></ng-content>
</div>
} }

File diff suppressed because one or more lines are too long

View File

@@ -53,13 +53,10 @@ bootstrapApplication(AppComponent, {
rebuildShowSongIds: async () => { rebuildShowSongIds: async () => {
console.info('[wgeneratorAdmin] rebuildShowSongIds started'); console.info('[wgeneratorAdmin] rebuildShowSongIds started');
const result = await userService.rebuildShowSongIds(progress => { const result = await userService.rebuildShowSongIds(progress => {
console.info( console.info(`[wgeneratorAdmin] rebuildShowSongIds ${progress.processed}/${progress.total} shows processed`, {
`[wgeneratorAdmin] rebuildShowSongIds ${progress.processed}/${progress.total} shows processed`, showId: progress.showId,
{ showSongsProcessed: progress.showSongsProcessed,
showId: progress.showId, });
showSongsProcessed: progress.showSongsProcessed,
}
);
}); });
console.info('[wgeneratorAdmin] rebuildShowSongIds finished', result); console.info('[wgeneratorAdmin] rebuildShowSongIds finished', result);
return result; return result;

View File

@@ -78,7 +78,7 @@ const defaultTestingProviders: TestingProviderList = [
}, },
]; ];
const originalConfigureTestingModule = TestBed.configureTestingModule.bind(TestBed) as typeof TestBed.configureTestingModule; const originalConfigureTestingModule = TestBed.configureTestingModule.bind(TestBed);
const configureTestingModule: typeof TestBed.configureTestingModule = moduleDef => { const configureTestingModule: typeof TestBed.configureTestingModule = moduleDef => {
const extraProviders: TestingProviderList = moduleDef?.providers ?? []; const extraProviders: TestingProviderList = moduleDef?.providers ?? [];
const mergedModuleDef: TestingModuleDefinition = moduleDef ? {...moduleDef} : {}; const mergedModuleDef: TestingModuleDefinition = moduleDef ? {...moduleDef} : {};