diff --git a/client/src/app/core/core-services/projector.service.ts b/client/src/app/core/core-services/projector.service.ts index de2cb5771..f7e5e8397 100644 --- a/client/src/app/core/core-services/projector.service.ts +++ b/client/src/app/core/core-services/projector.service.ts @@ -255,7 +255,7 @@ export class ProjectorService { projectorData.forEach(entry => { if (entry.data.error && entry.element.stable) { // Remove this element - const idElementToRemove = this.slideManager.getIdentifialbeProjectorElement(entry.element); + const idElementToRemove = this.slideManager.getIdentifiableProjectorElement(entry.element); elements = elements.filter(element => { return !elementIdentifies(idElementToRemove, element); }); @@ -332,7 +332,7 @@ export class ProjectorService { */ public getSlideTitle(element: ProjectorElement): ProjectorTitle { if (this.slideManager.canSlideBeMappedToModel(element.name)) { - const idElement = this.slideManager.getIdentifialbeProjectorElement(element); + const idElement = this.slideManager.getIdentifiableProjectorElement(element); const viewModel = this.getViewModelFromProjectorElement(idElement); if (viewModel) { return viewModel.getProjectorTitle(); diff --git a/client/src/app/site/agenda/components/list-of-speakers/list-of-speakers.component.ts b/client/src/app/site/agenda/components/list-of-speakers/list-of-speakers.component.ts index 33d30d5c7..4091cda0e 100644 --- a/client/src/app/site/agenda/components/list-of-speakers/list-of-speakers.component.ts +++ b/client/src/app/site/agenda/components/list-of-speakers/list-of-speakers.component.ts @@ -21,8 +21,8 @@ import { SortingListComponent } from 'app/shared/components/sorting-list/sorting import { BaseViewComponent } from 'app/site/base/base-view'; import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable'; import { ViewProjector } from 'app/site/projector/models/view-projector'; -import { CurrentListOfSpeakersService } from 'app/site/projector/services/current-agenda-item.service'; -import { CurrentListOfSpeakersSlideService } from 'app/site/projector/services/current-list-of-of-speakers-slide.service'; +import { CurrentListOfSpeakersSlideService } from 'app/site/projector/services/current-list-of-speakers-slide.service'; +import { CurrentListOfSpeakersService } from 'app/site/projector/services/current-list-of-speakers.service'; import { ViewUser } from 'app/site/users/models/view-user'; import { ViewListOfSpeakers } from '../../models/view-list-of-speakers'; import { SpeakerState, ViewSpeaker } from '../../models/view-speaker'; @@ -254,7 +254,7 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit .getListOfSpeakersObservable(referenceProjector) .subscribe(listOfSpeakers => { if (listOfSpeakers) { - this.setListOfSpeakersId(listOfSpeakers.id); + this.setListOfSpeakers(listOfSpeakers); } }); this.subscriptions.push(this.projectorSubscription); @@ -279,30 +279,34 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit this.closSubscription = this.listOfSpeakersRepo.getViewModelObservable(id).subscribe(listOfSpeakers => { if (listOfSpeakers) { - const title = this.isCurrentListOfSpeakers - ? 'Current list of speakers' - : listOfSpeakers.getTitle() + ` - ${this.translate.instant('List of speakers')}`; - super.setTitle(title); - this.viewListOfSpeakers = listOfSpeakers; - const allSpeakers = this.viewListOfSpeakers.speakers.sort((a, b) => a.weight - b.weight); - this.speakers = allSpeakers.filter(speaker => speaker.state === SpeakerState.WAITING); - // Since the speaker repository is not a normal repository, sorting cannot be handled there - this.speakers.sort((a: ViewSpeaker, b: ViewSpeaker) => a.weight - b.weight); - this.filterUsers(); - this.finishedSpeakers = allSpeakers.filter(speaker => speaker.state === SpeakerState.FINISHED); - - // convert begin time to date and sort - this.finishedSpeakers.sort((a: ViewSpeaker, b: ViewSpeaker) => { - const aTime = new Date(a.begin_time).getTime(); - const bTime = new Date(b.begin_time).getTime(); - return aTime - bTime; - }); - - this.activeSpeaker = allSpeakers.find(speaker => speaker.state === SpeakerState.CURRENT); + this.setListOfSpeakers(listOfSpeakers); } }); } + private setListOfSpeakers(listOfSpeakers: ViewListOfSpeakers): void { + const title = this.isCurrentListOfSpeakers + ? 'Current list of speakers' + : listOfSpeakers.getTitle() + ` - ${this.translate.instant('List of speakers')}`; + super.setTitle(title); + this.viewListOfSpeakers = listOfSpeakers; + const allSpeakers = this.viewListOfSpeakers.speakers.sort((a, b) => a.weight - b.weight); + this.speakers = allSpeakers.filter(speaker => speaker.state === SpeakerState.WAITING); + // Since the speaker repository is not a normal repository, sorting cannot be handled there + this.speakers.sort((a: ViewSpeaker, b: ViewSpeaker) => a.weight - b.weight); + this.filterUsers(); + this.finishedSpeakers = allSpeakers.filter(speaker => speaker.state === SpeakerState.FINISHED); + + // convert begin time to date and sort + this.finishedSpeakers.sort((a: ViewSpeaker, b: ViewSpeaker) => { + const aTime = new Date(a.begin_time).getTime(); + const bTime = new Date(b.begin_time).getTime(); + return aTime - bTime; + }); + + this.activeSpeaker = allSpeakers.find(speaker => speaker.state === SpeakerState.CURRENT); + } + /** * @returns the verbose name of the model of the content object from viewItem. * E.g. if a motion is the current content object, "Motion" will be the returned value. diff --git a/client/src/app/site/projector/components/presentation-control/presentation-control.component.ts b/client/src/app/site/projector/components/presentation-control/presentation-control.component.ts index 22ba0de2b..67a602f58 100644 --- a/client/src/app/site/projector/components/presentation-control/presentation-control.component.ts +++ b/client/src/app/site/projector/components/presentation-control/presentation-control.component.ts @@ -138,7 +138,7 @@ export class PresentationControlComponent extends BaseViewComponent { } private updateElement(element: MediafileProjectorElement): void { - const idElement = this.slideManager.getIdentifialbeProjectorElement(element); + const idElement = this.slideManager.getIdentifiableProjectorElement(element); this.projectorService.updateElement(this.projector.projector, idElement).catch(this.raiseError); } } diff --git a/client/src/app/site/projector/components/projector-detail/projector-detail.component.ts b/client/src/app/site/projector/components/projector-detail/projector-detail.component.ts index cb5a4a2db..62590ef15 100644 --- a/client/src/app/site/projector/components/projector-detail/projector-detail.component.ts +++ b/client/src/app/site/projector/components/projector-detail/projector-detail.component.ts @@ -29,7 +29,7 @@ import { ViewCountdown } from 'app/site/projector/models/view-countdown'; import { ViewProjectorMessage } from 'app/site/projector/models/view-projector-message'; import { SlideManager } from 'app/slides/services/slide-manager.service'; import { CountdownData, CountdownDialogComponent } from '../countdown-dialog/countdown-dialog.component'; -import { CurrentListOfSpeakersSlideService } from '../../services/current-list-of-of-speakers-slide.service'; +import { CurrentListOfSpeakersSlideService } from '../../services/current-list-of-speakers-slide.service'; import { CurrentSpeakerChyronSlideService } from '../../services/current-speaker-chyron-slide.service'; import { MessageData, MessageDialogComponent } from '../message-dialog/message-dialog.component'; import { ProjectorEditDialogComponent } from '../projector-edit-dialog/projector-edit-dialog.component'; @@ -232,7 +232,7 @@ export class ProjectorDetailComponent extends BaseViewComponent implements OnIni } public unprojectCurrent(element: ProjectorElement): void { - const idElement = this.slideManager.getIdentifialbeProjectorElement(element); + const idElement = this.slideManager.getIdentifiableProjectorElement(element); this.projectorService.removeFrom(this.projector.projector, idElement).catch(this.raiseError); } diff --git a/client/src/app/site/projector/services/current-agenda-item.service.ts b/client/src/app/site/projector/services/current-agenda-item.service.ts deleted file mode 100644 index 01941443d..000000000 --- a/client/src/app/site/projector/services/current-agenda-item.service.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Injectable } from '@angular/core'; - -import { BehaviorSubject, Observable } from 'rxjs'; - -import { ProjectorService } from 'app/core/core-services/projector.service'; -import { ProjectorRepositoryService } from 'app/core/repositories/projector/projector-repository.service'; -import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers'; -import { isBaseViewModelWithListOfSpeakers } from 'app/site/base/base-view-model-with-list-of-speakers'; -import { SlideManager } from 'app/slides/services/slide-manager.service'; -import { ViewProjector } from '../models/view-projector'; - -/** - * Observes the projector config for a given projector and returns a observable of the - * current view list of speakers displayed on the projector. - */ -@Injectable({ - providedIn: 'root' -}) -export class CurrentListOfSpeakersService { - private currentListOfSpeakersIds: { [projectorId: number]: BehaviorSubject } = {}; - - public constructor( - private projectorService: ProjectorService, - private projectorRepo: ProjectorRepositoryService, - private slideManager: SlideManager - ) { - // Watch for changes and update the current list of speakers for every projector. - this.projectorRepo.getGeneralViewModelObservable().subscribe(projector => { - if (projector && this.currentListOfSpeakersIds[projector.id]) { - const listOfSpeakers = this.getCurrentListOfSpeakersForProjector(projector); - this.currentListOfSpeakersIds[projector.id].next(listOfSpeakers); - } - }); - } - - /** - * Returns an observable for the view list of speakers of the currently projected element on the - * given projector. - * - * @param projector The projector to observe. - * @returns An observalbe for the list of speakers. Null, if no element with an list of speakers is shown. - */ - public getListOfSpeakersObservable(projector: ViewProjector): Observable { - if (!this.currentListOfSpeakersIds[projector.id]) { - const listOfSpeakers = this.getCurrentListOfSpeakersForProjector(projector); - this.currentListOfSpeakersIds[projector.id] = new BehaviorSubject( - listOfSpeakers - ); - } - return this.currentListOfSpeakersIds[projector.id].asObservable(); - } - - /** - * Tries to get the view list of speakers for one non stable element on the projector. - * - * @param projector The projector - * @returns The view list of speakers or null, if there is no such projector element. - */ - private getCurrentListOfSpeakersForProjector(projector: ViewProjector): ViewListOfSpeakers | null { - const nonStableElements = projector.elements.filter(element => !element.stable); - if (nonStableElements.length > 0) { - // The normal case is just one non stable slide - const nonStableElement = this.slideManager.getIdentifialbeProjectorElement(nonStableElements[0]); - try { - const viewModel = this.projectorService.getViewModelFromProjectorElement(nonStableElement); - if (isBaseViewModelWithListOfSpeakers(viewModel)) { - return viewModel.listOfSpeakers; - } - } catch (e) { - // make TypeScript silent. - } - } - return null; - } -} diff --git a/client/src/app/site/projector/services/current-list-of-of-speakers-slide.service.ts b/client/src/app/site/projector/services/current-list-of-speakers-slide.service.ts similarity index 100% rename from client/src/app/site/projector/services/current-list-of-of-speakers-slide.service.ts rename to client/src/app/site/projector/services/current-list-of-speakers-slide.service.ts diff --git a/client/src/app/site/projector/services/current-list-of-speakers.service.ts b/client/src/app/site/projector/services/current-list-of-speakers.service.ts new file mode 100644 index 000000000..396f0486a --- /dev/null +++ b/client/src/app/site/projector/services/current-list-of-speakers.service.ts @@ -0,0 +1,124 @@ +import { Injectable } from '@angular/core'; + +import { BehaviorSubject, Observable, Subscription } from 'rxjs'; + +import { ProjectorService } from 'app/core/core-services/projector.service'; +import { ListOfSpeakersRepositoryService } from 'app/core/repositories/agenda/list-of-speakers-repository.service'; +import { ProjectorRepositoryService } from 'app/core/repositories/projector/projector-repository.service'; +import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers'; +import { isBaseViewModelWithListOfSpeakers } from 'app/site/base/base-view-model-with-list-of-speakers'; +import { SlideManager } from 'app/slides/services/slide-manager.service'; +import { ViewProjector } from '../models/view-projector'; + +/** + * Observes the projector config for a given projector and returns a observable of the + * current view list of speakers displayed on the projector. + */ +@Injectable({ + providedIn: 'root' +}) +export class CurrentListOfSpeakersService { + /** + * This map holds the current (number or null) los-id for the the projector. + * It is used to check, if the reference has changed (this clos id changed for one projector). + */ + private currentListOfSpeakersIds: { [projectorId: number]: number | null } = {}; + + /** + * Active subscriptions for the clos of each projector. + */ + private currentListOfSpeakersSubscriptions: { [projectorId: number]: Subscription } = {}; + + /** + * All subjects for all clos of each projector. + */ + private currentListOfSpeakers: { [projectorId: number]: BehaviorSubject } = {}; + + public constructor( + private projectorService: ProjectorService, + private projectorRepo: ProjectorRepositoryService, + private listOfSpeakersRepo: ListOfSpeakersRepositoryService, + private slideManager: SlideManager + ) { + // Watch for changes and update the current list of speakers for every projector. + this.projectorRepo.getGeneralViewModelObservable().subscribe(projector => { + if (projector) { + this.setListOfSpeakersForProjector(projector); + } + }); + } + + /** + * Returns an observable for the view list of speakers of the currently projected element on the + * given projector. + * + * @param projector The projector to observe. + * @returns An observalbe for the list of speakers. Null, if no element with an list of speakers is shown. + */ + public getListOfSpeakersObservable(projector: ViewProjector): Observable { + if (!this.currentListOfSpeakers[projector.id]) { + this.setListOfSpeakersForProjector(projector); + } + return this.currentListOfSpeakers[projector.id].asObservable(); + } + + /** + * calld on startup and on every projector update. + * If the reference didn't changed the clos for the projector just gets pushed. + * + * If it was the first call to this function for a projector or the id changed, + * there will be a subscription to the LOS-repo to stay informed when the clos + * of the projector is updated (if there is a current CLOS). + */ + private setListOfSpeakersForProjector(projector: ViewProjector): void { + const listOfSpeakers = this.getCurrentListOfSpeakersForProjector(projector); + const listOfSpeakersId = listOfSpeakers ? listOfSpeakers.id : null; + if (this.currentListOfSpeakersIds[projector.id] === listOfSpeakersId) { + this.currentListOfSpeakers[projector.id].next(listOfSpeakers); + } else { + this.currentListOfSpeakersIds[projector.id] = listOfSpeakersId; + + if (!this.currentListOfSpeakers[projector.id]) { + this.currentListOfSpeakers[projector.id] = new BehaviorSubject(listOfSpeakers); + } + // Do not send the listOfSpeakers through currentListOfSpeakers, because this will be + // toggled by the listOfSpeakersRepo subscription below. + + if (this.currentListOfSpeakersSubscriptions[projector.id]) { + this.currentListOfSpeakersSubscriptions[projector.id].unsubscribe(); + this.currentListOfSpeakersSubscriptions[projector.id] = null; + } + if (listOfSpeakersId !== null) { + this.currentListOfSpeakersSubscriptions[projector.id] = this.listOfSpeakersRepo + .getViewModelObservable(listOfSpeakers.id) + .subscribe(los => { + if (los && this.currentListOfSpeakers[projector.id]) { + this.currentListOfSpeakers[projector.id].next(los); + } + }); + } + } + } + + /** + * Tries to get the view list of speakers for one non stable element on the projector. + * + * @param projector The projector + * @returns The view list of speakers or null, if there is no such projector element. + */ + private getCurrentListOfSpeakersForProjector(projector: ViewProjector): ViewListOfSpeakers | null { + const nonStableElements = projector.elements.filter(element => !element.stable); + for (const nonStableElement of nonStableElements) { + const identifiableNonStableElement = this.slideManager.getIdentifiableProjectorElement(nonStableElement); + try { + const viewModel = this.projectorService.getViewModelFromProjectorElement(identifiableNonStableElement); + if (isBaseViewModelWithListOfSpeakers(viewModel)) { + return viewModel.listOfSpeakers; + } + } catch (e) { + // make TypeScript silent. + } + } + return null; + } +} diff --git a/client/src/app/slides/services/slide-manager.service.ts b/client/src/app/slides/services/slide-manager.service.ts index 50cc37f19..ae9b44e53 100644 --- a/client/src/app/slides/services/slide-manager.service.ts +++ b/client/src/app/slides/services/slide-manager.service.ts @@ -54,7 +54,7 @@ export class SlideManager { return this.loadedSlideConfigurations[slideName]; } - public getIdentifialbeProjectorElement

(element: P): IdentifiableProjectorElement & P { + public getIdentifiableProjectorElement

(element: P): IdentifiableProjectorElement & P { const identifiableElement: IdentifiableProjectorElement & P = element as IdentifiableProjectorElement & P; const identifiers = this.getManifest(element.name).elementIdentifiers.map(x => x); // map to copy. identifiableElement.getIdentifiers = () => identifiers;