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

@@ -52,9 +52,7 @@ describe('TransposeService', () => {
const line: Line = {
type: LineType.chord,
text: '',
chords: [
{chord: 'Q', add: 'sus4', slashChord: null, position: 0, length: 1},
],
chords: [{chord: 'Q', add: 'sus4', slashChord: null, position: 0, length: 1}],
};
const rendered = service.renderChords(line);
@@ -66,9 +64,7 @@ describe('TransposeService', () => {
const line: Line = {
type: LineType.chord,
text: '',
chords: [
{chord: 'C', add: null, slashChord: 'Q', position: 0, length: 1},
],
chords: [{chord: 'C', add: null, slashChord: 'Q', position: 0, length: 1}],
};
const rendered = service.renderChords(line);
@@ -80,9 +76,7 @@ describe('TransposeService', () => {
const line: Line = {
type: LineType.chord,
text: '',
chords: [
{chord: 'C', add: null, slashChord: null, position: 120, length: 1},
],
chords: [{chord: 'C', add: null, slashChord: null, position: 120, length: 1}],
};
const rendered = service.renderChords(line);

View File

@@ -111,7 +111,7 @@ export class TransposeService {
private transposeChord(chord: Chord, map: TransposeMap): Chord {
const translatedChord = map[chord.chord] ?? 'X';
const translatedSlashChord = chord.slashChord ? map[chord.slashChord] ?? 'X' : null;
const translatedSlashChord = chord.slashChord ? (map[chord.slashChord] ?? 'X') : null;
return {
...chord,
@@ -147,7 +147,7 @@ export class TransposeService {
private renderChord(chord: Chord): string {
const renderedChord = scaleMapping[chord.chord] ?? 'X';
const renderedSlashChord = chord.slashChord ? scaleMapping[chord.slashChord] ?? 'X' : '';
const renderedSlashChord = chord.slashChord ? (scaleMapping[chord.slashChord] ?? 'X') : '';
return renderedChord + (chord.add ?? '') + (renderedSlashChord ? '/' + renderedSlashChord : '');
}

View File

@@ -9,10 +9,12 @@
<mat-label>Typ</mat-label>
<mat-select formControlName="type">
<mat-option [value]="null">- kein Filter -</mat-option>
<mat-option *ngFor="let type of types" [value]="type">{{
@for (type of types; track type) {
<mat-option [value]="type">{{
type | songType
}}
</mat-option>
}}
</mat-option>
}
</mat-select>
</mat-form-field>
@@ -20,10 +22,12 @@
<mat-label>Tonart</mat-label>
<mat-select formControlName="key">
<mat-option [value]="null">- kein Filter -</mat-option>
<mat-option *ngFor="let key of keys" [value]="key">{{
@for (key of keys; track key) {
<mat-option [value]="key">{{
key | key
}}
</mat-option>
}}
</mat-option>
}
</mat-select>
</mat-form-field>
@@ -31,10 +35,12 @@
<mat-label>Rechtlicher Status</mat-label>
<mat-select formControlName="legalType">
<mat-option [value]="null">- kein Filter -</mat-option>
<mat-option *ngFor="let key of legalType" [value]="key">{{
@for (key of legalType; track key) {
<mat-option [value]="key">{{
key | legalType
}}
</mat-option>
}}
</mat-option>
}
</mat-select>
</mat-form-field>
@@ -42,10 +48,12 @@
<mat-label>Attribute</mat-label>
<mat-select formControlName="flag">
<mat-option [value]="null">- kein Filter -</mat-option>
<mat-option *ngFor="let flag of getFlags()" [value]="flag">{{
@for (flag of getFlags(); track flag) {
<mat-option [value]="flag">{{
flag
}}
</mat-option>
}}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>

View File

@@ -9,7 +9,7 @@ import {MatFormField, MatLabel} from '@angular/material/form-field';
import {MatInput} from '@angular/material/input';
import {MatSelect} from '@angular/material/select';
import {MatOption} from '@angular/material/core';
import {NgFor} from '@angular/common';
import {LegalTypePipe} from '../../../../widget-modules/pipes/legal-type-translator/legal-type.pipe';
import {KeyPipe} from '../../../../widget-modules/pipes/key-translator/key.pipe';
import {SongTypePipe} from '../../../../widget-modules/pipes/song-type-translater/song-type.pipe';
@@ -18,7 +18,7 @@ import {SongTypePipe} from '../../../../widget-modules/pipes/song-type-translate
selector: 'app-filter',
templateUrl: './filter.component.html',
styleUrls: ['./filter.component.less'],
imports: [ReactiveFormsModule, MatFormField, MatLabel, MatInput, MatSelect, MatOption, NgFor, LegalTypePipe, KeyPipe, SongTypePipe],
imports: [ReactiveFormsModule, MatFormField, MatLabel, MatInput, MatSelect, MatOption, LegalTypePipe, KeyPipe, SongTypePipe],
})
export class FilterComponent {
public filterFormGroup: UntypedFormGroup;

View File

@@ -1,30 +1,40 @@
<div *ngIf="songs$ | async as songs">
<app-list-header [anyFilterActive]="anyFilterActive">
<app-filter [songs]="songs" route="songs"></app-filter>
</app-list-header>
<app-card [padding]="false">
<div *ngFor="let song of songs; trackBy: trackBy" [routerLink]="song.id" class="list-item">
<div class="number">{{ song.number }}</div>
<div>{{ song.title }}</div>
<div>
<ng-container *appRole="['contributor']">
<div *ngIf="song.status === 'draft'" class="warning">
<fa-icon [icon]="faDraft"></fa-icon>
@if (songs$ | async; as songs) {
<div>
<app-list-header [anyFilterActive]="anyFilterActive">
<app-filter [songs]="songs" route="songs"></app-filter>
</app-list-header>
<app-card [padding]="false">
@for (song of songs; track trackBy($index, song)) {
<div [routerLink]="song.id" class="list-item">
<div class="number">{{ song.number }}</div>
<div>{{ song.title }}</div>
<div>
<ng-container *appRole="['contributor']">
@if (song.status === 'draft') {
<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 *ngIf="song.status === 'set'" class="neutral">
<fa-icon [icon]="faDraft"></fa-icon>
</div>
<div *ngIf="song.status === 'final'" class="success">
<fa-icon [icon]="faFinal"></fa-icon>
</div>
</ng-container>
<div *ngIf="song.legalType === 'open'" class="warning">
<fa-icon [icon]="faLegal"></fa-icon>
<div>{{ song.key }}</div>
</div>
</div>
<div>{{ song.key }}</div>
</div>
</app-card>
</div>
}
</app-card>
</div>
}

View File

@@ -9,7 +9,7 @@ import {filterSong} from '../../../services/filter.helper';
import {FilterValues} from './filter/filter-values';
import {ScrollService} from '../../../services/scroll.service';
import {faBalanceScaleRight, faCheck, faPencilRuler} from '@fortawesome/free-solid-svg-icons';
import {AsyncPipe, NgFor, NgIf} from '@angular/common';
import {AsyncPipe} from '@angular/common';
import {ListHeaderComponent} from '../../../widget-modules/components/list-header/list-header.component';
import {FilterComponent} from './filter/filter.component';
import {CardComponent} from '../../../widget-modules/components/card/card.component';
@@ -22,7 +22,7 @@ import {FaIconComponent} from '@fortawesome/angular-fontawesome';
styleUrls: ['./song-list.component.less'],
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [fade],
imports: [NgIf, ListHeaderComponent, FilterComponent, CardComponent, NgFor, RouterLink, RoleDirective, FaIconComponent, AsyncPipe],
imports: [ListHeaderComponent, FilterComponent, CardComponent, RouterLink, RoleDirective, FaIconComponent, AsyncPipe],
})
export class SongListComponent implements OnInit, OnDestroy {
public anyFilterActive = false;

View File

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

View File

@@ -7,7 +7,7 @@ import {FileDataService} from '../../../services/file-data.service';
import {Observable} from 'rxjs';
import {File} from '../../../services/file';
import {CardComponent} from '../../../../../widget-modules/components/card/card.component';
import {AsyncPipe, NgFor, NgIf, NgStyle} from '@angular/common';
import {AsyncPipe, NgStyle} from '@angular/common';
import {MatIconButton} from '@angular/material/button';
import {MatIcon} from '@angular/material/icon';
import {FileComponent} from './file/file.component';
@@ -16,7 +16,7 @@ import {FileComponent} from './file/file.component';
selector: 'app-edit-file',
templateUrl: './edit-file.component.html',
styleUrls: ['./edit-file.component.less'],
imports: [CardComponent, NgIf, NgStyle, MatIconButton, MatIcon, NgFor, FileComponent, AsyncPipe],
imports: [CardComponent, NgStyle, MatIconButton, MatIcon, FileComponent, AsyncPipe],
})
export class EditFileComponent {
public selectedFiles: FileList | null = null;

View File

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

View File

@@ -11,7 +11,7 @@ import {MatChipGrid, MatChipInput, MatChipInputEvent, MatChipRow} from '@angular
import {faExternalLinkAlt, faSave, faTimesCircle} from '@fortawesome/free-solid-svg-icons';
import {MatDialog} from '@angular/material/dialog';
import {SaveDialogComponent} from './save-dialog/save-dialog.component';
import {NgFor, NgIf} from '@angular/common';
import {CardComponent} from '../../../../../widget-modules/components/card/card.component';
import {MatFormField, MatLabel, MatSuffix} from '@angular/material/form-field';
import {MatInput} from '@angular/material/input';
@@ -34,14 +34,12 @@ import {StatusPipe} from '../../../../../widget-modules/pipes/status-translater/
templateUrl: './edit-song.component.html',
styleUrls: ['./edit-song.component.less'],
imports: [
NgIf,
CardComponent,
ReactiveFormsModule,
MatFormField,
MatLabel,
MatInput,
MatSelect,
NgFor,
MatOption,
CdkTextareaAutosize,
SongTextComponent,

View File

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

View File

@@ -3,14 +3,14 @@ import {first, map, switchMap} from 'rxjs/operators';
import {ActivatedRoute} from '@angular/router';
import {SongService} from '../../../services/song.service';
import {Song} from '../../../services/song';
import {DatePipe, NgFor, NgIf} from '@angular/common';
import {DatePipe} from '@angular/common';
import {CardComponent} from '../../../../../widget-modules/components/card/card.component';
@Component({
selector: 'app-history',
templateUrl: './history.component.html',
styleUrls: ['./history.component.less'],
imports: [NgIf, CardComponent, NgFor, DatePipe],
imports: [CardComponent, DatePipe],
})
export class HistoryComponent implements OnInit {
public song: Song | null = null;

View File

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

View File

@@ -12,7 +12,7 @@ import {faEdit, faFileCirclePlus, faTrash} from '@fortawesome/free-solid-svg-ico
import {ShowService} from '../../shows/services/show.service';
import {Show} from '../../shows/services/show';
import {ShowSongService} from '../../shows/services/show-song.service';
import {AsyncPipe, DatePipe, NgFor, NgIf} from '@angular/common';
import {AsyncPipe, DatePipe} from '@angular/common';
import {CardComponent} from '../../../widget-modules/components/card/card.component';
import {RoleDirective} from '../../../services/user/role.directive';
import {SongTextComponent} from '../../../widget-modules/components/song-text/song-text.component';
@@ -31,12 +31,10 @@ import {ShowTypePipe} from '../../../widget-modules/pipes/show-type-translater/s
templateUrl: './song.component.html',
styleUrls: ['./song.component.less'],
imports: [
NgIf,
CardComponent,
RoleDirective,
SongTextComponent,
MatChipListbox,
NgFor,
MatChipOption,
ButtonRowComponent,
ButtonComponent,