diff --git a/client/src/app/shared/models/core/projector.ts b/client/src/app/shared/models/core/projector.ts index 689ea8592..0da905990 100644 --- a/client/src/app/shared/models/core/projector.ts +++ b/client/src/app/shared/models/core/projector.ts @@ -56,6 +56,7 @@ export class Projector extends BaseModel { public name: string; public width: number; public height: number; + public reference_projector_id: number; public projectiondefaults: ProjectionDefault[]; public constructor(input?: any) { diff --git a/client/src/app/site/base/base-repository.ts b/client/src/app/site/base/base-repository.ts index ace9f4850..2f839440b 100644 --- a/client/src/app/site/base/base-repository.ts +++ b/client/src/app/site/base/base-repository.ts @@ -1,4 +1,4 @@ -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { OpenSlidesComponent } from '../../openslides.component'; import { BaseViewModel } from './base-view-model'; @@ -24,6 +24,11 @@ export abstract class BaseRepository = new BehaviorSubject([]); + /** + * Observable subject for any changes of view models. + */ + protected readonly generalViewModelSubject: Subject = new Subject(); + /** * Construction routine for the base repository * @@ -149,6 +154,13 @@ export abstract class BaseRepository { + return this.generalViewModelSubject.asObservable(); + } + /** * Updates the ViewModel observable using a ViewModel corresponding to the id */ @@ -156,6 +168,7 @@ export abstract class BaseRepositoryRequired + + +

Current list fo speakers reference

+ + + + self + + + {{ refProjector.getTitle() | translate }} + + + +

Resolution and size

@@ -82,6 +96,8 @@ {{ updateForm.value.width }} + +
Show clock diff --git a/client/src/app/site/projector/components/projector-list/projector-list.component.ts b/client/src/app/site/projector/components/projector-list/projector-list.component.ts index 9dba89597..50fe6ffa2 100644 --- a/client/src/app/site/projector/components/projector-list/projector-list.component.ts +++ b/client/src/app/site/projector/components/projector-list/projector-list.component.ts @@ -91,7 +91,8 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit name: ['', Validators.required], aspectRatio: ['', Validators.required], width: [0, Validators.required], - clock: [true] + clock: [true], + reference_projector_id: [] }); } @@ -184,11 +185,16 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit } this.editId = projector.id; this.updateForm.reset(); + + const reference_projector_id = projector.reference_projector_id + ? projector.reference_projector_id + : projector.id; this.updateForm.patchValue({ name: projector.name, aspectRatio: this.getAspectRatioKey(projector), width: projector.width, - clock: this.clockSlideService.isProjectedOn(projector) + clock: this.clockSlideService.isProjectedOn(projector), + reference_projector_id: reference_projector_id }); } @@ -215,7 +221,8 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit const updateProjector: Partial = { name: this.updateForm.value.name, width: this.updateForm.value.width, - height: Math.round(this.updateForm.value.width / aspectRatios[this.updateForm.value.aspectRatio]) + height: Math.round(this.updateForm.value.width / aspectRatios[this.updateForm.value.aspectRatio]), + reference_projector_id: this.updateForm.value.reference_projector_id }; try { await this.clockSlideService.setProjectedOn(projector, this.updateForm.value.clock); @@ -237,4 +244,14 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit this.repo.delete(projector).then(null, this.raiseError); } } + + /** + * Get all available reference projectors for the given projector. These + * projectors are all existing projectors exluding the given projector + * + * @returns all available reference projectors + */ + public getReferenceProjectorsFor(projector: ViewProjector): ViewProjector[] { + return this.repo.getViewModelList().filter(p => p.id !== projector.id); + } } diff --git a/client/src/app/site/projector/models/view-projector.ts b/client/src/app/site/projector/models/view-projector.ts index 99fa9f310..d7ebf3613 100644 --- a/client/src/app/site/projector/models/view-projector.ts +++ b/client/src/app/site/projector/models/view-projector.ts @@ -48,6 +48,10 @@ export class ViewProjector extends BaseViewModel { return this.projector ? this.projector.scroll : null; } + public get reference_projector_id(): number { + return this.projector ? this.projector.reference_projector_id : null; + } + public constructor(projector?: Projector) { super(); this._projector = projector; 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-of-speakers-slide.service.ts index bcb9ebdd1..0689d664e 100644 --- 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-of-speakers-slide.service.ts @@ -2,6 +2,10 @@ import { Injectable } from '@angular/core'; import { ProjectorService } from 'app/core/services/projector.service'; import { ViewProjector } from '../models/view-projector'; import { IdentifiableProjectorElement } from 'app/shared/models/core/projector'; +import { ProjectorRepositoryService } from './projector-repository.service'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { SlideManager } from 'app/slides/services/slide-manager.service'; +import { AgendaBaseModel } from 'app/shared/models/base/agenda-base-model'; /** */ @@ -9,8 +13,28 @@ import { IdentifiableProjectorElement } from 'app/shared/models/core/projector'; providedIn: 'root' }) export class CurrentListOfSpeakersSlideService { - public constructor(private projectorService: ProjectorService) {} + private currentItemIds: { [projectorId: number]: BehaviorSubject } = {}; + public constructor( + private projectorService: ProjectorService, + private projectorRepo: ProjectorRepositoryService, + private slideManager: SlideManager + ) { + this.projectorRepo.getGeneralViewModelObservable().subscribe(projector => { + const itemId = this.getCurrentAgendaItemIdForProjector(projector); + if (this.currentItemIds[projector.id]) { + this.currentItemIds[projector.id].next(itemId); + } + }); + } + + /** + * Returns the basic projector element for the CLOS slide. If overlay=True, the projector element + * will be the overlay instead of the slide. + * + * @param overlay Wether to have a slide or overlay + * @returns the identifiable CLOS projector element. + */ private getCurrentListOfSpeakersProjectorElement(overlay: boolean): IdentifiableProjectorElement { return { name: overlay ? 'agenda/current-list-of-speakers-overlay' : 'agenda/current-list-of-speakers', @@ -19,6 +43,51 @@ export class CurrentListOfSpeakersSlideService { }; } + /** + * Returns an observable for the agenda item id of the currently projected element on the + * given projector. + * + * @param projector The projector to observe. + * @returns An observalbe for the agenda item id. Null, if no element with an agenda item is shown. + */ + public getAgendaItemIdObservable(projector: ViewProjector): Observable { + if (!this.currentItemIds[projector.id]) { + const itemId = this.getCurrentAgendaItemIdForProjector(projector); + this.currentItemIds[projector.id] = new BehaviorSubject(itemId); + } + return this.currentItemIds[projector.id].asObservable(); + } + + /** + * Tries to get the agenda item id for one non stable element on the projector. + * + * @param projector The projector + * @returns The agenda item id or null, if there is no such projector element. + */ + private getCurrentAgendaItemIdForProjector(projector: ViewProjector): number | null { + const nonStableElements = projector.elements.filter(element => !element.stable); + if (nonStableElements.length > 0) { + const nonStableElement = this.slideManager.getIdentifialbeProjectorElement(nonStableElements[0]); // The normal case is just one non stable slide + try { + const model = this.projectorService.getModelFromProjectorElement(nonStableElement); + if (model instanceof AgendaBaseModel) { + // TODO: Use repositories associated to models + return (model).agenda_item_id; + } + } catch (e) { + // make TypeScript silent. + } + } + return null; + } + + /** + * Queries, if the slide/overlay is projected on the given projector. + * + * @param projector The projector + * @param overlay True, if we query for an overlay instead of the slide + * @returns if the slide/overlay is projected on the projector + */ public isProjectedOn(projector: ViewProjector, overlay: boolean): boolean { return this.projectorService.isProjectedOn( this.getCurrentListOfSpeakersProjectorElement(overlay), @@ -26,6 +95,12 @@ export class CurrentListOfSpeakersSlideService { ); } + /** + * Toggle the projection state of the slide/overlay on the given projector + * + * @param projector The projector + * @param overlay Slide or overlay + */ public async toggleOn(projector: ViewProjector, overlay: boolean): Promise { const isClosProjected = this.isProjectedOn(projector, overlay); if (isClosProjected) { diff --git a/client/src/app/slides/agenda/current-list-of-speakers-overlay/agenda-current-list-of-speakers-overlay-slide.component.ts b/client/src/app/slides/agenda/current-list-of-speakers-overlay/agenda-current-list-of-speakers-overlay-slide.component.ts index fec60e277..8a467b9a2 100644 --- a/client/src/app/slides/agenda/current-list-of-speakers-overlay/agenda-current-list-of-speakers-overlay-slide.component.ts +++ b/client/src/app/slides/agenda/current-list-of-speakers-overlay/agenda-current-list-of-speakers-overlay-slide.component.ts @@ -1,7 +1,6 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { BaseSlideComponent } from 'app/slides/base-slide-component'; -import { HttpService } from 'app/core/services/http.service'; import { AgendaCurrentListOfSpeakersSlideData } from '../base/agenda-current-list-of-speakers-slide-data'; @Component({ @@ -9,15 +8,10 @@ import { AgendaCurrentListOfSpeakersSlideData } from '../base/agenda-current-lis templateUrl: './agenda-current-list-of-speakers-overlay-slide.component.html', styleUrls: ['./agenda-current-list-of-speakers-overlay-slide.component.scss'] }) -export class AgendaCurrentListOfSpeakersOverlaySlideComponent - extends BaseSlideComponent - implements OnInit { - public constructor(private http: HttpService) { +export class AgendaCurrentListOfSpeakersOverlaySlideComponent extends BaseSlideComponent< + AgendaCurrentListOfSpeakersSlideData +> { + public constructor() { super(); - console.log(this.http); - } - - public ngOnInit(): void { - console.log('Hello from current list of speakers overlay'); } } diff --git a/client/src/app/slides/agenda/current-list-of-speakers/agenda-current-list-of-speakers-slide.component.ts b/client/src/app/slides/agenda/current-list-of-speakers/agenda-current-list-of-speakers-slide.component.ts index 31b21f2d2..436248765 100644 --- a/client/src/app/slides/agenda/current-list-of-speakers/agenda-current-list-of-speakers-slide.component.ts +++ b/client/src/app/slides/agenda/current-list-of-speakers/agenda-current-list-of-speakers-slide.component.ts @@ -14,7 +14,5 @@ export class AgendaCurrentListOfSpeakersSlideComponent extends BaseSlideComponen super(); } - public ngOnInit(): void { - console.log('Hello from current list of speakers slide'); - } + public ngOnInit(): void {} } diff --git a/client/src/app/slides/core/countdown/core-countdown-slide.component.ts b/client/src/app/slides/core/countdown/core-countdown-slide.component.ts index 5f1eb3250..466e44046 100644 --- a/client/src/app/slides/core/countdown/core-countdown-slide.component.ts +++ b/client/src/app/slides/core/countdown/core-countdown-slide.component.ts @@ -1,20 +1,14 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { BaseSlideComponent } from 'app/slides/base-slide-component'; import { CoreCountdownSlideData } from './core-countdown-slide-data'; -import { HttpService } from 'app/core/services/http.service'; @Component({ selector: 'os-core-countdown-slide', templateUrl: './core-countdown-slide.component.html', styleUrls: ['./core-countdown-slide.component.scss'] }) -export class CoreCountdownSlideComponent extends BaseSlideComponent implements OnInit { - public constructor(private http: HttpService) { +export class CoreCountdownSlideComponent extends BaseSlideComponent { + public constructor() { super(); - console.log(this.http); - } - - public ngOnInit(): void { - console.log('Hello from countdown slide'); } } diff --git a/client/src/app/slides/motions/motion/motions-motion-slide.component.ts b/client/src/app/slides/motions/motion/motions-motion-slide.component.ts index c3b472a8b..080c4d6d6 100644 --- a/client/src/app/slides/motions/motion/motions-motion-slide.component.ts +++ b/client/src/app/slides/motions/motion/motions-motion-slide.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { BaseSlideComponent } from 'app/slides/base-slide-component'; import { MotionsMotionSlideData } from './motions-motion-slide-data'; @@ -7,12 +7,8 @@ import { MotionsMotionSlideData } from './motions-motion-slide-data'; templateUrl: './motions-motion-slide.component.html', styleUrls: ['./motions-motion-slide.component.scss'] }) -export class MotionsMotionSlideComponent extends BaseSlideComponent implements OnInit { +export class MotionsMotionSlideComponent extends BaseSlideComponent { public constructor() { super(); } - - public ngOnInit(): void { - console.log('Hello from motion slide'); - } } diff --git a/client/src/app/slides/users/user/users-user-slide.component.ts b/client/src/app/slides/users/user/users-user-slide.component.ts index 33a6d43a2..e69e3ccd2 100644 --- a/client/src/app/slides/users/user/users-user-slide.component.ts +++ b/client/src/app/slides/users/user/users-user-slide.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { BaseSlideComponent } from 'app/slides/base-slide-component'; import { UsersUserSlideData } from './users-user-slide-data'; @@ -7,12 +7,8 @@ import { UsersUserSlideData } from './users-user-slide-data'; templateUrl: './users-user-slide.component.html', styleUrls: ['./users-user-slide.component.scss'] }) -export class UsersUserSlideComponent extends BaseSlideComponent implements OnInit { +export class UsersUserSlideComponent extends BaseSlideComponent { public constructor() { super(); } - - public ngOnInit(): void { - console.log('Hello from user slide'); - } } diff --git a/openslides/core/migrations/0016_projector_reference_projector.py b/openslides/core/migrations/0016_projector_reference_projector.py new file mode 100644 index 000000000..ad8005d91 --- /dev/null +++ b/openslides/core/migrations/0016_projector_reference_projector.py @@ -0,0 +1,24 @@ +# Generated by Django 2.1.5 on 2019-01-31 10:24 + +from django.db import migrations, models + +import openslides.utils.models + + +class Migration(migrations.Migration): + + dependencies = [("core", "0015_auto_20190122_1216")] + + operations = [ + migrations.AddField( + model_name="projector", + name="reference_projector", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=openslides.utils.models.SET_NULL_AND_AUTOUPDATE, + related_name="references", + to="core.Projector", + ), + ) + ] diff --git a/openslides/core/models.py b/openslides/core/models.py index 054996d5e..caa1c0778 100644 --- a/openslides/core/models.py +++ b/openslides/core/models.py @@ -84,6 +84,14 @@ class Projector(RESTModelMixin, models.Model): name = models.CharField(max_length=255, unique=True, blank=True) + reference_projector = models.ForeignKey( + "self", + on_delete=SET_NULL_AND_AUTOUPDATE, + null=True, + blank=True, + related_name="references", + ) + class Meta: """ Contains general permissions that can not be placed in a specific app. diff --git a/openslides/core/serializers.py b/openslides/core/serializers.py index 5cf60a74a..a9d306434 100644 --- a/openslides/core/serializers.py +++ b/openslides/core/serializers.py @@ -97,6 +97,7 @@ class ProjectorSerializer(ModelSerializer): "name", "width", "height", + "reference_projector", "projectiondefaults", ) read_only_fields = ("scale", "scroll")