Merge pull request #4344 from FinnStutzenstein/countdowns
Countdown slide and controls
This commit is contained in:
commit
9e794c4669
@ -52,9 +52,9 @@ export class ProjectorService {
|
|||||||
obj: Projectable | ProjectorElementBuildDeskriptor | IdentifiableProjectorElement
|
obj: Projectable | ProjectorElementBuildDeskriptor | IdentifiableProjectorElement
|
||||||
): IdentifiableProjectorElement {
|
): IdentifiableProjectorElement {
|
||||||
if (isProjectable(obj)) {
|
if (isProjectable(obj)) {
|
||||||
return obj.getSlide().getBasicProjectorElement();
|
return obj.getSlide().getBasicProjectorElement({});
|
||||||
} else if (isProjectorElementBuildDeskriptor(obj)) {
|
} else if (isProjectorElementBuildDeskriptor(obj)) {
|
||||||
return obj.getBasicProjectorElement();
|
return obj.getBasicProjectorElement({});
|
||||||
} else {
|
} else {
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@ -133,7 +133,9 @@ export class ProjectorService {
|
|||||||
const element = this.getProjectorElement(obj);
|
const element = this.getProjectorElement(obj);
|
||||||
|
|
||||||
if (element.stable) {
|
if (element.stable) {
|
||||||
// Just add this stable element
|
// remove the same element, if it is currently projected
|
||||||
|
projector.removeElements(element);
|
||||||
|
// Add this stable element
|
||||||
projector.addElement(element);
|
projector.addElement(element);
|
||||||
await this.projectRequest(projector, projector.elements);
|
await this.projectRequest(projector, projector.elements);
|
||||||
} else {
|
} else {
|
||||||
|
@ -8,6 +8,7 @@ import { ViewCountdown } from 'app/site/projector/models/view-countdown';
|
|||||||
import { Countdown } from 'app/shared/models/core/countdown';
|
import { Countdown } from 'app/shared/models/core/countdown';
|
||||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { ServertimeService } from 'app/core/core-services/servertime.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -18,7 +19,8 @@ export class CountdownRepositoryService extends BaseRepository<ViewCountdown, Co
|
|||||||
mapperService: CollectionStringMapperService,
|
mapperService: CollectionStringMapperService,
|
||||||
viewModelStoreService: ViewModelStoreService,
|
viewModelStoreService: ViewModelStoreService,
|
||||||
private dataSend: DataSendService,
|
private dataSend: DataSendService,
|
||||||
private translate: TranslateService
|
private translate: TranslateService,
|
||||||
|
private servertimeService: ServertimeService
|
||||||
) {
|
) {
|
||||||
super(DS, mapperService, viewModelStoreService, Countdown);
|
super(DS, mapperService, viewModelStoreService, Countdown);
|
||||||
}
|
}
|
||||||
@ -41,7 +43,21 @@ export class CountdownRepositoryService extends BaseRepository<ViewCountdown, Co
|
|||||||
await this.dataSend.updateModel(update);
|
await this.dataSend.updateModel(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async delete(viewCountdown: ViewCountdown): Promise<void> {
|
public async delete(countdown: ViewCountdown): Promise<void> {
|
||||||
await this.dataSend.deleteModel(viewCountdown.countdown);
|
await this.dataSend.deleteModel(countdown.countdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async start(countdown: ViewCountdown): Promise<void> {
|
||||||
|
const endTime = this.servertimeService.getServertime() / 1000 + countdown.countdown_time;
|
||||||
|
await this.update({ running: true, countdown_time: endTime }, countdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop(countdown: ViewCountdown): Promise<void> {
|
||||||
|
await this.update({ running: false, countdown_time: countdown.default_time }, countdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async pause(countdown: ViewCountdown): Promise<void> {
|
||||||
|
const endTime = countdown.countdown_time - this.servertimeService.getServertime() / 1000;
|
||||||
|
await this.update({ running: false, countdown_time: endTime }, countdown);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,9 @@ import { Injectable } from '@angular/core';
|
|||||||
* // will also result in 70
|
* // will also result in 70
|
||||||
* const b = this.durationService.stringToDuration('01:10');
|
* const b = this.durationService.stringToDuration('01:10');
|
||||||
*
|
*
|
||||||
|
* // will also result in 89 (interpret as seconds)
|
||||||
|
* const b = this.durationService.stringToDuration('01:20 m', 'm');
|
||||||
|
*
|
||||||
* // will result in 0
|
* // will result in 0
|
||||||
* const c = this.durationService.stringToDuration('01:10b');
|
* const c = this.durationService.stringToDuration('01:10b');
|
||||||
* ```
|
* ```
|
||||||
@ -20,6 +23,9 @@ import { Injectable } from '@angular/core';
|
|||||||
* ```ts
|
* ```ts
|
||||||
* // will result in 01:10 h
|
* // will result in 01:10 h
|
||||||
* const a = this.durationService.durationToString(70);
|
* const a = this.durationService.durationToString(70);
|
||||||
|
*
|
||||||
|
* // will result in 00:30 m (30 is interpreted as seconds)
|
||||||
|
* const a = this.durationService.durationToString(30);
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@ -32,13 +38,16 @@ export class DurationService {
|
|||||||
public constructor() {}
|
public constructor() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform a duration string to duration in minutes.
|
* Transform a duration string to duration in minutes or seconds. This depends on the
|
||||||
|
* provided suffix for the input.
|
||||||
*
|
*
|
||||||
* @param durationText the text to be transformed into a duration
|
* @param durationText the text to be transformed into a duration
|
||||||
* @returns time in minutes or 0 if values are below 0 or no parsable numbers
|
* @param suffix may be 'h' or 'm' for hour or minute. This character will be removed
|
||||||
|
* from the duration text.
|
||||||
|
* @returns time in minutes or seconds or 0 if values are below 0 or no parsable numbers
|
||||||
*/
|
*/
|
||||||
public stringToDuration(durationText: string): number {
|
public stringToDuration(durationText: string, suffix: 'h' | 'm' = 'h'): number {
|
||||||
const splitDuration = durationText.replace('h', '').split(':');
|
const splitDuration = durationText.replace(suffix, '').split(':');
|
||||||
let time: number;
|
let time: number;
|
||||||
if (splitDuration.length > 1 && !isNaN(+splitDuration[0]) && !isNaN(+splitDuration[1])) {
|
if (splitDuration.length > 1 && !isNaN(+splitDuration[0]) && !isNaN(+splitDuration[1])) {
|
||||||
time = +splitDuration[0] * 60 + +splitDuration[1];
|
time = +splitDuration[0] * 60 + +splitDuration[1];
|
||||||
@ -54,31 +63,18 @@ export class DurationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a duration number (given in minutes)
|
* Converts a duration number (given in minutes or seconds)
|
||||||
* To a string in HH:MM format
|
|
||||||
*
|
*
|
||||||
* @param duration value in minutes
|
* @param duration value in minutes
|
||||||
* @returns a more human readable time representation
|
* @returns a more human readable time representation
|
||||||
*/
|
*/
|
||||||
public durationToString(duration: number): string {
|
public durationToString(duration: number, suffix: 'h' | 'm' = 'h'): string {
|
||||||
const hours = Math.floor(duration / 60);
|
const major = Math.floor(duration / 60);
|
||||||
const minutes = `0${Math.floor(duration - hours * 60)}`.slice(-2);
|
const minor = `0${duration % 60}`.slice(-2);
|
||||||
if (!isNaN(+hours) && !isNaN(+minutes)) {
|
if (!isNaN(+major) && !isNaN(+minor)) {
|
||||||
return `${hours}:${minutes} h`;
|
return `${major}:${minor} ${suffix}`;
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a duration number (given in seconds)o a string in `MMM:SS` format
|
|
||||||
*
|
|
||||||
* @param time value in seconds
|
|
||||||
* @returns a more human readable time representation
|
|
||||||
*/
|
|
||||||
public secondDurationToString(time: number): string {
|
|
||||||
const minutes = Math.floor(time / 60);
|
|
||||||
const seconds = Math.floor(time % 60);
|
|
||||||
return `${minutes}:${`0${seconds}`.slice(-2)}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
<div id="countdown" [ngClass]="{
|
||||||
|
'negative': seconds <= 0,
|
||||||
|
'warning_time': seconds <= warningTime && seconds > 0 }">
|
||||||
|
{{ time }}
|
||||||
|
</div>
|
@ -0,0 +1,15 @@
|
|||||||
|
#countdown {
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 10px;
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 75px;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
&.warning_time {
|
||||||
|
color: #ed940d;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.negative {
|
||||||
|
color: #cc0000;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CountdownTimeComponent } from './countdown-time.component';
|
||||||
|
import { E2EImportsModule } from '../../../../e2e-imports.module';
|
||||||
|
|
||||||
|
describe('CountdownTimeComponent', () => {
|
||||||
|
let component: CountdownTimeComponent;
|
||||||
|
let fixture: ComponentFixture<CountdownTimeComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CountdownTimeComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,99 @@
|
|||||||
|
import { Component, OnDestroy, Input } from '@angular/core';
|
||||||
|
|
||||||
|
import { ServertimeService } from 'app/core/core-services/servertime.service';
|
||||||
|
|
||||||
|
export interface CountdownData {
|
||||||
|
running: boolean;
|
||||||
|
countdown_time: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the countdown time.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'os-countdown-time',
|
||||||
|
templateUrl: './countdown-time.component.html',
|
||||||
|
styleUrls: ['./countdown-time.component.scss']
|
||||||
|
})
|
||||||
|
export class CountdownTimeComponent implements OnDestroy {
|
||||||
|
/**
|
||||||
|
* The time in seconds to make the countdown orange, is the countdown is below this value.
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
public warningTime: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The amount of seconds to display
|
||||||
|
*/
|
||||||
|
public seconds: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String formattet seconds.
|
||||||
|
*/
|
||||||
|
public time: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The updateinterval.
|
||||||
|
*/
|
||||||
|
private countdownInterval: any;
|
||||||
|
|
||||||
|
private _countdown: CountdownData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The needed data for the countdown.
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
public set countdown(data: CountdownData) {
|
||||||
|
this._countdown = data;
|
||||||
|
if (this.countdownInterval) {
|
||||||
|
clearInterval(this.countdownInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
this.updateCountdownTime();
|
||||||
|
this.countdownInterval = setInterval(() => this.updateCountdownTime(), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get countdown(): CountdownData {
|
||||||
|
return this._countdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor(private servertimeService: ServertimeService) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the countdown time and string format it.
|
||||||
|
*/
|
||||||
|
private updateCountdownTime(): void {
|
||||||
|
if (this.countdown.running) {
|
||||||
|
this.seconds = Math.floor(this.countdown.countdown_time - this.servertimeService.getServertime() / 1000);
|
||||||
|
} else {
|
||||||
|
this.seconds = this.countdown.countdown_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
const negative = this.seconds < 0;
|
||||||
|
let seconds = this.seconds;
|
||||||
|
if (negative) {
|
||||||
|
seconds = -seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
const time = new Date(seconds * 1000);
|
||||||
|
const m = '0' + time.getMinutes();
|
||||||
|
const s = '0' + time.getSeconds();
|
||||||
|
|
||||||
|
this.time = m.slice(-2) + ':' + s.slice(-2);
|
||||||
|
|
||||||
|
if (negative) {
|
||||||
|
this.time = '-' + this.time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all pending intervals.
|
||||||
|
*/
|
||||||
|
public ngOnDestroy(): void {
|
||||||
|
if (this.countdownInterval) {
|
||||||
|
clearInterval(this.countdownInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,15 +17,15 @@
|
|||||||
<div *ngFor="let option of options">
|
<div *ngFor="let option of options">
|
||||||
<div *ngIf="isDecisionOption(option)">
|
<div *ngIf="isDecisionOption(option)">
|
||||||
<mat-checkbox
|
<mat-checkbox
|
||||||
[checked]="projectorElement[option.key]"
|
[checked]="optionValues[option.key]"
|
||||||
(change)="projectorElement[option.key] = !projectorElement[option.key]"
|
(change)="optionValues[option.key] = !optionValues[option.key]"
|
||||||
>
|
>
|
||||||
{{ option.displayName | translate }}
|
{{ option.displayName | translate }}
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="isChoiceOption(option)">
|
<div *ngIf="isChoiceOption(option)">
|
||||||
<h3>{{ option.displayName | translate }}</h3>
|
<h3>{{ option.displayName | translate }}</h3>
|
||||||
<mat-radio-group [name]="option.key" [(ngModel)]="projectorElement[option.key]">
|
<mat-radio-group [name]="option.key" [(ngModel)]="optionValues[option.key]">
|
||||||
<mat-radio-button *ngFor="let choice of option.choices" [value]="choice.value">
|
<mat-radio-button *ngFor="let choice of option.choices" [value]="choice.value">
|
||||||
{{ choice.displayName | translate }}
|
{{ choice.displayName | translate }}
|
||||||
</mat-radio-button>
|
</mat-radio-button>
|
||||||
|
@ -26,7 +26,7 @@ export type ProjectionDialogReturnType = [Projector[], IdentifiableProjectorElem
|
|||||||
export class ProjectionDialogComponent {
|
export class ProjectionDialogComponent {
|
||||||
public projectors: Projector[];
|
public projectors: Projector[];
|
||||||
private selectedProjectors: Projector[] = [];
|
private selectedProjectors: Projector[] = [];
|
||||||
public projectorElement: IdentifiableProjectorElement;
|
public optionValues: object = {};
|
||||||
public options: SlideOptions;
|
public options: SlideOptions;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
@ -52,11 +52,9 @@ export class ProjectionDialogComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.projectorElement = this.projectorElementBuildDescriptor.getBasicProjectorElement();
|
|
||||||
|
|
||||||
// Set option defaults
|
// Set option defaults
|
||||||
this.projectorElementBuildDescriptor.slideOptions.forEach(option => {
|
this.projectorElementBuildDescriptor.slideOptions.forEach(option => {
|
||||||
this.projectorElement[option.key] = option.default;
|
this.optionValues[option.key] = option.default;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.options = this.projectorElementBuildDescriptor.slideOptions;
|
this.options = this.projectorElementBuildDescriptor.slideOptions;
|
||||||
@ -88,7 +86,9 @@ export class ProjectionDialogComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onOk(): void {
|
public onOk(): void {
|
||||||
this.dialogRef.close([this.selectedProjectors, this.projectorElement]);
|
let element = this.projectorElementBuildDescriptor.getBasicProjectorElement(this.optionValues);
|
||||||
|
element = { ...element, ...this.optionValues };
|
||||||
|
this.dialogRef.close([this.selectedProjectors, element]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onCancel(): void {
|
public onCancel(): void {
|
||||||
|
@ -4,8 +4,9 @@
|
|||||||
|
|
||||||
&.content {
|
&.content {
|
||||||
width: calc(100% - 100px);
|
width: calc(100% - 100px);
|
||||||
margin-left: 50px;
|
position: absolute;
|
||||||
margin-right: 50px;
|
left: 50px;
|
||||||
|
top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,10 +5,13 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
import { BaseComponent } from 'app/base.component';
|
import { BaseComponent } from 'app/base.component';
|
||||||
import { SlideManager } from 'app/slides/services/slide-manager.service';
|
import { SlideManager } from 'app/slides/services/slide-manager.service';
|
||||||
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||||
import { SlideOptions } from 'app/slides/slide-manifest';
|
|
||||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||||
import { SlideData } from 'app/site/projector/services/projector-data.service';
|
import { SlideData } from 'app/site/projector/services/projector-data.service';
|
||||||
|
import { ProjectorElement } from 'app/shared/models/core/projector';
|
||||||
|
|
||||||
|
function hasError(obj: object): obj is { error: string } {
|
||||||
|
return (<{ error: string }>obj).error !== undefined;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Container for one slide. Cares about the position (scale, scroll) in the projector,
|
* Container for one slide. Cares about the position (scale, scroll) in the projector,
|
||||||
* and loading of slides.
|
* and loading of slides.
|
||||||
@ -31,24 +34,31 @@ export class SlideContainerComponent extends BaseComponent {
|
|||||||
private _slideData: SlideData<object>;
|
private _slideData: SlideData<object>;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
public set slideData(data: SlideData<object>) {
|
public set slideData(slideData: SlideData<object>) {
|
||||||
// If there is no ata or an error, clear and exit.
|
// If there is no ata or an error, clear and exit.
|
||||||
if (!data || data.error) {
|
if (!slideData || hasError(slideData) || (slideData.data && hasError(slideData.data))) {
|
||||||
// clear slide container:
|
// clear slide container:
|
||||||
if (this.slide) {
|
if (this.slide) {
|
||||||
this.slide.clear();
|
this.slide.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.error) {
|
let error;
|
||||||
console.error(data.error);
|
if (hasError(slideData)) {
|
||||||
|
error = slideData.error;
|
||||||
|
} else if (slideData.data && hasError(slideData.data)) {
|
||||||
|
error = slideData.data.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.log(error);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._slideData = data;
|
this._slideData = slideData;
|
||||||
if (this.previousSlideName !== data.element.name) {
|
if (this.previousSlideName !== slideData.element.name) {
|
||||||
this.slideChanged(data.element.name);
|
this.slideChanged(slideData.element);
|
||||||
this.previousSlideName = data.element.name;
|
this.previousSlideName = slideData.element.name;
|
||||||
}
|
}
|
||||||
this.setDataForComponent();
|
this.setDataForComponent();
|
||||||
}
|
}
|
||||||
@ -88,7 +98,7 @@ export class SlideContainerComponent extends BaseComponent {
|
|||||||
/**
|
/**
|
||||||
* The current slideoptions.
|
* The current slideoptions.
|
||||||
*/
|
*/
|
||||||
public slideOptions: SlideOptions = { scaleable: false, scrollable: false };
|
public slideOptions: { scaleable: boolean; scrollable: boolean } = { scaleable: false, scrollable: false };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Styles for scaling and scrolling.
|
* Styles for scaling and scrolling.
|
||||||
@ -137,9 +147,19 @@ export class SlideContainerComponent extends BaseComponent {
|
|||||||
*
|
*
|
||||||
* @param slideName The slide to load.
|
* @param slideName The slide to load.
|
||||||
*/
|
*/
|
||||||
private slideChanged(slideName: string): void {
|
private slideChanged(element: ProjectorElement): void {
|
||||||
this.slideOptions = this.slideManager.getSlideOptions(slideName);
|
const options = this.slideManager.getSlideConfiguration(element.name);
|
||||||
this.slideManager.getSlideFactory(slideName).then(slideFactory => {
|
if (typeof options.scaleable === 'boolean') {
|
||||||
|
this.slideOptions.scaleable = options.scaleable;
|
||||||
|
} else {
|
||||||
|
this.slideOptions.scaleable = options.scaleable(element);
|
||||||
|
}
|
||||||
|
if (typeof options.scrollable === 'boolean') {
|
||||||
|
this.slideOptions.scrollable = options.scrollable;
|
||||||
|
} else {
|
||||||
|
this.slideOptions.scrollable = options.scrollable(element);
|
||||||
|
}
|
||||||
|
this.slideManager.getSlideFactory(element.name).then(slideFactory => {
|
||||||
this.slide.clear();
|
this.slide.clear();
|
||||||
this.slideRef = this.slide.createComponent(slideFactory);
|
this.slideRef = this.slide.createComponent(slideFactory);
|
||||||
this.setDataForComponent();
|
this.setDataForComponent();
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
import { BaseModel } from '../base/base-model';
|
import { BaseModel } from '../base/base-model';
|
||||||
|
|
||||||
|
export interface ProjectorElementOptions {
|
||||||
|
/**
|
||||||
|
* Additional data.
|
||||||
|
*/
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A projectorelement must have a name and optional attributes.
|
* A projectorelement must have a name and optional attributes.
|
||||||
* error is listed here, because this might be set by the server, if
|
* error is listed here, because this might be set by the server, if
|
||||||
* something is wrong and I want you to be sensible about this.
|
* something is wrong and I want you to be sensible about this.
|
||||||
*/
|
*/
|
||||||
export interface ProjectorElement {
|
export interface ProjectorElement extends ProjectorElementOptions {
|
||||||
/**
|
/**
|
||||||
* The name of the element.
|
* The name of the element.
|
||||||
*/
|
*/
|
||||||
@ -16,11 +23,6 @@ export interface ProjectorElement {
|
|||||||
* DO NOT read additional data (name is save).
|
* DO NOT read additional data (name is save).
|
||||||
*/
|
*/
|
||||||
error?: string;
|
error?: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* Additional data.
|
|
||||||
*/
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IdentifiableProjectorElement extends ProjectorElement {
|
export interface IdentifiableProjectorElement extends ProjectorElement {
|
||||||
|
@ -82,6 +82,7 @@ import { MetaTextBlockComponent } from './components/meta-text-block/meta-text-b
|
|||||||
import { OpenSlidesTranslateModule } from '../core/translate/openslides-translate-module';
|
import { OpenSlidesTranslateModule } from '../core/translate/openslides-translate-module';
|
||||||
import { ProjectorComponent } from './components/projector/projector.component';
|
import { ProjectorComponent } from './components/projector/projector.component';
|
||||||
import { SlideContainerComponent } from './components/slide-container/slide-container.component';
|
import { SlideContainerComponent } from './components/slide-container/slide-container.component';
|
||||||
|
import { CountdownTimeComponent } from './components/contdown-time/countdown-time.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Share Module for all "dumb" components and pipes.
|
* Share Module for all "dumb" components and pipes.
|
||||||
@ -201,7 +202,8 @@ import { SlideContainerComponent } from './components/slide-container/slide-cont
|
|||||||
ProjectorComponent,
|
ProjectorComponent,
|
||||||
SlideContainerComponent,
|
SlideContainerComponent,
|
||||||
OwlDateTimeModule,
|
OwlDateTimeModule,
|
||||||
OwlNativeDateTimeModule
|
OwlNativeDateTimeModule,
|
||||||
|
CountdownTimeComponent
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
PermsDirective,
|
PermsDirective,
|
||||||
@ -227,7 +229,8 @@ import { SlideContainerComponent } from './components/slide-container/slide-cont
|
|||||||
ResizedDirective,
|
ResizedDirective,
|
||||||
MetaTextBlockComponent,
|
MetaTextBlockComponent,
|
||||||
ProjectorComponent,
|
ProjectorComponent,
|
||||||
SlideContainerComponent
|
SlideContainerComponent,
|
||||||
|
CountdownTimeComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: DateAdapter, useClass: OpenSlidesDateAdapter },
|
{ provide: DateAdapter, useClass: OpenSlidesDateAdapter },
|
||||||
|
@ -342,6 +342,6 @@ export class SpeakerListComponent extends BaseViewComponent implements OnInit {
|
|||||||
const duration = Math.floor(
|
const duration = Math.floor(
|
||||||
(new Date(speaker.end_time).valueOf() - new Date(speaker.begin_time).valueOf()) / 1000
|
(new Date(speaker.end_time).valueOf() - new Date(speaker.begin_time).valueOf()) / 1000
|
||||||
);
|
);
|
||||||
return `${this.durationService.secondDurationToString(duration)} ${this.translate.instant('minutes')}`;
|
return `${this.durationService.durationToString(duration, 'm')} ${this.translate.instant('minutes')}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ export class ViewTopic extends BaseAgendaViewModel {
|
|||||||
|
|
||||||
public getSlide(): ProjectorElementBuildDeskriptor {
|
public getSlide(): ProjectorElementBuildDeskriptor {
|
||||||
return {
|
return {
|
||||||
getBasicProjectorElement: () => ({
|
getBasicProjectorElement: options => ({
|
||||||
name: Topic.COLLECTIONSTRING,
|
name: Topic.COLLECTIONSTRING,
|
||||||
id: this.id,
|
id: this.id,
|
||||||
getIdentifiers: () => ['name', 'id']
|
getIdentifiers: () => ['name', 'id']
|
||||||
|
@ -81,7 +81,7 @@ export class ViewAssignment extends BaseAgendaViewModel {
|
|||||||
|
|
||||||
public getSlide(): ProjectorElementBuildDeskriptor {
|
public getSlide(): ProjectorElementBuildDeskriptor {
|
||||||
return {
|
return {
|
||||||
getBasicProjectorElement: () => ({
|
getBasicProjectorElement: options => ({
|
||||||
name: Assignment.COLLECTIONSTRING,
|
name: Assignment.COLLECTIONSTRING,
|
||||||
id: this.id,
|
id: this.id,
|
||||||
getIdentifiers: () => ['name', 'id']
|
getIdentifiers: () => ['name', 'id']
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Displayable } from 'app/site/base/displayable';
|
import { Displayable } from 'app/site/base/displayable';
|
||||||
import { IdentifiableProjectorElement } from 'app/shared/models/core/projector';
|
import { IdentifiableProjectorElement, ProjectorElementOptions } from 'app/shared/models/core/projector';
|
||||||
import { SlideOptions } from './slide-options';
|
import { SlideOptions } from './slide-options';
|
||||||
|
|
||||||
export function isProjectorElementBuildDeskriptor(obj: any): obj is ProjectorElementBuildDeskriptor {
|
export function isProjectorElementBuildDeskriptor(obj: any): obj is ProjectorElementBuildDeskriptor {
|
||||||
@ -15,7 +15,7 @@ export function isProjectorElementBuildDeskriptor(obj: any): obj is ProjectorEle
|
|||||||
export interface ProjectorElementBuildDeskriptor {
|
export interface ProjectorElementBuildDeskriptor {
|
||||||
slideOptions: SlideOptions;
|
slideOptions: SlideOptions;
|
||||||
projectionDefaultName?: string;
|
projectionDefaultName?: string;
|
||||||
getBasicProjectorElement(): IdentifiableProjectorElement;
|
getBasicProjectorElement(options: ProjectorElementOptions): IdentifiableProjectorElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The title to show in the projection dialog
|
* The title to show in the projection dialog
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
export interface SlideDecisionOption {
|
interface BaseSlideOption {
|
||||||
key: string;
|
key: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
default: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SlideChoiceOption extends SlideDecisionOption {
|
export interface SlideDecisionOption extends BaseSlideOption {
|
||||||
|
default: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SlideChoiceOption extends BaseSlideOption {
|
||||||
|
default: string;
|
||||||
choices: { value: string; displayName: string }[];
|
choices: { value: string; displayName: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -561,7 +561,7 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
|
|||||||
|
|
||||||
public getSlide(): ProjectorElementBuildDeskriptor {
|
public getSlide(): ProjectorElementBuildDeskriptor {
|
||||||
return {
|
return {
|
||||||
getBasicProjectorElement: () => ({
|
getBasicProjectorElement: options => ({
|
||||||
name: Motion.COLLECTIONSTRING,
|
name: Motion.COLLECTIONSTRING,
|
||||||
id: this.id,
|
id: this.id,
|
||||||
getIdentifiers: () => ['name', 'id']
|
getIdentifiers: () => ['name', 'id']
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
<div *ngIf="countdown">
|
||||||
|
<os-countdown-time [countdown]="countdown" [warningTime]="warningTime"></os-countdown-time>
|
||||||
|
<button type="button" [disabled]="!canStop()" mat-icon-button (click)="stop($event)">
|
||||||
|
<mat-icon>stop</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="!countdown.running" type="button" mat-icon-button (click)="start($event)">
|
||||||
|
<mat-icon>play_arrow</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="countdown.running" type="button" mat-icon-button (click)="pause($event)">
|
||||||
|
<mat-icon>pause</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
@ -0,0 +1,26 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CountdownControlsComponent } from './countdown-controls.component';
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
|
||||||
|
describe('CountdownControlsComponent', () => {
|
||||||
|
let component: CountdownControlsComponent;
|
||||||
|
let fixture: ComponentFixture<CountdownControlsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
declarations: [CountdownControlsComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CountdownControlsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,67 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { Title } from '@angular/platform-browser';
|
||||||
|
import { MatSnackBar } from '@angular/material';
|
||||||
|
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { BaseViewComponent } from '../../../base/base-view';
|
||||||
|
import { ViewCountdown } from '../../models/view-countdown';
|
||||||
|
import { CountdownRepositoryService } from 'app/core/repositories/projector/countdown-repository.service';
|
||||||
|
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'os-countdown-controls',
|
||||||
|
templateUrl: './countdown-controls.component.html'
|
||||||
|
})
|
||||||
|
export class CountdownControlsComponent extends BaseViewComponent {
|
||||||
|
@Input()
|
||||||
|
public countdown: ViewCountdown;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time in seconds to make the countdown orange, is the countdown is below this value.
|
||||||
|
*/
|
||||||
|
public warningTime: number;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
titleService: Title,
|
||||||
|
translate: TranslateService,
|
||||||
|
matSnackBar: MatSnackBar,
|
||||||
|
private repo: CountdownRepositoryService,
|
||||||
|
private configService: ConfigService
|
||||||
|
) {
|
||||||
|
super(titleService, translate, matSnackBar);
|
||||||
|
|
||||||
|
this.configService.get<number>('agenda_countdown_warning_time').subscribe(time => (this.warningTime = time));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the countdown
|
||||||
|
*/
|
||||||
|
public start(event: Event): void {
|
||||||
|
event.stopPropagation();
|
||||||
|
this.repo.start(this.countdown).catch(this.raiseError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pause the countdown
|
||||||
|
*/
|
||||||
|
public pause(event: Event): void {
|
||||||
|
event.stopPropagation();
|
||||||
|
this.repo.pause(this.countdown).catch(this.raiseError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the countdown
|
||||||
|
*/
|
||||||
|
public stop(event: Event): void {
|
||||||
|
event.stopPropagation();
|
||||||
|
this.repo.stop(this.countdown).catch(this.raiseError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One can stop the countdown, if it is running or not resetted.
|
||||||
|
*/
|
||||||
|
public canStop(): boolean {
|
||||||
|
return this.countdown.running || this.countdown.countdown_time !== this.countdown.default_time;
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,13 @@
|
|||||||
<span translate>Required</span>
|
<span translate>Required</span>
|
||||||
</mat-hint>
|
</mat-hint>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
</p><p>
|
||||||
|
<mat-form-field>
|
||||||
|
<input formControlName="default_time" matInput placeholder="{{ 'Time' | translate}}" required>
|
||||||
|
<mat-hint *ngIf="!createForm.controls.default_time.valid">
|
||||||
|
<span translate>Required</span>
|
||||||
|
</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
@ -45,8 +52,10 @@
|
|||||||
<div class="header-name">
|
<div class="header-name">
|
||||||
{{ countdown.description }}
|
{{ countdown.description }}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="header-controls">
|
||||||
|
<os-countdown-controls [countdown]="countdown"></os-countdown-controls>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</mat-panel-title>
|
</mat-panel-title>
|
||||||
</mat-expansion-panel-header>
|
</mat-expansion-panel-header>
|
||||||
<form [formGroup]="updateForm"
|
<form [formGroup]="updateForm"
|
||||||
@ -60,11 +69,15 @@
|
|||||||
<span translate>Required</span>
|
<span translate>Required</span>
|
||||||
</mat-hint>
|
</mat-hint>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
</p><p>
|
||||||
|
<mat-form-field>
|
||||||
|
<input formControlName="default_time" matInput placeholder="{{ 'Time' | translate}}" required>
|
||||||
|
<mat-hint *ngIf="!updateForm.controls.default_time.valid">
|
||||||
|
<span translate>Required</span>
|
||||||
|
</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
<ng-container *ngIf="editId !== countdown.id">
|
|
||||||
TODO: Show countdown time etc.
|
|
||||||
</ng-container>
|
|
||||||
<mat-action-row>
|
<mat-action-row>
|
||||||
<button *ngIf="editId !== countdown.id" mat-button class="on-transition-fade" (click)="onEditButton(countdown)"
|
<button *ngIf="editId !== countdown.id" mat-button class="on-transition-fade" (click)="onEditButton(countdown)"
|
||||||
mat-icon-button>
|
mat-icon-button>
|
||||||
|
@ -14,7 +14,7 @@ mat-card {
|
|||||||
.header-container {
|
.header-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto;
|
grid-template-rows: auto;
|
||||||
grid-template-columns: 40px 1fr;
|
grid-template-columns: 40px 1fr 2fr;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
@ -25,10 +25,17 @@ mat-card {
|
|||||||
|
|
||||||
.header-projector-button {
|
.header-projector-button {
|
||||||
grid-column-start: 1;
|
grid-column-start: 1;
|
||||||
|
grid-column-end: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-name {
|
.header-name {
|
||||||
grid-column-start: 2;
|
grid-column-start: 2;
|
||||||
|
grid-column-end: 3;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-controls {
|
||||||
|
grid-column-start: 3;
|
||||||
|
grid-column-end: 4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
|
|
||||||
import { CountdownListComponent } from './countdown-list.component';
|
import { CountdownListComponent } from './countdown-list.component';
|
||||||
import { E2EImportsModule } from 'e2e-imports.module';
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
import { CountdownControlsComponent } from '../countdown-controls/countdown-controls.component';
|
||||||
|
|
||||||
describe('CountdownListComponent', () => {
|
describe('CountdownListComponent', () => {
|
||||||
let component: CountdownListComponent;
|
let component: CountdownListComponent;
|
||||||
@ -10,7 +11,7 @@ describe('CountdownListComponent', () => {
|
|||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [E2EImportsModule],
|
imports: [E2EImportsModule],
|
||||||
declarations: [CountdownListComponent]
|
declarations: [CountdownListComponent, CountdownControlsComponent]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -10,9 +10,10 @@ import { BaseViewComponent } from '../../../base/base-view';
|
|||||||
import { ViewCountdown } from '../../models/view-countdown';
|
import { ViewCountdown } from '../../models/view-countdown';
|
||||||
import { CountdownRepositoryService } from 'app/core/repositories/projector/countdown-repository.service';
|
import { CountdownRepositoryService } from 'app/core/repositories/projector/countdown-repository.service';
|
||||||
import { Countdown } from 'app/shared/models/core/countdown';
|
import { Countdown } from 'app/shared/models/core/countdown';
|
||||||
|
import { DurationService } from 'app/core/ui-services/duration.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List view for the statute paragraphs.
|
* List view for countdowns.
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'os-countdown-list',
|
selector: 'os-countdown-list',
|
||||||
@ -37,20 +38,20 @@ export class CountdownListComponent extends BaseViewComponent implements OnInit
|
|||||||
public openId: number | null;
|
public openId: number | null;
|
||||||
public editId: number | null;
|
public editId: number | null;
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
public constructor(
|
public constructor(
|
||||||
titleService: Title,
|
titleService: Title,
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
matSnackBar: MatSnackBar,
|
matSnackBar: MatSnackBar,
|
||||||
private repo: CountdownRepositoryService,
|
private repo: CountdownRepositoryService,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private promptService: PromptService
|
private promptService: PromptService,
|
||||||
|
private durationService: DurationService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar);
|
super(titleService, translate, matSnackBar);
|
||||||
|
|
||||||
const form = {
|
const form = {
|
||||||
description: ['', Validators.required]
|
description: ['', Validators.required],
|
||||||
|
default_time: ['', Validators.required]
|
||||||
};
|
};
|
||||||
this.createForm = this.formBuilder.group(form);
|
this.createForm = this.formBuilder.group(form);
|
||||||
this.updateForm = this.formBuilder.group(form);
|
this.updateForm = this.formBuilder.group(form);
|
||||||
@ -75,7 +76,8 @@ export class CountdownListComponent extends BaseViewComponent implements OnInit
|
|||||||
if (!this.countdownToCreate) {
|
if (!this.countdownToCreate) {
|
||||||
this.createForm.reset();
|
this.createForm.reset();
|
||||||
this.createForm.setValue({
|
this.createForm.setValue({
|
||||||
description: ''
|
description: '',
|
||||||
|
default_time: '1:00 m'
|
||||||
});
|
});
|
||||||
this.countdownToCreate = new Countdown();
|
this.countdownToCreate = new Countdown();
|
||||||
}
|
}
|
||||||
@ -86,7 +88,17 @@ export class CountdownListComponent extends BaseViewComponent implements OnInit
|
|||||||
*/
|
*/
|
||||||
public create(): void {
|
public create(): void {
|
||||||
if (this.createForm.valid) {
|
if (this.createForm.valid) {
|
||||||
this.countdownToCreate.patchValues(this.createForm.value as Countdown);
|
let default_time = this.durationService.stringToDuration(this.createForm.value.default_time, 'm');
|
||||||
|
if (default_time === 0) {
|
||||||
|
default_time = 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newValues: Partial<Countdown> = {
|
||||||
|
description: this.createForm.value.description,
|
||||||
|
default_time: default_time
|
||||||
|
};
|
||||||
|
newValues.countdown_time = default_time;
|
||||||
|
this.countdownToCreate.patchValues(newValues);
|
||||||
this.repo.create(this.countdownToCreate).then(() => {
|
this.repo.create(this.countdownToCreate).then(() => {
|
||||||
this.countdownToCreate = null;
|
this.countdownToCreate = null;
|
||||||
}, this.raiseError);
|
}, this.raiseError);
|
||||||
@ -101,7 +113,8 @@ export class CountdownListComponent extends BaseViewComponent implements OnInit
|
|||||||
this.editId = countdown.id;
|
this.editId = countdown.id;
|
||||||
|
|
||||||
this.updateForm.setValue({
|
this.updateForm.setValue({
|
||||||
description: countdown.description
|
description: countdown.description,
|
||||||
|
default_time: this.durationService.durationToString(countdown.default_time, 'm')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +124,18 @@ export class CountdownListComponent extends BaseViewComponent implements OnInit
|
|||||||
*/
|
*/
|
||||||
public onSaveButton(countdown: ViewCountdown): void {
|
public onSaveButton(countdown: ViewCountdown): void {
|
||||||
if (this.updateForm.valid) {
|
if (this.updateForm.valid) {
|
||||||
this.repo.update(this.updateForm.value as Partial<Countdown>, countdown).then(() => {
|
let default_time = this.durationService.stringToDuration(this.updateForm.value.default_time, 'm');
|
||||||
|
if (default_time === 0) {
|
||||||
|
default_time = 60;
|
||||||
|
}
|
||||||
|
const newValues: Partial<Countdown> = {
|
||||||
|
description: this.updateForm.value.description,
|
||||||
|
default_time: default_time
|
||||||
|
};
|
||||||
|
if (!countdown.running) {
|
||||||
|
newValues.countdown_time = default_time;
|
||||||
|
}
|
||||||
|
this.repo.update(newValues, countdown).then(() => {
|
||||||
this.openId = this.editId = null;
|
this.openId = this.editId = null;
|
||||||
}, this.raiseError);
|
}, this.raiseError);
|
||||||
}
|
}
|
||||||
@ -123,7 +147,7 @@ export class CountdownListComponent extends BaseViewComponent implements OnInit
|
|||||||
* @param countdown The countdown to delete
|
* @param countdown The countdown to delete
|
||||||
*/
|
*/
|
||||||
public async onDeleteButton(countdown: ViewCountdown): Promise<void> {
|
public async onDeleteButton(countdown: ViewCountdown): Promise<void> {
|
||||||
const content = this.translate.instant('Delete') + ` ${countdown.description}?`;
|
const content = this.translate.instant('Delete countdown') + ` ${countdown.description}?`;
|
||||||
if (await this.promptService.open('Are you sure?', content)) {
|
if (await this.promptService.open('Are you sure?', content)) {
|
||||||
this.repo.delete(countdown).then(() => (this.openId = this.editId = null), this.raiseError);
|
this.repo.delete(countdown).then(() => (this.openId = this.editId = null), this.raiseError);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { E2EImportsModule } from 'e2e-imports.module';
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
import { ProjectorMessageListComponent } from './projector-message-list.component';
|
import { ProjectorMessageListComponent } from './projector-message-list.component';
|
||||||
|
|
||||||
describe('CountdownListComponent', () => {
|
describe('ProjectorMessageListComponent', () => {
|
||||||
let component: ProjectorMessageListComponent;
|
let component: ProjectorMessageListComponent;
|
||||||
let fixture: ComponentFixture<ProjectorMessageListComponent>;
|
let fixture: ComponentFixture<ProjectorMessageListComponent>;
|
||||||
|
|
||||||
|
@ -16,6 +16,18 @@ export class ViewCountdown extends BaseProjectableViewModel {
|
|||||||
return this.countdown.id;
|
return this.countdown.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get running(): boolean {
|
||||||
|
return this.countdown.running;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get default_time(): number {
|
||||||
|
return this.countdown.default_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get countdown_time(): number {
|
||||||
|
return this.countdown.countdown_time;
|
||||||
|
}
|
||||||
|
|
||||||
public get description(): string {
|
public get description(): string {
|
||||||
return this.countdown.description;
|
return this.countdown.description;
|
||||||
}
|
}
|
||||||
@ -38,13 +50,19 @@ export class ViewCountdown extends BaseProjectableViewModel {
|
|||||||
|
|
||||||
public getSlide(): ProjectorElementBuildDeskriptor {
|
public getSlide(): ProjectorElementBuildDeskriptor {
|
||||||
return {
|
return {
|
||||||
getBasicProjectorElement: () => ({
|
getBasicProjectorElement: options => ({
|
||||||
stable: true,
|
stable: true,
|
||||||
name: Countdown.COLLECTIONSTRING,
|
name: Countdown.COLLECTIONSTRING,
|
||||||
id: this.id,
|
id: this.id,
|
||||||
getIdentifiers: () => ['name', 'id']
|
getIdentifiers: () => ['name', 'id']
|
||||||
}),
|
}),
|
||||||
slideOptions: [],
|
slideOptions: [
|
||||||
|
{
|
||||||
|
key: 'fullscreen',
|
||||||
|
displayName: 'Fullscreen',
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
],
|
||||||
projectionDefaultName: 'countdowns',
|
projectionDefaultName: 'countdowns',
|
||||||
getTitle: () => this.getTitle()
|
getTitle: () => this.getTitle()
|
||||||
};
|
};
|
||||||
|
@ -39,7 +39,7 @@ export class ViewProjectorMessage extends BaseProjectableViewModel {
|
|||||||
|
|
||||||
public getSlide(): ProjectorElementBuildDeskriptor {
|
public getSlide(): ProjectorElementBuildDeskriptor {
|
||||||
return {
|
return {
|
||||||
getBasicProjectorElement: () => ({
|
getBasicProjectorElement: options => ({
|
||||||
stable: true,
|
stable: true,
|
||||||
name: ProjectorMessage.COLLECTIONSTRING,
|
name: ProjectorMessage.COLLECTIONSTRING,
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
@ -10,6 +10,7 @@ import { ProjectorDataService } from './services/projector-data.service';
|
|||||||
import { CurrentListOfSpeakersSlideService } from './services/current-list-of-of-speakers-slide.service';
|
import { CurrentListOfSpeakersSlideService } from './services/current-list-of-of-speakers-slide.service';
|
||||||
import { CountdownListComponent } from './components/countdown-list/countdown-list.component';
|
import { CountdownListComponent } from './components/countdown-list/countdown-list.component';
|
||||||
import { ProjectorMessageListComponent } from './components/projector-message-list/projector-message-list.component';
|
import { ProjectorMessageListComponent } from './components/projector-message-list/projector-message-list.component';
|
||||||
|
import { CountdownControlsComponent } from './components/countdown-controls/countdown-controls.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
providers: [ClockSlideService, ProjectorDataService, CurrentListOfSpeakersSlideService],
|
providers: [ClockSlideService, ProjectorDataService, CurrentListOfSpeakersSlideService],
|
||||||
@ -18,7 +19,8 @@ import { ProjectorMessageListComponent } from './components/projector-message-li
|
|||||||
ProjectorListComponent,
|
ProjectorListComponent,
|
||||||
ProjectorDetailComponent,
|
ProjectorDetailComponent,
|
||||||
CountdownListComponent,
|
CountdownListComponent,
|
||||||
ProjectorMessageListComponent
|
ProjectorMessageListComponent,
|
||||||
|
CountdownControlsComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class ProjectorModule {}
|
export class ProjectorModule {}
|
||||||
|
@ -22,12 +22,10 @@ export class CurrentListOfSpeakersSlideService {
|
|||||||
private slideManager: SlideManager
|
private slideManager: SlideManager
|
||||||
) {
|
) {
|
||||||
this.projectorRepo.getGeneralViewModelObservable().subscribe(projector => {
|
this.projectorRepo.getGeneralViewModelObservable().subscribe(projector => {
|
||||||
if (projector) {
|
if (projector && this.currentItemIds[projector.id]) {
|
||||||
const item = this.getCurrentAgendaItemIdForProjector(projector);
|
const item = this.getCurrentAgendaItemIdForProjector(projector);
|
||||||
if (this.currentItemIds[projector.id]) {
|
|
||||||
this.currentItemIds[projector.id].next(item);
|
this.currentItemIds[projector.id].next(item);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,7 +188,7 @@ export class ViewUser extends BaseProjectableViewModel implements Searchable {
|
|||||||
|
|
||||||
public getSlide(): ProjectorElementBuildDeskriptor {
|
public getSlide(): ProjectorElementBuildDeskriptor {
|
||||||
return {
|
return {
|
||||||
getBasicProjectorElement: () => ({
|
getBasicProjectorElement: options => ({
|
||||||
name: User.COLLECTIONSTRING,
|
name: User.COLLECTIONSTRING,
|
||||||
id: this.id,
|
id: this.id,
|
||||||
getIdentifiers: () => ['name', 'id']
|
getIdentifiers: () => ['name', 'id']
|
||||||
|
49
client/src/app/slides/all-slide-configurations.ts
Normal file
49
client/src/app/slides/all-slide-configurations.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { SlideDynamicConfiguration, Slide } from './slide-manifest';
|
||||||
|
|
||||||
|
export const allSlidesDynamicConfiguration: (SlideDynamicConfiguration & Slide)[] = [
|
||||||
|
{
|
||||||
|
slide: 'topics/topic',
|
||||||
|
scaleable: true,
|
||||||
|
scrollable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slide: 'motions/motion',
|
||||||
|
scaleable: true,
|
||||||
|
scrollable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slide: 'users/user',
|
||||||
|
scaleable: true,
|
||||||
|
scrollable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slide: 'core/clock',
|
||||||
|
scaleable: false,
|
||||||
|
scrollable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slide: 'core/countdown',
|
||||||
|
scaleable: false,
|
||||||
|
scrollable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slide: 'core/projector-message',
|
||||||
|
scaleable: false,
|
||||||
|
scrollable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slide: 'agenda/current-list-of-speakers',
|
||||||
|
scaleable: true,
|
||||||
|
scrollable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slide: 'agenda/current-list-of-speakers-overlay',
|
||||||
|
scaleable: false,
|
||||||
|
scrollable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slide: 'assignments/assignment',
|
||||||
|
scaleable: true,
|
||||||
|
scrollable: true
|
||||||
|
}
|
||||||
|
];
|
@ -12,8 +12,6 @@ export const allSlides: SlideManifest[] = [
|
|||||||
slide: 'topics/topic',
|
slide: 'topics/topic',
|
||||||
path: 'topics/topic',
|
path: 'topics/topic',
|
||||||
loadChildren: './slides/agenda/topic/topics-topic-slide.module#TopicsTopicSlideModule',
|
loadChildren: './slides/agenda/topic/topics-topic-slide.module#TopicsTopicSlideModule',
|
||||||
scaleable: true,
|
|
||||||
scrollable: true,
|
|
||||||
verboseName: 'Topic',
|
verboseName: 'Topic',
|
||||||
elementIdentifiers: ['name', 'id'],
|
elementIdentifiers: ['name', 'id'],
|
||||||
canBeMappedToModel: true
|
canBeMappedToModel: true
|
||||||
@ -22,8 +20,6 @@ export const allSlides: SlideManifest[] = [
|
|||||||
slide: 'motions/motion',
|
slide: 'motions/motion',
|
||||||
path: 'motions/motion',
|
path: 'motions/motion',
|
||||||
loadChildren: './slides/motions/motion/motions-motion-slide.module#MotionsMotionSlideModule',
|
loadChildren: './slides/motions/motion/motions-motion-slide.module#MotionsMotionSlideModule',
|
||||||
scaleable: true,
|
|
||||||
scrollable: true,
|
|
||||||
verboseName: 'Motion',
|
verboseName: 'Motion',
|
||||||
elementIdentifiers: ['name', 'id'],
|
elementIdentifiers: ['name', 'id'],
|
||||||
canBeMappedToModel: true
|
canBeMappedToModel: true
|
||||||
@ -32,8 +28,6 @@ export const allSlides: SlideManifest[] = [
|
|||||||
slide: 'users/user',
|
slide: 'users/user',
|
||||||
path: 'users/user',
|
path: 'users/user',
|
||||||
loadChildren: './slides/users/user/users-user-slide.module#UsersUserSlideModule',
|
loadChildren: './slides/users/user/users-user-slide.module#UsersUserSlideModule',
|
||||||
scaleable: true,
|
|
||||||
scrollable: true,
|
|
||||||
verboseName: 'Participant',
|
verboseName: 'Participant',
|
||||||
elementIdentifiers: ['name', 'id'],
|
elementIdentifiers: ['name', 'id'],
|
||||||
canBeMappedToModel: true
|
canBeMappedToModel: true
|
||||||
@ -42,8 +36,6 @@ export const allSlides: SlideManifest[] = [
|
|||||||
slide: 'core/clock',
|
slide: 'core/clock',
|
||||||
path: 'core/clock',
|
path: 'core/clock',
|
||||||
loadChildren: './slides/core/clock/clock-slide.module#ClockSlideModule',
|
loadChildren: './slides/core/clock/clock-slide.module#ClockSlideModule',
|
||||||
scaleable: false,
|
|
||||||
scrollable: false,
|
|
||||||
verboseName: 'Clock',
|
verboseName: 'Clock',
|
||||||
elementIdentifiers: ['name'],
|
elementIdentifiers: ['name'],
|
||||||
canBeMappedToModel: false
|
canBeMappedToModel: false
|
||||||
@ -52,8 +44,6 @@ export const allSlides: SlideManifest[] = [
|
|||||||
slide: 'core/countdown',
|
slide: 'core/countdown',
|
||||||
path: 'core/countdown',
|
path: 'core/countdown',
|
||||||
loadChildren: './slides/core/countdown/countdown-slide.module#CountdownSlideModule',
|
loadChildren: './slides/core/countdown/countdown-slide.module#CountdownSlideModule',
|
||||||
scaleable: false,
|
|
||||||
scrollable: false,
|
|
||||||
verboseName: 'Countdown',
|
verboseName: 'Countdown',
|
||||||
elementIdentifiers: ['name', 'id'],
|
elementIdentifiers: ['name', 'id'],
|
||||||
canBeMappedToModel: true
|
canBeMappedToModel: true
|
||||||
@ -62,8 +52,6 @@ export const allSlides: SlideManifest[] = [
|
|||||||
slide: 'core/projector-message',
|
slide: 'core/projector-message',
|
||||||
path: 'core/projector-message',
|
path: 'core/projector-message',
|
||||||
loadChildren: './slides/core/projector-message/projector-message-slide.module#ProjectorMessageSlideModule',
|
loadChildren: './slides/core/projector-message/projector-message-slide.module#ProjectorMessageSlideModule',
|
||||||
scaleable: false,
|
|
||||||
scrollable: false,
|
|
||||||
verboseName: 'Message',
|
verboseName: 'Message',
|
||||||
elementIdentifiers: ['name', 'id'],
|
elementIdentifiers: ['name', 'id'],
|
||||||
canBeMappedToModel: true
|
canBeMappedToModel: true
|
||||||
@ -73,8 +61,6 @@ export const allSlides: SlideManifest[] = [
|
|||||||
path: 'agenda/current-list-of-speakers',
|
path: 'agenda/current-list-of-speakers',
|
||||||
loadChildren:
|
loadChildren:
|
||||||
'./slides/agenda/current-list-of-speakers/agenda-current-list-of-speakers-slide.module#AgendaCurrentListOfSpeakersSlideModule',
|
'./slides/agenda/current-list-of-speakers/agenda-current-list-of-speakers-slide.module#AgendaCurrentListOfSpeakersSlideModule',
|
||||||
scaleable: true,
|
|
||||||
scrollable: true,
|
|
||||||
verboseName: 'Current list of speakers',
|
verboseName: 'Current list of speakers',
|
||||||
elementIdentifiers: ['name', 'id'],
|
elementIdentifiers: ['name', 'id'],
|
||||||
canBeMappedToModel: false
|
canBeMappedToModel: false
|
||||||
@ -84,8 +70,6 @@ export const allSlides: SlideManifest[] = [
|
|||||||
path: 'agenda/current-list-of-speakers-overlay',
|
path: 'agenda/current-list-of-speakers-overlay',
|
||||||
loadChildren:
|
loadChildren:
|
||||||
'./slides/agenda/current-list-of-speakers-overlay/agenda-current-list-of-speakers-overlay-slide.module#AgendaCurrentListOfSpeakersOverlaySlideModule',
|
'./slides/agenda/current-list-of-speakers-overlay/agenda-current-list-of-speakers-overlay-slide.module#AgendaCurrentListOfSpeakersOverlaySlideModule',
|
||||||
scaleable: false,
|
|
||||||
scrollable: false,
|
|
||||||
verboseName: 'Current list of speakers overlay',
|
verboseName: 'Current list of speakers overlay',
|
||||||
elementIdentifiers: ['name', 'id'],
|
elementIdentifiers: ['name', 'id'],
|
||||||
canBeMappedToModel: false
|
canBeMappedToModel: false
|
||||||
@ -94,8 +78,6 @@ export const allSlides: SlideManifest[] = [
|
|||||||
slide: 'assignments/assignment',
|
slide: 'assignments/assignment',
|
||||||
path: 'assignments/assignment',
|
path: 'assignments/assignment',
|
||||||
loadChildren: './slides/assignments/assignment/assignment-slide.module#AssignmentSlideModule',
|
loadChildren: './slides/assignments/assignment/assignment-slide.module#AssignmentSlideModule',
|
||||||
scaleable: true,
|
|
||||||
scrollable: true,
|
|
||||||
verboseName: 'Election',
|
verboseName: 'Election',
|
||||||
elementIdentifiers: ['name', 'id'],
|
elementIdentifiers: ['name', 'id'],
|
||||||
canBeMappedToModel: true
|
canBeMappedToModel: true
|
||||||
|
@ -15,6 +15,8 @@ export class ClockSlideComponent extends BaseSlideComponent<{}> implements OnIni
|
|||||||
|
|
||||||
private servertimeSubscription: Subscription | null = null;
|
private servertimeSubscription: Subscription | null = null;
|
||||||
|
|
||||||
|
private clockInterval: any;
|
||||||
|
|
||||||
public constructor(private servertimeService: ServertimeService) {
|
public constructor(private servertimeService: ServertimeService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@ -26,7 +28,7 @@ export class ClockSlideComponent extends BaseSlideComponent<{}> implements OnIni
|
|||||||
.subscribe(() => this.updateClock());
|
.subscribe(() => this.updateClock());
|
||||||
|
|
||||||
// Update clock every 10 seconds.
|
// Update clock every 10 seconds.
|
||||||
setInterval(() => this.updateClock(), 10 * 1000);
|
this.clockInterval = setInterval(() => this.updateClock(), 10 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateClock(): void {
|
private updateClock(): void {
|
||||||
@ -42,5 +44,8 @@ export class ClockSlideComponent extends BaseSlideComponent<{}> implements OnIni
|
|||||||
if (this.servertimeSubscription) {
|
if (this.servertimeSubscription) {
|
||||||
this.servertimeSubscription.unsubscribe();
|
this.servertimeSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
if (this.clockInterval) {
|
||||||
|
clearInterval(this.clockInterval);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
export interface CountdownSlideData {
|
export interface CountdownSlideData {
|
||||||
error: string;
|
description: string;
|
||||||
|
countdown_time: number;
|
||||||
|
running: boolean;
|
||||||
|
warning_time: number;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
<div id="outer">
|
<div class="countdown overlay" *ngIf="data && !data.element.fullscreen">
|
||||||
COUNTDOWN
|
<os-countdown-time [countdown]="data.data" [warningTime]="data.data.warning_time"></os-countdown-time>
|
||||||
|
<div class="description">
|
||||||
|
{{ data.data.description }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="countdown fullscreen" *ngIf="data && data.element.fullscreen">
|
||||||
|
<div>
|
||||||
|
<os-countdown-time [countdown]="data.data" [warningTime]="data.data.warning_time"></os-countdown-time>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,10 +1,42 @@
|
|||||||
#outer {
|
.countdown {
|
||||||
position: absolute;
|
z-index: 8;
|
||||||
right: 0;
|
|
||||||
|
&.fullscreen {
|
||||||
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
background-color: green;
|
left: 0;
|
||||||
height: 30px;
|
width: 100%;
|
||||||
margin: 10px;
|
height: 100%;
|
||||||
margin-top: 100px;
|
z-index: 20;
|
||||||
z-index: 2;
|
display: grid;
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
font-size: 10vw;
|
||||||
|
display: inline-grid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.overlay {
|
||||||
|
position: relative;
|
||||||
|
float: right;
|
||||||
|
margin: 100px 10px 10px 10px;
|
||||||
|
padding: 10px 40px 7px 10px;
|
||||||
|
min-height: 72px;
|
||||||
|
min-width: 180px;
|
||||||
|
font-size: 3.7em;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: right;
|
||||||
|
border-radius: 7px 7px 7px 7px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border: 1px solid #e3e3e3;
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 18px;
|
||||||
|
padding-right: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||||
import { CountdownSlideData } from './countdown-slide-data';
|
import { CountdownSlideData } from './countdown-slide-data';
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { Injectable, Inject, Injector, NgModuleFactoryLoader, ComponentFactory, Type } from '@angular/core';
|
import { Injectable, Inject, Injector, NgModuleFactoryLoader, ComponentFactory, Type } from '@angular/core';
|
||||||
|
|
||||||
import { SlideManifest, SlideOptions } from '../slide-manifest';
|
import { SlideManifest, SlideDynamicConfiguration, Slide } from '../slide-manifest';
|
||||||
import { SLIDE } from '../slide-token';
|
import { SLIDE } from '../slide-token';
|
||||||
import { SLIDE_MANIFESTS } from '../slide-manifest';
|
import { SLIDE_MANIFESTS } from '../slide-manifest';
|
||||||
import { BaseSlideComponent } from '../base-slide-component';
|
import { BaseSlideComponent } from '../base-slide-component';
|
||||||
import { ProjectorElement, IdentifiableProjectorElement } from 'app/shared/models/core/projector';
|
import { ProjectorElement, IdentifiableProjectorElement } from 'app/shared/models/core/projector';
|
||||||
|
import { allSlidesDynamicConfiguration } from '../all-slide-configurations';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cares about loading slides dynamically.
|
* Cares about loading slides dynamically.
|
||||||
@ -12,6 +13,7 @@ import { ProjectorElement, IdentifiableProjectorElement } from 'app/shared/model
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class SlideManager {
|
export class SlideManager {
|
||||||
private loadedSlides: { [name: string]: SlideManifest } = {};
|
private loadedSlides: { [name: string]: SlideManifest } = {};
|
||||||
|
private loadedSlideConfigurations: { [name: string]: SlideDynamicConfiguration & Slide } = {};
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
@Inject(SLIDE_MANIFESTS) private manifests: SlideManifest[],
|
@Inject(SLIDE_MANIFESTS) private manifests: SlideManifest[],
|
||||||
@ -21,6 +23,9 @@ export class SlideManager {
|
|||||||
this.manifests.forEach(slideManifest => {
|
this.manifests.forEach(slideManifest => {
|
||||||
this.loadedSlides[slideManifest.slide] = slideManifest;
|
this.loadedSlides[slideManifest.slide] = slideManifest;
|
||||||
});
|
});
|
||||||
|
allSlidesDynamicConfiguration.forEach(config => {
|
||||||
|
this.loadedSlideConfigurations[config.slide] = config;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,12 +47,13 @@ export class SlideManager {
|
|||||||
* @param slideName The slide
|
* @param slideName The slide
|
||||||
* @returns SlideOptions for the requested slide.
|
* @returns SlideOptions for the requested slide.
|
||||||
*/
|
*/
|
||||||
public getSlideOptions(slideName: string): SlideOptions {
|
public getSlideConfiguration(slideName: string): SlideDynamicConfiguration {
|
||||||
return this.getManifest(slideName);
|
if (!this.loadedSlideConfigurations[slideName]) {
|
||||||
|
throw new Error(`Could not find slide for "${slideName}"`);
|
||||||
|
}
|
||||||
|
return this.loadedSlideConfigurations[slideName];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
public getIdentifialbeProjectorElement(element: ProjectorElement): IdentifiableProjectorElement {
|
public getIdentifialbeProjectorElement(element: ProjectorElement): IdentifiableProjectorElement {
|
||||||
const identifiableElement: IdentifiableProjectorElement = element as IdentifiableProjectorElement;
|
const identifiableElement: IdentifiableProjectorElement = element as IdentifiableProjectorElement;
|
||||||
const identifiers = this.getManifest(element.name).elementIdentifiers.map(x => x); // map to copy.
|
const identifiers = this.getManifest(element.name).elementIdentifiers.map(x => x); // map to copy.
|
||||||
|
@ -1,27 +1,32 @@
|
|||||||
import { InjectionToken } from '@angular/core';
|
import { InjectionToken } from '@angular/core';
|
||||||
import { IdentifiableProjectorElement } from 'app/shared/models/core/projector';
|
import { IdentifiableProjectorElement, ProjectorElement } from 'app/shared/models/core/projector';
|
||||||
|
|
||||||
|
type BooleanOrFunction = boolean | ((element: ProjectorElement) => boolean);
|
||||||
|
|
||||||
|
export interface Slide {
|
||||||
|
slide: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Slides can have these options.
|
* Slides can have these options.
|
||||||
*/
|
*/
|
||||||
export interface SlideOptions {
|
export interface SlideDynamicConfiguration {
|
||||||
/**
|
/**
|
||||||
* Should this slide be scrollable?
|
* Should this slide be scrollable?
|
||||||
*/
|
*/
|
||||||
scrollable: boolean;
|
scrollable: BooleanOrFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should this slide be scaleable?
|
* Should this slide be scaleable?
|
||||||
*/
|
*/
|
||||||
scaleable: boolean;
|
scaleable: BooleanOrFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is similar to router entries, so we can trick the router. Keep slideName and
|
* Is similar to router entries, so we can trick the router. Keep slideName and
|
||||||
* path in sync.
|
* path in sync.
|
||||||
*/
|
*/
|
||||||
export interface SlideManifest extends SlideOptions {
|
export interface SlideManifest extends Slide {
|
||||||
slide: string;
|
|
||||||
path: string;
|
path: string;
|
||||||
loadChildren: string;
|
loadChildren: string;
|
||||||
verboseName: string;
|
verboseName: string;
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
from ..utils.projector import AllData, register_projector_slide
|
from ..utils.projector import (
|
||||||
|
AllData,
|
||||||
|
ProjectorElementException,
|
||||||
|
get_config,
|
||||||
|
register_projector_slide,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Important: All functions have to be prune. This means, that thay can only
|
# Important: All functions have to be prune. This means, that thay can only
|
||||||
@ -23,9 +28,16 @@ def countdown_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any
|
|||||||
countdown_id = element.get("id") or 1
|
countdown_id = element.get("id") or 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return all_data["core/countdown"][countdown_id]
|
countdown = all_data["core/countdown"][countdown_id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return {"error": f"Countdown {countdown_id} does not exist"}
|
raise ProjectorElementException(f"Countdown {countdown_id} does not exist")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"description": countdown["description"],
|
||||||
|
"running": countdown["running"],
|
||||||
|
"countdown_time": countdown["countdown_time"],
|
||||||
|
"warning_time": get_config(all_data, "agenda_countdown_warning_time"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def message_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
|
def message_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
@ -44,7 +56,7 @@ def message_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
try:
|
try:
|
||||||
return all_data["core/projector-message"][message_id]
|
return all_data["core/projector-message"][message_id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return {"error": f"Message {message_id} does not exist"}
|
raise ProjectorElementException(f"Message {message_id} does not exist")
|
||||||
|
|
||||||
|
|
||||||
def clock_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
|
def clock_slide(all_data: AllData, element: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
Loading…
Reference in New Issue
Block a user