add song usage (experimental)
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<ng-container *ngIf="count>0">Verwendet: {{count}}x</ng-container>
|
||||||
|
|
||||||
|
|
||||||
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
18
src/app/modules/songs/song/song-used/song-used.component.ts
Normal file
18
src/app/modules/songs/song/song-used/song-used.component.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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">
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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('/');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user