Merge pull request #4221 from FinnStutzenstein/client-projector

projector cleanup
This commit is contained in:
Sean 2019-01-31 19:03:13 +01:00 committed by GitHub
commit c00b1a8325
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 194 additions and 91 deletions

View File

@ -39,41 +39,11 @@ export class ProjectorService extends OpenSlidesComponent {
} }
/** /**
* Checks, if a given object is projected. * Retusn the identifiable projector element from the given types of slides/elements/descriptors
* *
* @param obj The object in question * @param obj Something related to IdentifiableProjectorElement
* @returns true, if the object is projected on one projector. * @returns the identifiable projector element from obj.
*/ */
public isProjected(obj: Projectable | ProjectorElementBuildDeskriptor): boolean {
if (isProjectable(obj)) {
return this.DS.getAll<Projector>('core/projector').some(projector => {
return projector.isElementShown(obj.getSlide().getBasicProjectorElement());
});
} else {
return this.DS.getAll<Projector>('core/projector').some(projector => {
return projector.isElementShown(obj.getBasicProjectorElement());
});
}
}
/**
* Get all projectors where the object is prejected on.
*
* @param obj The object in question
* @return All projectors, where this Object is projected on
*/
public getProjectorsWhichAreProjecting(obj: Projectable | ProjectorElementBuildDeskriptor): Projector[] {
if (isProjectable(obj)) {
return this.DS.getAll<Projector>('core/projector').filter(projector => {
return projector.isElementShown(obj.getSlide().getBasicProjectorElement());
});
} else {
return this.DS.getAll<Projector>('core/projector').filter(projector => {
return projector.isElementShown(obj.getBasicProjectorElement());
});
}
}
private getProjectorElement( private getProjectorElement(
obj: Projectable | ProjectorElementBuildDeskriptor | IdentifiableProjectorElement obj: Projectable | ProjectorElementBuildDeskriptor | IdentifiableProjectorElement
): IdentifiableProjectorElement { ): IdentifiableProjectorElement {
@ -86,6 +56,34 @@ export class ProjectorService extends OpenSlidesComponent {
} }
} }
/**
* Checks, if a given object is projected.
*
* @param obj The object in question
* @returns true, if the object is projected on one projector.
*/
public isProjected(obj: Projectable | ProjectorElementBuildDeskriptor | IdentifiableProjectorElement): boolean {
const element = this.getProjectorElement(obj);
return this.DS.getAll<Projector>('core/projector').some(projector => {
return projector.isElementShown(element);
});
}
/**
* Get all projectors where the object is prejected on.
*
* @param obj The object in question
* @return All projectors, where this Object is projected on
*/
public getProjectorsWhichAreProjecting(
obj: Projectable | ProjectorElementBuildDeskriptor | IdentifiableProjectorElement
): Projector[] {
const element = this.getProjectorElement(obj);
return this.DS.getAll<Projector>('core/projector').filter(projector => {
return projector.isElementShown(element);
});
}
/** /**
* Checks, if the object is projected on the given projector. * Checks, if the object is projected on the given projector.
* *
@ -101,12 +99,8 @@ export class ProjectorService extends OpenSlidesComponent {
} }
/** /**
* Projects the given ProjectorElement on the given projectors. * Projects the given ProjectorElement on the given projectors. Removes the element
* * from all non-given projectors
* TODO: this does not care about the element being stable. Some more logic must be added later.
*
* On the given projectors: Delete all non-stable elements and add the given element.
* On all other projectors: If the element (compared with name and id) is there, it will be deleted.
* *
* @param projectors All projectors where to add the element. * @param projectors All projectors where to add the element.
* @param element The element in question. * @param element The element in question.
@ -121,6 +115,13 @@ export class ProjectorService extends OpenSlidesComponent {
}); });
} }
/**
* Projcets the given object on the projector. If the object is non-stable, all other non-stable
* elements will be removed and added to the history.
*
* @param projector The projector to add the object to.
* @param obj The object to project
*/
public async projectOn( public async projectOn(
projector: Projector, projector: Projector,
obj: Projectable | ProjectorElementBuildDeskriptor | IdentifiableProjectorElement obj: Projectable | ProjectorElementBuildDeskriptor | IdentifiableProjectorElement
@ -147,26 +148,40 @@ export class ProjectorService extends OpenSlidesComponent {
} }
} }
/**
* Removes the given object from the projector. Non stable elements will be added to the history.
*
* @param projector The projector
* @param obj the object to unproject
*/
public async removeFrom( public async removeFrom(
projector: Projector, projector: Projector,
obj: Projectable | ProjectorElementBuildDeskriptor | IdentifiableProjectorElement obj: Projectable | ProjectorElementBuildDeskriptor | IdentifiableProjectorElement
): Promise<void> { ): Promise<void> {
const element = this.getProjectorElement(obj); const element = this.getProjectorElement(obj);
if (element.stable) { const removedElements = projector.removeElements(element);
// Just remove this stable element if (removedElements.length > 0) {
projector.removeElements(element); if (element.stable) {
await this.projectRequest(projector, projector.elements); await this.projectRequest(projector, projector.elements);
} else { } else {
// For non-stable elements remove all current non-stable elements and add them to the history // For non-stable elements: Add removed elements to the history.
const removedElements = projector.removeElements(element);
if (removedElements.length > 0) {
console.log(projector.elements, removedElements);
await this.projectRequest(projector, projector.elements, null, removedElements); await this.projectRequest(projector, projector.elements, null, removedElements);
} }
} }
} }
/**
* Executes the request to change projector elements.
*
* Note: Just one of `appendToHistory` and `deleteLastHistoryElement` can be given.
*
* @param projector The affected projector
* @param elements (optional) Elements to set.
* @param preview (optional) preview to set
* @param appendToHistory (optional) Elements to be appended to the history
* @param deleteLastHistroyElement (optional) If given, the last history element will be removed.
*/
private async projectRequest( private async projectRequest(
projector: Projector, projector: Projector,
elements?: ProjectorElements, elements?: ProjectorElements,
@ -206,21 +221,41 @@ export class ProjectorService extends OpenSlidesComponent {
}); });
} }
/**
* Returns a model associated with the identifiable projector element. Throws an error,
* if the element is not mappable.
*
* @param element The projector element
* @returns the model from the projector element
*/
public getModelFromProjectorElement<T extends BaseModel>(element: IdentifiableProjectorElement): T { public getModelFromProjectorElement<T extends BaseModel>(element: IdentifiableProjectorElement): T {
if (!this.slideManager.canSlideBeMappedToModel(element.name)) { if (!this.slideManager.canSlideBeMappedToModel(element.name)) {
throw new Error('THis projectorelement cannot be mapped to a model'); throw new Error('This projector element cannot be mapped to a model');
} }
const identifiers = element.getIdentifiers(); const identifiers = element.getIdentifiers();
if (!identifiers.includes('name') || !identifiers.includes('name')) { if (!identifiers.includes('name') || !identifiers.includes('id')) {
throw new Error('To map this element to a model, a name and id is needed.'); throw new Error('To map this element to a model, a name and id is needed.');
} }
return this.DS.get<T>(element.name, element.id); return this.DS.get<T>(element.name, element.id);
} }
/**
* Projects the next slide in the queue. Moves all currently projected
* non-stable slides to the history.
*
* @param projector The projector
*/
public async projectNextSlide(projector: Projector): Promise<void> { public async projectNextSlide(projector: Projector): Promise<void> {
await this.projectPreviewSlide(projector, 0); await this.projectPreviewSlide(projector, 0);
} }
/**
* Projects one slide (given by the index of the preview) on the given projector. Moves
* all current projected non-stable elements to the history.
*
* @param projector The projector
* @param previewIndex The index in the `elements_preview` array.
*/
public async projectPreviewSlide(projector: Projector, previewIndex: number): Promise<void> { public async projectPreviewSlide(projector: Projector, previewIndex: number): Promise<void> {
if (projector.elements_preview.length === 0 || previewIndex >= projector.elements_preview.length) { if (projector.elements_preview.length === 0 || previewIndex >= projector.elements_preview.length) {
return; return;
@ -231,6 +266,11 @@ export class ProjectorService extends OpenSlidesComponent {
await this.projectRequest(projector, projector.elements, projector.elements_preview, removedElements); await this.projectRequest(projector, projector.elements, projector.elements_preview, removedElements);
} }
/**
* Projects the last slide of the history. This slide will be removed from the history.
*
* @param projector The projector
*/
public async projectPreviousSlide(projector: Projector): Promise<void> { public async projectPreviousSlide(projector: Projector): Promise<void> {
if (projector.elements_history.length === 0) { if (projector.elements_history.length === 0) {
return; return;
@ -253,10 +293,21 @@ export class ProjectorService extends OpenSlidesComponent {
await this.projectRequest(projector, projector.elements, projector.elements_preview, null, true); await this.projectRequest(projector, projector.elements, projector.elements_preview, null, true);
} }
/**
* Saves the preview of the projector
*
* @param projector The projector to save the preview.
*/
public async savePreview(projector: Projector): Promise<void> { public async savePreview(projector: Projector): Promise<void> {
await this.projectRequest(projector, null, projector.elements_preview); await this.projectRequest(projector, null, projector.elements_preview);
} }
/**
* Appends the given element to the preview.
*
* @param projector The projector
* @param element The element to add to the preview.
*/
public async addElementToPreview(projector: Projector, element: ProjectorElement): Promise<void> { public async addElementToPreview(projector: Projector, element: ProjectorElement): Promise<void> {
projector.elements_preview.push(element); projector.elements_preview.push(element);
await this.projectRequest(projector, null, projector.elements_preview); await this.projectRequest(projector, null, projector.elements_preview);

View File

@ -5,6 +5,13 @@ import { HttpService } from './http.service';
import { environment } from 'environments/environment.prod'; import { environment } from 'environments/environment.prod';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
/**
* This service provides the timeoffset to the server and a user of this service
* can query the servertime.
*
* This service needs to be started with `startScheduler` which will update
* the servertime frequently.
*/
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
@ -13,7 +20,7 @@ export class ServertimeService extends OpenSlidesComponent {
private static NORMAL_TIMEOUT = 60 * 5; private static NORMAL_TIMEOUT = 60 * 5;
/** /**
* In milliseconds * The server offset in milliseconds
*/ */
private serverOffsetSubject = new BehaviorSubject<number>(0); private serverOffsetSubject = new BehaviorSubject<number>(0);
@ -21,27 +28,40 @@ export class ServertimeService extends OpenSlidesComponent {
super(); super();
} }
/**
* Starts the scheduler to sync with the server.
*/
public startScheduler(): void { public startScheduler(): void {
this.scheduleNextRefresh(0); this.scheduleNextRefresh(0);
} }
/**
* Get an observable for the server offset.
*/
public getServerOffsetObservable(): Observable<number> { public getServerOffsetObservable(): Observable<number> {
return this.serverOffsetSubject.asObservable(); return this.serverOffsetSubject.asObservable();
} }
/**
* Schedules the next sync with the server.
*
* @param seconds The timeout in seconds to the refresh.
*/
private scheduleNextRefresh(seconds: number): void { private scheduleNextRefresh(seconds: number): void {
setTimeout(async () => { setTimeout(async () => {
let timeout = ServertimeService.NORMAL_TIMEOUT; let timeout = ServertimeService.NORMAL_TIMEOUT;
try { try {
await this.refreshServertime(); await this.refreshServertime();
} catch (e) { } catch (e) {
console.log(e);
timeout = ServertimeService.FAILURE_TIMEOUT; timeout = ServertimeService.FAILURE_TIMEOUT;
} }
this.scheduleNextRefresh(timeout); this.scheduleNextRefresh(timeout);
}, 1000 * seconds); }, 1000 * seconds);
} }
/**
* Queries the servertime and calculates the offset.
*/
private async refreshServertime(): Promise<void> { private async refreshServertime(): Promise<void> {
// servertime is the time in seconds. // servertime is the time in seconds.
const servertime = await this.http.get<number>(environment.urlPrefix + '/core/servertime/'); const servertime = await this.http.get<number>(environment.urlPrefix + '/core/servertime/');
@ -52,6 +72,9 @@ export class ServertimeService extends OpenSlidesComponent {
this.serverOffsetSubject.next(Math.floor(Date.now() - servertime * 1000)); this.serverOffsetSubject.next(Math.floor(Date.now() - servertime * 1000));
} }
/**
* Calculate the time of the server.
*/
public getServertime(): number { public getServertime(): number {
return Date.now() - this.serverOffsetSubject.getValue(); return Date.now() - this.serverOffsetSubject.getValue();
} }

View File

@ -1,10 +1,19 @@
import { Component, OnInit, Input } from '@angular/core'; import { Component, OnInit, Input } from '@angular/core';
import { Projectable, ProjectorElementBuildDeskriptor } from 'app/site/base/projectable'; import {
Projectable,
ProjectorElementBuildDeskriptor,
isProjectable,
isProjectorElementBuildDeskriptor
} from 'app/site/base/projectable';
import { ProjectionDialogService } from 'app/core/services/projection-dialog.service'; import { ProjectionDialogService } from 'app/core/services/projection-dialog.service';
import { ProjectorService } from '../../../core/services/projector.service'; import { ProjectorService } from '../../../core/services/projector.service';
/** /**
* The projector button to project something on the projector.
*
* Use the input [object] to specify the object to project. It can either be
* a Projectable or a ProjectorElementBuildDeskriptor
*/ */
@Component({ @Component({
selector: 'os-projector-button', selector: 'os-projector-button',
@ -12,11 +21,29 @@ import { ProjectorService } from '../../../core/services/projector.service';
styleUrls: ['./projector-button.component.scss'] styleUrls: ['./projector-button.component.scss']
}) })
export class ProjectorButtonComponent implements OnInit { export class ProjectorButtonComponent implements OnInit {
/**
* The object to project.
*/
private _object: Projectable | ProjectorElementBuildDeskriptor;
public get object(): Projectable | ProjectorElementBuildDeskriptor {
return this._object;
}
@Input() @Input()
public object: Projectable | ProjectorElementBuildDeskriptor; public set object(obj: Projectable | ProjectorElementBuildDeskriptor) {
if (isProjectable(obj) || isProjectorElementBuildDeskriptor(obj)) {
this._object = obj;
} else {
console.error(
'Your model for the projectorbutton is not projectable and not' + 'a projectorElementBuildDescriptor!',
obj
);
}
}
/** /**
* The consotructor * The constructor
*/ */
public constructor( public constructor(
private projectionDialogService: ProjectionDialogService, private projectionDialogService: ProjectionDialogService,
@ -28,6 +55,11 @@ export class ProjectorButtonComponent implements OnInit {
*/ */
public ngOnInit(): void {} public ngOnInit(): void {}
/**
* Click on the projector button
*
* @param event the click event
*/
public onClick(event: Event): void { public onClick(event: Event): void {
event.stopPropagation(); event.stopPropagation();
this.projectionDialogService.openProjectDialogFor(this.object); this.projectionDialogService.openProjectDialogFor(this.object);
@ -39,10 +71,9 @@ export class ProjectorButtonComponent implements OnInit {
* @returns true, if the object is projected on one projector. * @returns true, if the object is projected on one projector.
*/ */
public isProjected(): boolean { public isProjected(): boolean {
if (this.object) { if (!this.object) {
return this.projectorService.isProjected(this.object);
} else {
return false; return false;
} }
return this.projectorService.isProjected(this.object);
} }
} }

View File

@ -84,8 +84,12 @@ export class Projector extends BaseModel<Projector> {
* @returns all removed unstable elements * @returns all removed unstable elements
*/ */
public removeAllNonStableElements(): ProjectorElements { public removeAllNonStableElements(): ProjectorElements {
const unstableElements = this.elements.filter(element => !element.stable); let unstableElements: ProjectorElements;
this.elements = this.elements.filter(element => element.stable); let stableElements: ProjectorElements;
[unstableElements, stableElements] = this.partitionArray(this.elements, element => !element.stable);
this.elements = stableElements;
return unstableElements; return unstableElements;
} }
@ -99,8 +103,11 @@ export class Projector extends BaseModel<Projector> {
} }
/** /**
* Must match everything. If a projectorelement does not have all keys * Removes and returns all projector elements, witch can be identified with the
* to identify, it will be removed, if all existing keys match * given element.
*
* @param element The element to remove
* @returns all removed projector elements
*/ */
public removeElements(element: IdentifiableProjectorElement): ProjectorElements { public removeElements(element: IdentifiableProjectorElement): ProjectorElements {
let removedElements: ProjectorElements; let removedElements: ProjectorElements;
@ -114,6 +121,14 @@ export class Projector extends BaseModel<Projector> {
return removedElements; return removedElements;
} }
/**
* Splits up the array into two arrays. All elements with a true return value from the callback
* will be in the fist array, all others in the second one.
*
* @param array The array to split
* @param callback To evaluate every entry
* @returns the splitted array
*/
private partitionArray<T>(array: T[], callback: (element: T) => boolean): [T[], T[]] { private partitionArray<T>(array: T[], callback: (element: T) => boolean): [T[], T[]] {
return array.reduce( return array.reduce(
(result, element) => { (result, element) => {

View File

@ -69,7 +69,7 @@ export class CountdownListComponent extends BaseViewComponent implements OnInit
} }
/** /**
* Add a new Section. * Add a new countdown.
*/ */
public onPlusButton(): void { public onPlusButton(): void {
if (!this.countdownToCreate) { if (!this.countdownToCreate) {

View File

@ -7,7 +7,7 @@ import { BaseViewComponent } from '../../../base/base-view';
import { MatSnackBar } from '@angular/material'; import { MatSnackBar } from '@angular/material';
/** /**
* List view for the statute paragraphs. * List view for the projector messages.
*/ */
@Component({ @Component({
selector: 'os-projectormessage-list', selector: 'os-projectormessage-list',
@ -22,7 +22,7 @@ export class ProjectorMessageListComponent extends BaseViewComponent implements
/** /**
* Init function. * Init function.
* *
* Sets the title and gets/observes countdowns from DataStore * Sets the title and gets/observes messages from DataStore
*/ */
public ngOnInit(): void { public ngOnInit(): void {
super.setTitle('Messages'); super.setTitle('Messages');

View File

@ -15,7 +15,7 @@ export class CurrentListOfSpeakersSlideService {
return { return {
name: overlay ? 'agenda/current-list-of-speakers-overlay' : 'agenda/current-list-of-speakers', name: overlay ? 'agenda/current-list-of-speakers-overlay' : 'agenda/current-list-of-speakers',
stable: overlay, stable: overlay,
getIdentifiers: () => ['name', 'stable'] getIdentifiers: () => ['name']
}; };
} }

View File

@ -46,7 +46,7 @@ export class ProjectorDataService {
Object.keys(update).forEach(_id => { Object.keys(update).forEach(_id => {
const id = parseInt(_id, 10); const id = parseInt(_id, 10);
if ((<{ error: string }>update[id]).error !== undefined) { if ((<{ error: string }>update[id]).error !== undefined) {
console.log('TODO: Why does the server sends errors on autpupdates?'); console.log('TODO: Why does the server sends errors on autoupdates?');
} else { } else {
if (this.currentProjectorData[id]) { if (this.currentProjectorData[id]) {
this.currentProjectorData[id].next(update[id] as ProjectorData); this.currentProjectorData[id].next(update[id] as ProjectorData);

View File

@ -1,9 +1,7 @@
import { Component, OnInit, Input } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { BaseSlideComponent } from 'app/slides/base-slide-component'; 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'; import { AgendaCurrentListOfSpeakersSlideData } from '../base/agenda-current-list-of-speakers-slide-data';
import { SlideData } from 'app/site/projector/services/projector-data.service';
@Component({ @Component({
selector: 'os-agenda-current-list-of-speakers-slide', selector: 'os-agenda-current-list-of-speakers-slide',
@ -12,23 +10,8 @@ import { SlideData } from 'app/site/projector/services/projector-data.service';
}) })
export class AgendaCurrentListOfSpeakersSlideComponent extends BaseSlideComponent<AgendaCurrentListOfSpeakersSlideData> export class AgendaCurrentListOfSpeakersSlideComponent extends BaseSlideComponent<AgendaCurrentListOfSpeakersSlideData>
implements OnInit { implements OnInit {
private _data: SlideData<AgendaCurrentListOfSpeakersSlideData>; public constructor() {
public isOverlay: boolean;
public get data(): SlideData<AgendaCurrentListOfSpeakersSlideData> {
return this._data;
}
@Input()
public set data(value: SlideData<AgendaCurrentListOfSpeakersSlideData>) {
this.isOverlay = !value || value.element.stable;
this._data = value;
}
public constructor(private http: HttpService) {
super(); super();
console.log(this.http);
} }
public ngOnInit(): void { public ngOnInit(): void {

View File

@ -32,7 +32,7 @@ export class CoreClockSlideComponent extends BaseSlideComponent<{}> implements O
const hours = '0' + time.getHours(); const hours = '0' + time.getHours();
const minutes = '0' + time.getMinutes(); const minutes = '0' + time.getMinutes();
// Will display time in 10:30:23 format // Will display time in hh:mm format
this.time = hours.slice(-2) + ':' + minutes.slice(-2); this.time = hours.slice(-2) + ':' + minutes.slice(-2);
} }

View File

@ -24,7 +24,8 @@
<div *ngIf="!data.data.is_child" [innerHTML]="data.data.text"></div> <div *ngIf="!data.data.is_child" [innerHTML]="data.data.text"></div>
<!-- Amendment text --> <!-- Amendment text -->
<div *ngIf="data.data.is_child" [innerHTML]="data.data.amendment_paragraphs[0]"></div> <div *ngIf="data.data.is_child && data.data.amendment_paragraphs"
[innerHTML]="data.data.amendment_paragraphs[0]"></div>
<!-- Reason --> <!-- Reason -->
<div *ngIf="data.data.reason"> <div *ngIf="data.data.reason">

View File

@ -23,15 +23,14 @@ def user_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
user_id = element.get("id") user_id = element.get("id")
if user_id is None: if user_id is None:
return {"error": "id is required for user slide"} raise ProjectorElementException("id is required for user slide")
try: try:
user = all_data["users/user"][user_id] user = all_data["users/user"][user_id]
except KeyError: except KeyError:
raise ProjectorElementException(f"user with id {user_id} does not exist") raise ProjectorElementException(f"user with id {user_id} does not exist")
return_value = {"user": get_user_name(all_data, user["id"])} return {"user": get_user_name(all_data, user["id"])}
return return_value
def get_user_name(all_data: AllData, user_id: int) -> str: def get_user_name(all_data: AllData, user_id: int) -> str: