diff --git a/client/src/app/core/core-services/projector.service.ts b/client/src/app/core/core-services/projector.service.ts index 550cc86d4..e7dd36ac1 100644 --- a/client/src/app/core/core-services/projector.service.ts +++ b/client/src/app/core/core-services/projector.service.ts @@ -52,9 +52,9 @@ export class ProjectorService { obj: Projectable | ProjectorElementBuildDeskriptor | IdentifiableProjectorElement ): IdentifiableProjectorElement { if (isProjectable(obj)) { - return obj.getSlide().getBasicProjectorElement(); + return obj.getSlide().getBasicProjectorElement({}); } else if (isProjectorElementBuildDeskriptor(obj)) { - return obj.getBasicProjectorElement(); + return obj.getBasicProjectorElement({}); } else { return obj; } @@ -133,7 +133,9 @@ export class ProjectorService { const element = this.getProjectorElement(obj); 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); await this.projectRequest(projector, projector.elements); } else { diff --git a/client/src/app/core/repositories/projector/countdown-repository.service.ts b/client/src/app/core/repositories/projector/countdown-repository.service.ts index b978f1518..699498296 100644 --- a/client/src/app/core/repositories/projector/countdown-repository.service.ts +++ b/client/src/app/core/repositories/projector/countdown-repository.service.ts @@ -8,6 +8,7 @@ import { ViewCountdown } from 'app/site/projector/models/view-countdown'; import { Countdown } from 'app/shared/models/core/countdown'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { TranslateService } from '@ngx-translate/core'; +import { ServertimeService } from 'app/core/core-services/servertime.service'; @Injectable({ providedIn: 'root' @@ -18,7 +19,8 @@ export class CountdownRepositoryService extends BaseRepository { - await this.dataSend.deleteModel(viewCountdown.countdown); + public async delete(countdown: ViewCountdown): Promise { + await this.dataSend.deleteModel(countdown.countdown); + } + + public async start(countdown: ViewCountdown): Promise { + const endTime = this.servertimeService.getServertime() / 1000 + countdown.countdown_time; + await this.update({ running: true, countdown_time: endTime }, countdown); + } + + public async stop(countdown: ViewCountdown): Promise { + await this.update({ running: false, countdown_time: countdown.default_time }, countdown); + } + + public async pause(countdown: ViewCountdown): Promise { + const endTime = countdown.countdown_time - this.servertimeService.getServertime() / 1000; + await this.update({ running: false, countdown_time: endTime }, countdown); } } diff --git a/client/src/app/core/ui-services/duration.service.ts b/client/src/app/core/ui-services/duration.service.ts index 9ccccaa00..286f7e029 100644 --- a/client/src/app/core/ui-services/duration.service.ts +++ b/client/src/app/core/ui-services/duration.service.ts @@ -12,6 +12,9 @@ import { Injectable } from '@angular/core'; * // will also result in 70 * 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 * const c = this.durationService.stringToDuration('01:10b'); * ``` @@ -20,6 +23,9 @@ import { Injectable } from '@angular/core'; * ```ts * // will result in 01:10 h * const a = this.durationService.durationToString(70); + * + * // will result in 00:30 m (30 is interpreted as seconds) + * const a = this.durationService.durationToString(30); * ``` */ @Injectable({ @@ -32,13 +38,16 @@ export class DurationService { 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 - * @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 { - const splitDuration = durationText.replace('h', '').split(':'); + public stringToDuration(durationText: string, suffix: 'h' | 'm' = 'h'): number { + const splitDuration = durationText.replace(suffix, '').split(':'); let time: number; if (splitDuration.length > 1 && !isNaN(+splitDuration[0]) && !isNaN(+splitDuration[1])) { time = +splitDuration[0] * 60 + +splitDuration[1]; @@ -54,31 +63,18 @@ export class DurationService { } /** - * Converts a duration number (given in minutes) - * To a string in HH:MM format + * Converts a duration number (given in minutes or seconds) * * @param duration value in minutes * @returns a more human readable time representation */ - public durationToString(duration: number): string { - const hours = Math.floor(duration / 60); - const minutes = `0${Math.floor(duration - hours * 60)}`.slice(-2); - if (!isNaN(+hours) && !isNaN(+minutes)) { - return `${hours}:${minutes} h`; + public durationToString(duration: number, suffix: 'h' | 'm' = 'h'): string { + const major = Math.floor(duration / 60); + const minor = `0${duration % 60}`.slice(-2); + if (!isNaN(+major) && !isNaN(+minor)) { + return `${major}:${minor} ${suffix}`; } else { 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)}`; - } } diff --git a/client/src/app/shared/components/contdown-time/countdown-time.component.html b/client/src/app/shared/components/contdown-time/countdown-time.component.html new file mode 100644 index 000000000..6ea9552c7 --- /dev/null +++ b/client/src/app/shared/components/contdown-time/countdown-time.component.html @@ -0,0 +1,5 @@ +
+ {{ time }} +
diff --git a/client/src/app/shared/components/contdown-time/countdown-time.component.scss b/client/src/app/shared/components/contdown-time/countdown-time.component.scss new file mode 100644 index 000000000..0d64a1944 --- /dev/null +++ b/client/src/app/shared/components/contdown-time/countdown-time.component.scss @@ -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; + } +} diff --git a/client/src/app/shared/components/contdown-time/countdown-time.component.spec.ts b/client/src/app/shared/components/contdown-time/countdown-time.component.spec.ts new file mode 100644 index 000000000..063ca2410 --- /dev/null +++ b/client/src/app/shared/components/contdown-time/countdown-time.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [E2EImportsModule] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CountdownTimeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/shared/components/contdown-time/countdown-time.component.ts b/client/src/app/shared/components/contdown-time/countdown-time.component.ts new file mode 100644 index 000000000..caf847ad3 --- /dev/null +++ b/client/src/app/shared/components/contdown-time/countdown-time.component.ts @@ -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); + } + } +} diff --git a/client/src/app/shared/components/projection-dialog/projection-dialog.component.html b/client/src/app/shared/components/projection-dialog/projection-dialog.component.html index 6137ae627..afbfbe1d4 100644 --- a/client/src/app/shared/components/projection-dialog/projection-dialog.component.html +++ b/client/src/app/shared/components/projection-dialog/projection-dialog.component.html @@ -17,15 +17,15 @@
{{ option.displayName | translate }}

{{ option.displayName | translate }}

- + {{ choice.displayName | translate }} diff --git a/client/src/app/shared/components/projection-dialog/projection-dialog.component.ts b/client/src/app/shared/components/projection-dialog/projection-dialog.component.ts index 4997d665e..dbbd05d55 100644 --- a/client/src/app/shared/components/projection-dialog/projection-dialog.component.ts +++ b/client/src/app/shared/components/projection-dialog/projection-dialog.component.ts @@ -26,7 +26,7 @@ export type ProjectionDialogReturnType = [Projector[], IdentifiableProjectorElem export class ProjectionDialogComponent { public projectors: Projector[]; private selectedProjectors: Projector[] = []; - public projectorElement: IdentifiableProjectorElement; + public optionValues: object = {}; public options: SlideOptions; public constructor( @@ -52,11 +52,9 @@ export class ProjectionDialogComponent { } } - this.projectorElement = this.projectorElementBuildDescriptor.getBasicProjectorElement(); - // Set option defaults this.projectorElementBuildDescriptor.slideOptions.forEach(option => { - this.projectorElement[option.key] = option.default; + this.optionValues[option.key] = option.default; }); this.options = this.projectorElementBuildDescriptor.slideOptions; @@ -88,7 +86,9 @@ export class ProjectionDialogComponent { } 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 { diff --git a/client/src/app/shared/components/slide-container/slide-container.component.scss b/client/src/app/shared/components/slide-container/slide-container.component.scss index ff8d831f2..afb68000a 100644 --- a/client/src/app/shared/components/slide-container/slide-container.component.scss +++ b/client/src/app/shared/components/slide-container/slide-container.component.scss @@ -4,8 +4,9 @@ &.content { width: calc(100% - 100px); - margin-left: 50px; - margin-right: 50px; + position: absolute; + left: 50px; + top: 0; } } diff --git a/client/src/app/shared/components/slide-container/slide-container.component.ts b/client/src/app/shared/components/slide-container/slide-container.component.ts index 31126fa46..0fd8979cf 100644 --- a/client/src/app/shared/components/slide-container/slide-container.component.ts +++ b/client/src/app/shared/components/slide-container/slide-container.component.ts @@ -5,10 +5,13 @@ import { TranslateService } from '@ngx-translate/core'; import { BaseComponent } from 'app/base.component'; import { SlideManager } from 'app/slides/services/slide-manager.service'; 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 { 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, * and loading of slides. @@ -31,24 +34,31 @@ export class SlideContainerComponent extends BaseComponent { private _slideData: SlideData; @Input() - public set slideData(data: SlideData) { + public set slideData(slideData: SlideData) { // 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: if (this.slide) { this.slide.clear(); } - if (data.error) { - console.error(data.error); + let error; + if (hasError(slideData)) { + error = slideData.error; + } else if (slideData.data && hasError(slideData.data)) { + error = slideData.data.error; + } + + if (error) { + console.log(error); } return; } - this._slideData = data; - if (this.previousSlideName !== data.element.name) { - this.slideChanged(data.element.name); - this.previousSlideName = data.element.name; + this._slideData = slideData; + if (this.previousSlideName !== slideData.element.name) { + this.slideChanged(slideData.element); + this.previousSlideName = slideData.element.name; } this.setDataForComponent(); } @@ -88,7 +98,7 @@ export class SlideContainerComponent extends BaseComponent { /** * 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. @@ -137,9 +147,19 @@ export class SlideContainerComponent extends BaseComponent { * * @param slideName The slide to load. */ - private slideChanged(slideName: string): void { - this.slideOptions = this.slideManager.getSlideOptions(slideName); - this.slideManager.getSlideFactory(slideName).then(slideFactory => { + private slideChanged(element: ProjectorElement): void { + const options = this.slideManager.getSlideConfiguration(element.name); + 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.slideRef = this.slide.createComponent(slideFactory); this.setDataForComponent(); diff --git a/client/src/app/shared/models/core/projector.ts b/client/src/app/shared/models/core/projector.ts index c8ae50056..4d0cf4a8f 100644 --- a/client/src/app/shared/models/core/projector.ts +++ b/client/src/app/shared/models/core/projector.ts @@ -1,11 +1,18 @@ import { BaseModel } from '../base/base-model'; +export interface ProjectorElementOptions { + /** + * Additional data. + */ + [key: string]: any; +} + /** * A projectorelement must have a name and optional attributes. * 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. */ -export interface ProjectorElement { +export interface ProjectorElement extends ProjectorElementOptions { /** * The name of the element. */ @@ -16,11 +23,6 @@ export interface ProjectorElement { * DO NOT read additional data (name is save). */ error?: string; - - /** - * Additional data. - */ - [key: string]: any; } export interface IdentifiableProjectorElement extends ProjectorElement { diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index c72e6f4a2..302f930d3 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts @@ -82,6 +82,7 @@ import { MetaTextBlockComponent } from './components/meta-text-block/meta-text-b import { OpenSlidesTranslateModule } from '../core/translate/openslides-translate-module'; import { ProjectorComponent } from './components/projector/projector.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. @@ -201,7 +202,8 @@ import { SlideContainerComponent } from './components/slide-container/slide-cont ProjectorComponent, SlideContainerComponent, OwlDateTimeModule, - OwlNativeDateTimeModule + OwlNativeDateTimeModule, + CountdownTimeComponent ], declarations: [ PermsDirective, @@ -227,7 +229,8 @@ import { SlideContainerComponent } from './components/slide-container/slide-cont ResizedDirective, MetaTextBlockComponent, ProjectorComponent, - SlideContainerComponent + SlideContainerComponent, + CountdownTimeComponent ], providers: [ { provide: DateAdapter, useClass: OpenSlidesDateAdapter }, diff --git a/client/src/app/site/agenda/components/speaker-list/speaker-list.component.ts b/client/src/app/site/agenda/components/speaker-list/speaker-list.component.ts index b5f65ecd3..60700b136 100644 --- a/client/src/app/site/agenda/components/speaker-list/speaker-list.component.ts +++ b/client/src/app/site/agenda/components/speaker-list/speaker-list.component.ts @@ -342,6 +342,6 @@ export class SpeakerListComponent extends BaseViewComponent implements OnInit { const duration = Math.floor( (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')}`; } } diff --git a/client/src/app/site/agenda/models/view-topic.ts b/client/src/app/site/agenda/models/view-topic.ts index 1e02bd80e..aa5baf20e 100644 --- a/client/src/app/site/agenda/models/view-topic.ts +++ b/client/src/app/site/agenda/models/view-topic.ts @@ -97,7 +97,7 @@ export class ViewTopic extends BaseAgendaViewModel { public getSlide(): ProjectorElementBuildDeskriptor { return { - getBasicProjectorElement: () => ({ + getBasicProjectorElement: options => ({ name: Topic.COLLECTIONSTRING, id: this.id, getIdentifiers: () => ['name', 'id'] diff --git a/client/src/app/site/assignments/models/view-assignment.ts b/client/src/app/site/assignments/models/view-assignment.ts index 5e5e3131e..f10bca171 100644 --- a/client/src/app/site/assignments/models/view-assignment.ts +++ b/client/src/app/site/assignments/models/view-assignment.ts @@ -81,7 +81,7 @@ export class ViewAssignment extends BaseAgendaViewModel { public getSlide(): ProjectorElementBuildDeskriptor { return { - getBasicProjectorElement: () => ({ + getBasicProjectorElement: options => ({ name: Assignment.COLLECTIONSTRING, id: this.id, getIdentifiers: () => ['name', 'id'] diff --git a/client/src/app/site/base/projectable.ts b/client/src/app/site/base/projectable.ts index 182f6c58a..a0d4e8b2b 100644 --- a/client/src/app/site/base/projectable.ts +++ b/client/src/app/site/base/projectable.ts @@ -1,5 +1,5 @@ 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'; export function isProjectorElementBuildDeskriptor(obj: any): obj is ProjectorElementBuildDeskriptor { @@ -15,7 +15,7 @@ export function isProjectorElementBuildDeskriptor(obj: any): obj is ProjectorEle export interface ProjectorElementBuildDeskriptor { slideOptions: SlideOptions; projectionDefaultName?: string; - getBasicProjectorElement(): IdentifiableProjectorElement; + getBasicProjectorElement(options: ProjectorElementOptions): IdentifiableProjectorElement; /** * The title to show in the projection dialog diff --git a/client/src/app/site/base/slide-options.ts b/client/src/app/site/base/slide-options.ts index 3693fb40a..a47083d28 100644 --- a/client/src/app/site/base/slide-options.ts +++ b/client/src/app/site/base/slide-options.ts @@ -1,10 +1,14 @@ -export interface SlideDecisionOption { +interface BaseSlideOption { key: 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 }[]; } diff --git a/client/src/app/site/motions/models/view-motion.ts b/client/src/app/site/motions/models/view-motion.ts index 14bc08797..139d87e2c 100644 --- a/client/src/app/site/motions/models/view-motion.ts +++ b/client/src/app/site/motions/models/view-motion.ts @@ -561,7 +561,7 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable { public getSlide(): ProjectorElementBuildDeskriptor { return { - getBasicProjectorElement: () => ({ + getBasicProjectorElement: options => ({ name: Motion.COLLECTIONSTRING, id: this.id, getIdentifiers: () => ['name', 'id'] diff --git a/client/src/app/site/projector/components/countdown-controls/countdown-controls.component.html b/client/src/app/site/projector/components/countdown-controls/countdown-controls.component.html new file mode 100644 index 000000000..c2313286e --- /dev/null +++ b/client/src/app/site/projector/components/countdown-controls/countdown-controls.component.html @@ -0,0 +1,12 @@ +
+ + + + +
diff --git a/client/src/app/site/projector/components/countdown-controls/countdown-controls.component.spec.ts b/client/src/app/site/projector/components/countdown-controls/countdown-controls.component.spec.ts new file mode 100644 index 000000000..b71c6a561 --- /dev/null +++ b/client/src/app/site/projector/components/countdown-controls/countdown-controls.component.spec.ts @@ -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; + + 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(); + }); +}); diff --git a/client/src/app/site/projector/components/countdown-controls/countdown-controls.component.ts b/client/src/app/site/projector/components/countdown-controls/countdown-controls.component.ts new file mode 100644 index 000000000..23248e19d --- /dev/null +++ b/client/src/app/site/projector/components/countdown-controls/countdown-controls.component.ts @@ -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('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; + } +} diff --git a/client/src/app/site/projector/components/countdown-list/countdown-list.component.html b/client/src/app/site/projector/components/countdown-list/countdown-list.component.html index 127439691..caf500e12 100644 --- a/client/src/app/site/projector/components/countdown-list/countdown-list.component.html +++ b/client/src/app/site/projector/components/countdown-list/countdown-list.component.html @@ -18,6 +18,13 @@ Required +

+ + + + Required + +

@@ -45,8 +52,10 @@
{{ countdown.description }}
+
+ +
-
Required +

+ + + + Required + +

- - TODO: Show countdown time etc. -