Merge pull request #4227 from FinnStutzenstein/clos

Projector reference for CLOS
This commit is contained in:
Finn Stutzenstein 2019-02-01 10:10:47 +01:00 committed by GitHub
commit 44851af172
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 177 additions and 40 deletions

View File

@ -56,6 +56,7 @@ export class Projector extends BaseModel<Projector> {
public name: string;
public width: number;
public height: number;
public reference_projector_id: number;
public projectiondefaults: ProjectionDefault[];
public constructor(input?: any) {

View File

@ -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<V extends BaseViewModel, M extends BaseMode
*/
protected readonly viewModelListSubject: BehaviorSubject<V[]> = new BehaviorSubject<V[]>([]);
/**
* Observable subject for any changes of view models.
*/
protected readonly generalViewModelSubject: Subject<V> = new Subject<V>();
/**
* Construction routine for the base repository
*
@ -149,6 +154,13 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
return this.viewModelListSubject.asObservable().pipe(auditTime(1));
}
/**
* This observable fires every time an object is changed in the repository.
*/
public getGeneralViewModelObservable(): Observable<V> {
return this.generalViewModelSubject.asObservable();
}
/**
* Updates the ViewModel observable using a ViewModel corresponding to the id
*/
@ -156,6 +168,7 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
if (this.viewModelSubjects[id]) {
this.viewModelSubjects[id].next(this.viewModelStore[id]);
}
this.generalViewModelSubject.next(this.viewModelStore[id]);
}
/**

View File

@ -73,6 +73,20 @@
<span translate>Required</span>
</mat-hint>
</mat-form-field>
<!-- Reference projector for the current list of speakers -->
<h3 translate>Current list fo 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>
<!-- Aspect ratio field -->
<mat-radio-group formControlName="aspectRatio" [name]="projector.id">
@ -82,6 +96,8 @@
</mat-radio-group>
<mat-slider [thumbLabel]="true" formControlName="width" min="800" max="3840" step="10"></mat-slider>
{{ updateForm.value.width }}
<!-- Clock -->
<div>
<mat-checkbox formControlName="clock">
<span translate>Show clock</span>

View File

@ -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<Projector> = {
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);
}
}

View File

@ -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;

View File

@ -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<number | null> } = {};
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<number | null> {
if (!this.currentItemIds[projector.id]) {
const itemId = this.getCurrentAgendaItemIdForProjector(projector);
this.currentItemIds[projector.id] = new BehaviorSubject<number | null>(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 (<any>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<void> {
const isClosProjected = this.isProjectedOn(projector, overlay);
if (isClosProjected) {

View File

@ -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<AgendaCurrentListOfSpeakersSlideData>
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');
}
}

View File

@ -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 {}
}

View File

@ -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<CoreCountdownSlideData> implements OnInit {
public constructor(private http: HttpService) {
export class CoreCountdownSlideComponent extends BaseSlideComponent<CoreCountdownSlideData> {
public constructor() {
super();
console.log(this.http);
}
public ngOnInit(): void {
console.log('Hello from countdown slide');
}
}

View File

@ -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<MotionsMotionSlideData> implements OnInit {
export class MotionsMotionSlideComponent extends BaseSlideComponent<MotionsMotionSlideData> {
public constructor() {
super();
}
public ngOnInit(): void {
console.log('Hello from motion slide');
}
}

View File

@ -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<UsersUserSlideData> implements OnInit {
export class UsersUserSlideComponent extends BaseSlideComponent<UsersUserSlideData> {
public constructor() {
super();
}
public ngOnInit(): void {
console.log('Hello from user slide');
}
}

View File

@ -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",
),
)
]

View File

@ -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.

View File

@ -97,6 +97,7 @@ class ProjectorSerializer(ModelSerializer):
"name",
"width",
"height",
"reference_projector",
"projectiondefaults",
)
read_only_fields = ("scale", "scroll")