add song usage (experimental)

This commit is contained in:
2023-08-12 10:54:34 +02:00
parent 551bed9a77
commit ea3d6b0b5e
14 changed files with 117 additions and 12 deletions

View File

@@ -1,5 +1,5 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {BehaviorSubject, Observable} from 'rxjs'; import {BehaviorSubject, firstValueFrom, Observable} from 'rxjs';
import {DbService} from '../../../services/db.service'; import {DbService} from '../../../services/db.service';
import {Show} from './show'; import {Show} from './show';
import {map} from 'rxjs/operators'; import {map} from 'rxjs/operators';
@@ -15,6 +15,8 @@ export class ShowDataService {
} }
public list$ = new BehaviorSubject<Show[]>([]); public list$ = new BehaviorSubject<Show[]>([]);
public list = () => firstValueFrom(this.dbService.col$<Show>(this.collection));
public read$ = (showId: string): Observable<Show | null> => this.list$.pipe(map(_ => _.find(s => s.id === showId) || null)); public read$ = (showId: string): Observable<Show | null> => this.list$.pipe(map(_ => _.find(s => s.id === showId) || null));
// public list$ = (): Observable<Show[]> => this.dbService.col$(this.collection); // public list$ = (): Observable<Show[]> => this.dbService.col$(this.collection);

View File

@@ -1,6 +1,6 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {DbService} from '../../../services/db.service'; import {DbService} from '../../../services/db.service';
import {Observable} from 'rxjs'; import {firstValueFrom, Observable} from 'rxjs';
import {ShowSong} from './show-song'; import {ShowSong} from './show-song';
import {QueryFn} from '@angular/fire/compat/firestore/interfaces'; import {QueryFn} from '@angular/fire/compat/firestore/interfaces';
@@ -14,6 +14,8 @@ export class ShowSongDataService {
public constructor(private dbService: DbService) {} public constructor(private dbService: DbService) {}
public list$ = (showId: string, queryFn?: QueryFn): Observable<ShowSong[]> => this.dbService.col$(`${this.collection}/${showId}/${this.subCollection}`, queryFn); public list$ = (showId: string, queryFn?: QueryFn): Observable<ShowSong[]> => this.dbService.col$(`${this.collection}/${showId}/${this.subCollection}`, queryFn);
public list = (showId: string): Promise<ShowSong[]> => firstValueFrom(this.list$(showId));
public read$ = (showId: string, songId: string): Observable<ShowSong | null> => this.dbService.doc$(`${this.collection}/${showId}/${this.subCollection}/${songId}`); public read$ = (showId: string, songId: string): Observable<ShowSong | null> => this.dbService.doc$(`${this.collection}/${showId}/${this.subCollection}/${songId}`);
public update$ = async (showId: string, songId: string, data: Partial<ShowSong>): Promise<void> => public update$ = async (showId: string, songId: string, data: Partial<ShowSong>): Promise<void> =>
await this.dbService.doc(`${this.collection}/${showId}/${this.subCollection}/${songId}`).update(data); await this.dbService.doc(`${this.collection}/${showId}/${this.subCollection}/${songId}`).update(data);

View File

@@ -1,10 +1,12 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {ShowSongDataService} from './show-song-data.service'; import {ShowSongDataService} from './show-song-data.service';
import {firstValueFrom, Observable} from 'rxjs'; import {firstValueFrom, forkJoin, mergeMap, Observable} from 'rxjs';
import {ShowSong} from './show-song'; import {ShowSong} from './show-song';
import {SongDataService} from '../../songs/services/song-data.service'; import {SongDataService} from '../../songs/services/song-data.service';
import {UserService} from '../../../services/user/user.service'; import {UserService} from '../../../services/user/user.service';
import {ShowService} from './show.service'; import {ShowService} from './show.service';
import {map, switchMap} from 'rxjs/operators';
import {ShowDataService} from './show-data.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@@ -14,7 +16,8 @@ export class ShowSongService {
private showSongDataService: ShowSongDataService, private showSongDataService: ShowSongDataService,
private songDataService: SongDataService, private songDataService: SongDataService,
private userService: UserService, private userService: UserService,
private showService: ShowService private showService: ShowService,
private showDataService: ShowDataService
) {} ) {}
public async new$(showId: string, songId: string, addedLive = false): Promise<string | null> { public async new$(showId: string, songId: string, addedLive = false): Promise<string | null> {
@@ -47,5 +50,39 @@ export class ShowSongService {
await this.showService.update$(showId, {order}); await this.showService.update$(showId, {order});
} }
public countSongUsage$(songId: string) {
return this.userService.user$.pipe(
switchMap(user =>
this.showService.list$().pipe(
map(shows => {
const myShows = shows.filter(_ => _.owner === user?.id);
return myShows.map(show => this.list$(show.id));
}),
mergeMap(songs => forkJoin(songs)),
map(songs => songs.reduce((pn, u) => [...pn, ...u], [])),
map(songs => songs.reduce((count, song) => (song.songId === songId ? 1 : 0), 0))
)
)
);
}
public async countSongUsage(songId: string) {
// return 0; // todo
const user = await this.userService.currentUser();
const userId = user?.id;
if (!userId) return null;
const shows = await this.showDataService.list();
let count = 0;
for (const show of shows.filter(_ => _.owner === userId)) {
const songs = await this.showSongDataService.list(show.id);
for (const song of songs) {
if (song.songId === songId) count++;
}
}
return count;
}
public update$ = async (showId: string, songId: string, data: Partial<ShowSong>): Promise<void> => await this.showSongDataService.update$(showId, songId, data); public update$ = async (showId: string, songId: string, data: Partial<ShowSong>): Promise<void> => await this.showSongDataService.update$(showId, songId, data);
} }

View File

@@ -0,0 +1,3 @@
<ng-container *ngIf="count>0">Verwendet: {{count}}x</ng-container>

View File

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

View File

@@ -0,0 +1,18 @@
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
import {ShowSongService} from '../../../shows/services/show-song.service';
@Component({
selector: 'app-song-used',
templateUrl: './song-used.component.html',
styleUrls: ['./song-used.component.less'],
})
export class SongUsedComponent implements OnInit {
@Input() public songId: string | null = null;
public count = 0;
public constructor(private service: ShowSongService, private cref: ChangeDetectorRef) {}
public async ngOnInit() {
this.count = (await this.service.countSongUsage(this.songId ?? '')) ?? 0;
}
}

View File

@@ -29,6 +29,9 @@
<div *ngIf="song.label">Verlag: {{ song.label }}</div> <div *ngIf="song.label">Verlag: {{ song.label }}</div>
<div *ngIf="song.origin">Quelle: {{ song.origin }}</div> <div *ngIf="song.origin">Quelle: {{ song.origin }}</div>
<div *ngIf="song.origin">Quelle: {{ song.origin }}</div> <div *ngIf="song.origin">Quelle: {{ song.origin }}</div>
<div>
<app-song-used [songId]="song.id"></app-song-used>
</div>
</div> </div>
</div> </div>
@@ -65,7 +68,8 @@
>Bearbeiten >Bearbeiten
</app-button> </app-button>
<ng-container *appRole="['leader']"> <ng-container *appRole="['leader']">
<app-button [icon]="faFileCirclePlus" [matMenuTriggerFor]="menu">
<app-button [icon]="faFileCirclePlus" [matMenuTriggerFor]="menu">
Zu Veranstaltung hinzufügen Zu Veranstaltung hinzufügen
</app-button> </app-button>
<mat-menu #menu="matMenu"> <mat-menu #menu="matMenu">

View File

@@ -15,9 +15,10 @@ import {ButtonModule} from '../../../widget-modules/components/button/button.mod
import {FileComponent} from './file/file.component'; import {FileComponent} from './file/file.component';
import {MatMenuModule} from '@angular/material/menu'; import {MatMenuModule} from '@angular/material/menu';
import {ShowTypeTranslaterModule} from '../../../widget-modules/pipes/show-type-translater/show-type-translater.module'; import {ShowTypeTranslaterModule} from '../../../widget-modules/pipes/show-type-translater/show-type-translater.module';
import {SongUsedComponent} from './song-used/song-used.component';
@NgModule({ @NgModule({
declarations: [SongComponent, FileComponent], declarations: [SongComponent, FileComponent, SongUsedComponent],
exports: [SongComponent], exports: [SongComponent],
imports: [ imports: [
CommonModule, CommonModule,

View File

@@ -21,7 +21,13 @@
></app-link> ></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 class="actions">
<app-filter></app-filter> <app-filter *appRole="['user', 'presenter', 'leader']"></app-filter>
<app-link
*ngIf="userService.userId$|async"
[icon]="faLogout"
link="/user/logout"
></app-link>
</div> </div>
</nav> </nav>

View File

@@ -32,7 +32,7 @@ nav {
display: flex; display: flex;
height: 100%; height: 100%;
align-items: center; align-items: center;
padding-right: 20px; // padding-right: 20px;
} }

View File

@@ -1,7 +1,9 @@
import {Component} from '@angular/core'; import {Component} from '@angular/core';
import {faChalkboard, faMusic, faPersonBooth, faUserCog} from '@fortawesome/free-solid-svg-icons'; import {faChalkboard, faMusic, faPersonBooth, faRightFromBracket, faUserCog} from '@fortawesome/free-solid-svg-icons';
import {fromEvent, Observable} from 'rxjs'; import {fromEvent, Observable} from 'rxjs';
import {distinctUntilChanged, map, shareReplay, startWith} from 'rxjs/operators'; import {distinctUntilChanged, map, shareReplay, startWith} from 'rxjs/operators';
import {UserService} from '../../../../services/user/user.service';
import {Router} from '@angular/router';
@Component({ @Component({
selector: 'app-navigation', selector: 'app-navigation',
@@ -13,6 +15,9 @@ export class NavigationComponent {
public faShows = faPersonBooth; public faShows = faPersonBooth;
public faUser = faUserCog; public faUser = faUserCog;
public faPresentation = faChalkboard; public faPresentation = faChalkboard;
public faLogout = faRightFromBracket;
public constructor(public userService: UserService, private router: Router) {}
public readonly windowScroll$: Observable<number> = fromEvent(window, 'scroll').pipe( public readonly windowScroll$: Observable<number> = fromEvent(window, 'scroll').pipe(
map(() => window.scrollY), map(() => window.scrollY),
@@ -22,4 +27,9 @@ export class NavigationComponent {
); );
public isNavigationHidden = (scroll: number | null): boolean => (scroll ?? 0) > 60; public isNavigationHidden = (scroll: number | null): boolean => (scroll ?? 0) > 60;
public async logout(): Promise<void> {
await this.userService.logout();
await this.router.navigateByUrl('/');
}
} }

View File

@@ -3,7 +3,7 @@
.card { .card {
margin: 20px; margin: 20px;
border-radius: 8px; border-radius: 8px;
background: #fffc; background: #fff;
backdrop-filter: blur(12px); backdrop-filter: blur(12px);
overflow: hidden; overflow: hidden;
width: 800px; width: 800px;

View File

@@ -11,7 +11,7 @@
<link href="favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"> <link href="favicon-16x16.png" rel="icon" sizes="16x16" type="image/png">
<link color="#4286f4" href="safari-pinned-tab.svg" rel="mask-icon"> <link color="#4286f4" href="safari-pinned-tab.svg" rel="mask-icon">
<meta content="#4286f4" name="msapplication-TileColor"> <meta content="#4286f4" name="msapplication-TileColor">
<meta content="#4286f4" name="theme-color"> <meta content="#222222" name="theme-color">
<link href="manifest.webmanifest" rel="manifest"> <link href="manifest.webmanifest" rel="manifest">
<meta content="#4286f4" name="theme-color"> <meta content="#4286f4" name="theme-color">