Merge pull request #4441 from FinnStutzenstein/closReference

One global clos reference. More projector buttons for the clos view
This commit is contained in:
Emanuel Schütze 2019-03-01 13:37:28 +01:00 committed by GitHub
commit c8c55ce597
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 161 additions and 91 deletions

View File

@ -43,7 +43,7 @@ export class ProjectorRepositoryService extends BaseRepository<ViewProjector, Pr
private http: HttpService, private http: HttpService,
private translate: TranslateService private translate: TranslateService
) { ) {
super(DS, mapperService, viewModelStoreService, Projector); super(DS, mapperService, viewModelStoreService, Projector, [Projector]);
} }
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {

View File

@ -3,14 +3,14 @@
[ngClass]="isProjected() ? 'projector-active' : 'projector-inactive'"> [ngClass]="isProjected() ? 'projector-active' : 'projector-inactive'">
<mat-icon>videocam</mat-icon> <mat-icon>videocam</mat-icon>
</button> </button>
<button type="button" *ngIf="menuItem" mat-menu-item (click)="onClick()"
[ngClass]="isProjected() ? 'projector-active' : 'projector-inactive'">
<mat-icon>videocam</mat-icon>
<span translate>Project</span>
</button>
<button type="button" *ngIf="text && !menuItem" mat-button (click)="onClick($event)" <button type="button" *ngIf="text && !menuItem" mat-button (click)="onClick($event)"
[ngClass]="isProjected() ? 'projector-active' : 'projector-inactive'"> [ngClass]="isProjected() ? 'projector-active' : 'projector-inactive'">
<mat-icon>videocam</mat-icon> <mat-icon>videocam</mat-icon>
{{ text | translate }} {{ text | translate }}
</button> </button>
<button type="button" *ngIf="menuItem" mat-menu-item (click)="onClick()"
[ngClass]="isProjected() ? 'projector-active' : 'projector-inactive'">
<mat-icon>videocam</mat-icon>
{{ (text || 'Project') | translate }}
</button>
</ng-container> </ng-container>

View File

@ -11,20 +11,6 @@
</div> </div>
</os-head-bar> </os-head-bar>
<!-- Select projector -->
<mat-card *ngIf="currentListOfSpeakers">
<h3 translate>
Manage the list of speakers for...
</h3>
<mat-form-field *ngIf="projectors && projectors.length > 0">
<mat-select [value]="projectors[0]" (selectionChange)="onSelectProjector($event)">
<mat-option *ngFor="let projector of projectors" [value]="projector">
{{ projector.name | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</mat-card>
<h1 class="title on-transition-fade" *ngIf="viewItem">{{ viewItem.contentObject.getTitle() }}</h1> <h1 class="title on-transition-fade" *ngIf="viewItem">{{ viewItem.contentObject.getTitle() }}</h1>
<mat-card class="speaker-card" *ngIf="viewItem"> <mat-card class="speaker-card" *ngIf="viewItem">
@ -131,10 +117,25 @@
</mat-card> </mat-card>
<mat-menu #speakerMenu="matMenu"> <mat-menu #speakerMenu="matMenu">
<os-projector-button
*ngIf="viewItem && projectors && projectors.length > 1"
[object]="getClosSlide()"
[menuItem]="true"
text="Current list of speakers (as slide)"
></os-projector-button>
<os-projector-button <os-projector-button
*ngIf="viewItem" *ngIf="viewItem"
[object]="viewItem.listOfSpeakersSlide" [object]="viewItem.listOfSpeakersSlide"
[menuItem]="true" [menuItem]="true"
text="List of speakers"
></os-projector-button>
<os-projector-button
*ngIf="viewItem"
[object]="viewItem.contentObject"
[menuItem]="true"
[text]="getContentObjectProjectorButtonText()"
></os-projector-button> ></os-projector-button>
<button mat-menu-item *ngIf="closedList" (click)="openSpeakerList()"> <button mat-menu-item *ngIf="closedList" (click)="openSpeakerList()">

View File

@ -1,7 +1,7 @@
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms'; import { FormGroup, FormControl } from '@angular/forms';
import { MatSnackBar, MatSelectChange } from '@angular/material'; import { MatSnackBar } from '@angular/material';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -21,6 +21,9 @@ import { UserRepositoryService } from 'app/core/repositories/users/user-reposito
import { DurationService } from 'app/core/ui-services/duration.service'; import { DurationService } from 'app/core/ui-services/duration.service';
import { CurrentAgendaItemService } from 'app/site/projector/services/current-agenda-item.service'; import { CurrentAgendaItemService } from 'app/site/projector/services/current-agenda-item.service';
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service'; import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
import { CollectionStringMapperService } from 'app/core/core-services/collectionStringMapper.service';
import { CurrentListOfSpeakersSlideService } from 'app/site/projector/services/current-list-of-of-speakers-slide.service';
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
/** /**
* The list of speakers for agenda items. * The list of speakers for agenda items.
@ -92,6 +95,13 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
return this.activeSpeaker ? false : true; return this.activeSpeaker ? false : true;
} }
/**
* Used to detect changes in the projector reference.
*/
private closReferenceProjectorId: number | null;
private closItemSubscription: Subscription | null;
/** /**
* Constructor for speaker list component. Generates the forms and subscribes * Constructor for speaker list component. Generates the forms and subscribes
* to the {@link currentListOfSpeakers} * to the {@link currentListOfSpeakers}
@ -121,7 +131,9 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
private promptService: PromptService, private promptService: PromptService,
private currentAgendaItemService: CurrentAgendaItemService, private currentAgendaItemService: CurrentAgendaItemService,
private durationService: DurationService, private durationService: DurationService,
private userRepository: UserRepositoryService private userRepository: UserRepositoryService,
private collectionStringMapper: CollectionStringMapperService,
private currentListOfSpeakersSlideService: CurrentListOfSpeakersSlideService
) { ) {
super(title, translate, snackBar); super(title, translate, snackBar);
this.isCurrentListOfSpeakers(); this.isCurrentListOfSpeakers();
@ -129,9 +141,10 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
if (this.currentListOfSpeakers) { if (this.currentListOfSpeakers) {
this.projectors = projectorRepo.getViewModelList(); this.projectors = projectorRepo.getViewModelList();
this.showClosOfProjector(this.projectors[0]); this.updateClosProjector();
projectorRepo.getViewModelListObservable().subscribe(newProjectors => { projectorRepo.getViewModelListObservable().subscribe(newProjectors => {
this.projectors = newProjectors; this.projectors = newProjectors;
this.updateClosProjector();
}); });
} else { } else {
this.getItemByUrl(); this.getItemByUrl();
@ -172,29 +185,26 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
} }
} }
/**
* Executed by selecting projectors
*
* @param event Holds the selected projector
*/
public onSelectProjector(event: MatSelectChange): void {
this.showClosOfProjector(event.value);
}
/** /**
* Shows the current list of speakers (CLOS) of a given projector. * Shows the current list of speakers (CLOS) of a given projector.
* Triggers after mat-select-change
*
* @param event Mat select change event, holds the projector in value
*/ */
private showClosOfProjector(projector: ViewProjector): void { private updateClosProjector(): void {
if (!this.projectors.length) {
return;
}
const referenceProjector = this.projectors[0].referenceProjector;
if (!referenceProjector || referenceProjector.id === this.closReferenceProjectorId) {
return;
}
this.closReferenceProjectorId = referenceProjector.id;
if (this.projectorSubscription) { if (this.projectorSubscription) {
this.projectorSubscription.unsubscribe(); this.projectorSubscription.unsubscribe();
this.viewItem = null; this.viewItem = null;
} }
this.projectorSubscription = this.currentAgendaItemService this.projectorSubscription = this.currentAgendaItemService
.getAgendaItemObservable(projector) .getAgendaItemObservable(referenceProjector)
.subscribe(item => { .subscribe(item => {
if (item) { if (item) {
this.setSpeakerList(item.id); this.setSpeakerList(item.id);
@ -202,6 +212,13 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
}); });
} }
/**
* @returns the CLOS slide build descriptor
*/
public getClosSlide(): ProjectorElementBuildDeskriptor {
return this.currentListOfSpeakersSlideService.getSlide(false);
}
/** /**
* Extract the ID from the url * Extract the ID from the url
* Determine whether the speaker list belongs to a motion or a topic * Determine whether the speaker list belongs to a motion or a topic
@ -217,7 +234,11 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
* @param item the item to use as List of Speakers * @param item the item to use as List of Speakers
*/ */
private setSpeakerList(id: number): void { private setSpeakerList(id: number): void {
this.itemRepo.getViewModelObservable(id).subscribe(newAgendaItem => { if (this.closItemSubscription) {
this.closItemSubscription.unsubscribe();
}
this.closItemSubscription = this.itemRepo.getViewModelObservable(id).subscribe(newAgendaItem => {
if (newAgendaItem) { if (newAgendaItem) {
this.viewItem = newAgendaItem; this.viewItem = newAgendaItem;
const allSpeakers = this.repo.createSpeakerList(newAgendaItem.item); const allSpeakers = this.repo.createSpeakerList(newAgendaItem.item);
@ -228,6 +249,17 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
}); });
} }
/**
* @returns the verbose name of the model of the content object from viewItem.
* If a motion is the current content object, "Motion" will be the returned value.
*/
public getContentObjectProjectorButtonText(): string {
const verboseName = this.collectionStringMapper
.getRepository(this.viewItem.item.content_object.collection)
.getVerboseName();
return verboseName;
}
/** /**
* Create a speaker out of an id * Create a speaker out of an id
* *

View File

@ -14,6 +14,19 @@
</div> </div>
</os-head-bar> </os-head-bar>
<mat-card *ngIf="!projectorToCreate && projectors">
<span translate>
Reference projector for current list of speakers:
</span>
<mat-form-field>
<mat-select [disabled]="!!editId" [value]="projectors[0].reference_projector_id" (selectionChange)="onSelectReferenceProjector($event)">
<mat-option *ngFor="let projector of projectors" [value]="projector.id">
{{ projector.getTitle() | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</mat-card>
<mat-card *ngIf="projectorToCreate"> <mat-card *ngIf="projectorToCreate">
<mat-card-title translate>New Projector</mat-card-title> <mat-card-title translate>New Projector</mat-card-title>
<mat-card-content> <mat-card-content>
@ -74,19 +87,6 @@
</mat-hint> </mat-hint>
</mat-form-field> </mat-form-field>
<!-- Reference projector for the current list of speakers -->
<h3 translate>Current list of speakers reference</h3>
<mat-form-field>
<mat-select formControlName="reference_projector_id" placeholder="{{ 'Reference projector' | translate }}">
<mat-option [value]="projector.id">
<span translate>self</span>
</mat-option>
<mat-option *ngFor="let refProjector of getReferenceProjectorsFor(projector)" [value]="refProjector.id">
<span>{{ refProjector.getTitle() | translate }}</span>
</mat-option>
</mat-select>
</mat-form-field>
<h3 translate>Resolution and size</h3> <h3 translate>Resolution and size</h3>
<!-- Aspect ratio field --> <!-- Aspect ratio field -->
<mat-radio-group formControlName="aspectRatio" [name]="projector.id"> <mat-radio-group formControlName="aspectRatio" [name]="projector.id">

View File

@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { MatSnackBar } from '@angular/material'; import { MatSnackBar, MatSelectChange } from '@angular/material';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -105,7 +105,6 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit
aspectRatio: ['', Validators.required], aspectRatio: ['', Validators.required],
width: [0, Validators.required], width: [0, Validators.required],
clock: [true], clock: [true],
reference_projector_id: [],
background_color: ['', Validators.required], background_color: ['', Validators.required],
header_background_color: ['', Validators.required], header_background_color: ['', Validators.required],
header_font_color: ['', Validators.required], header_font_color: ['', Validators.required],
@ -140,11 +139,12 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit
public create(): void { public create(): void {
if (this.createForm.valid && this.projectorToCreate) { if (this.createForm.valid && this.projectorToCreate) {
this.projectorToCreate.patchValues(this.createForm.value as Projector); this.projectorToCreate.patchValues(this.createForm.value as Projector);
// TODO: the server shouldn't want to have this data.. // TODO: the server shouldn't want to have element data..
this.projectorToCreate.patchValues({ this.projectorToCreate.patchValues({
elements: [{ name: 'core/clock', stable: true }], elements: [{ name: 'core/clock', stable: true }],
elements_preview: [], elements_preview: [],
elements_history: [] elements_history: [],
reference_projector_id: this.projectors[0].reference_projector_id
}); });
this.repo.create(this.projectorToCreate).then(() => (this.projectorToCreate = null), this.raiseError); this.repo.create(this.projectorToCreate).then(() => (this.projectorToCreate = null), this.raiseError);
} }
@ -206,25 +206,11 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit
this.editId = projector.id; this.editId = projector.id;
this.updateForm.reset(); 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),
reference_projector_id: reference_projector_id,
background_color: projector.background_color,
show_header_footer: projector.show_header_footer,
show_title: projector.show_title,
show_logo: projector.show_logo
});*/
this.updateForm.patchValue(projector.projector); this.updateForm.patchValue(projector.projector);
this.updateForm.patchValue({ this.updateForm.patchValue({
name: this.translate.instant(projector.name),
aspectRatio: this.getAspectRatioKey(projector), aspectRatio: this.getAspectRatioKey(projector),
clock: this.clockSlideService.isProjectedOn(projector), clock: this.clockSlideService.isProjectedOn(projector)
reference_projector_id: reference_projector_id
}); });
} }
@ -249,16 +235,6 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit
return; return;
} }
const updateProjector: Partial<Projector> = this.updateForm.value; const updateProjector: Partial<Projector> = this.updateForm.value;
/*const updateProjector: Partial<Projector> = {
name: this.updateForm.value.name,
width: this.updateForm.value.width,
height: Math.round(this.updateForm.value.width / aspectRatios[this.updateForm.value.aspectRatio]),
reference_projector_id: this.updateForm.value.reference_projector_id,
background_color: this.updateForm.value.background_color,
show_header_footer: this.updateForm.value.show_header_footer,
show_title: this.updateForm.value.show_title,
show_logo: this.updateForm.value.show_logo
};*/
updateProjector.height = Math.round( updateProjector.height = Math.round(
this.updateForm.value.width / aspectRatios[this.updateForm.value.aspectRatio] this.updateForm.value.width / aspectRatios[this.updateForm.value.aspectRatio]
); );
@ -284,13 +260,13 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit
} }
} }
/** public onSelectReferenceProjector(change: MatSelectChange): void {
* Get all available reference projectors for the given projector. These const update: Partial<Projector> = {
* projectors are all existing projectors exluding the given projector reference_projector_id: change.value
* };
* @returns all available reference projectors const promises = this.projectors.map(projector => {
*/ return this.repo.update(update, projector);
public getReferenceProjectorsFor(projector: ViewProjector): ViewProjector[] { });
return this.repo.getViewModelList().filter(p => p.id !== projector.id); Promise.all(promises).then(null, this.raiseError);
} }
} }

View File

@ -5,11 +5,20 @@ export class ViewProjector extends BaseViewModel {
public static COLLECTIONSTRING = Projector.COLLECTIONSTRING; public static COLLECTIONSTRING = Projector.COLLECTIONSTRING;
private _projector: Projector; private _projector: Projector;
private _referenceProjector: ViewProjector;
public get projector(): Projector { public get projector(): Projector {
return this._projector; return this._projector;
} }
public get referenceProjector(): ViewProjector {
if (!this.reference_projector_id) {
return this;
} else {
return this._referenceProjector;
}
}
public get id(): number { public get id(): number {
return this.projector.id; return this.projector.id;
} }
@ -87,14 +96,19 @@ export class ViewProjector extends BaseViewModel {
*/ */
public getVerboseName; public getVerboseName;
public constructor(projector?: Projector) { public constructor(projector: Projector, referenceProjector?: ViewProjector) {
super(Projector.COLLECTIONSTRING); super(Projector.COLLECTIONSTRING);
this._projector = projector; this._projector = projector;
this._referenceProjector = referenceProjector;
} }
public getTitle = () => { public getTitle = () => {
return this.name; return this.name;
}; };
public updateDependencies(update: BaseViewModel): void {} public updateDependencies(update: BaseViewModel): void {
if (update instanceof ViewProjector && this.reference_projector_id === update.id) {
this._referenceProjector = update;
}
}
} }

View File

@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
import { ProjectorService } from 'app/core/core-services/projector.service'; import { ProjectorService } from 'app/core/core-services/projector.service';
import { ViewProjector } from '../models/view-projector'; import { ViewProjector } from '../models/view-projector';
import { IdentifiableProjectorElement } from 'app/shared/models/core/projector'; import { IdentifiableProjectorElement } from 'app/shared/models/core/projector';
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
/** /**
* Handles the curent list of speakers slide. Manages the projection and provides * Handles the curent list of speakers slide. Manages the projection and provides
@ -29,6 +30,18 @@ export class CurrentListOfSpeakersSlideService {
}; };
} }
/**
* @returns the slide build descriptor for the overlay or slide
*/
public getSlide(overlay: boolean): ProjectorElementBuildDeskriptor {
return {
getBasicProjectorElement: options => this.getCurrentListOfSpeakersProjectorElement(overlay),
slideOptions: [],
projectionDefaultName: 'agenda_current_list_of_speakers',
getDialogTitle: () => 'Current list of speakers'
};
}
/** /**
* Queries, if the slide/overlay is projected on the given projector. * Queries, if the slide/overlay is projected on the given projector.
* *

View File

@ -0,0 +1,34 @@
# Generated by Finn Stutzenstein on 2019-03-01 10:02
from django.db import migrations
def set_reference_projector(apps, schema_editor):
"""
Sets all references to one projector. Tries to get the id from the former
config value. If there is no config or the id is invalid, the first projector
will be taken
"""
ConfigStore = apps.get_model("core", "ConfigStore")
Projector = apps.get_model("core", "Projector")
try:
config = ConfigStore.objects.get(
key="projector_currentListOfSpeakers_reference"
)
reference_id = config.value
config.delete() # cleanup. this config is not needed anymore
reference_projector = Projector.objects.get(pk=reference_id)
except (ConfigStore.DoesNotExist, Projector.DoesNotExist):
reference_projector = Projector.objects.first()
for projector in Projector.objects.all():
projector.reference_projector = reference_projector
projector.save(skip_autoupdate=True)
class Migration(migrations.Migration):
dependencies = [("core", "0019_countdown_title_2")]
operations = [migrations.RunPython(set_reference_projector)]