From 233961b4666bb526bddb072c76cc50f2ac1e0970 Mon Sep 17 00:00:00 2001 From: Sean Engelhardt Date: Wed, 23 Oct 2019 12:22:52 +0200 Subject: [PATCH] Enhance projector list - The projector list now scales to give a better overview - selecting the projector for the CLOS reference is more intuitive - editing and creating projectors now works over a dialog - editing projectors is now possible from the detail page - projector tiles look overall cleaner - Editing the projector offers a preview - no changes "on the fly" - Dialog has apply button to allow saving without closing - The slider has an input fild on the right side to allow the usage of specific values --- .../fullscreen-projector.component.html | 2 +- .../projector/projector.component.ts | 67 +++-- .../message-dialog.component.spec.ts | 10 +- .../projector-detail.component.html | 2 +- .../projector-detail.component.ts | 35 ++- .../projector-edit-dialog.component.html | 177 ++++++++++++ .../projector-edit-dialog.component.scss | 56 ++++ .../projector-edit-dialog.component.spec.ts | 41 +++ .../projector-edit-dialog.component.ts | 258 ++++++++++++++++++ .../projector-list-entry.component.html | 238 +--------------- .../projector-list-entry.component.scss | 46 ++-- .../projector-list-entry.component.ts | 216 +-------------- .../projector-list.component.html | 66 ++--- .../projector-list.component.scss | 13 +- .../projector-list.component.ts | 84 ++---- .../site/projector/models/view-projector.ts | 4 + .../app/site/projector/projector.module.ts | 7 +- 17 files changed, 717 insertions(+), 605 deletions(-) create mode 100644 client/src/app/site/projector/components/projector-edit-dialog/projector-edit-dialog.component.html create mode 100644 client/src/app/site/projector/components/projector-edit-dialog/projector-edit-dialog.component.scss create mode 100644 client/src/app/site/projector/components/projector-edit-dialog/projector-edit-dialog.component.spec.ts create mode 100644 client/src/app/site/projector/components/projector-edit-dialog/projector-edit-dialog.component.ts diff --git a/client/src/app/fullscreen-projector/fullscreen-projector/fullscreen-projector.component.html b/client/src/app/fullscreen-projector/fullscreen-projector/fullscreen-projector.component.html index f2b3d816d..5ab3e800e 100644 --- a/client/src/app/fullscreen-projector/fullscreen-projector/fullscreen-projector.component.html +++ b/client/src/app/fullscreen-projector/fullscreen-projector/fullscreen-projector.component.html @@ -1,5 +1,5 @@
- +
diff --git a/client/src/app/shared/components/projector/projector.component.ts b/client/src/app/shared/components/projector/projector.component.ts index df4dd3d6a..995e48e82 100644 --- a/client/src/app/shared/components/projector/projector.component.ts +++ b/client/src/app/shared/components/projector/projector.component.ts @@ -9,6 +9,7 @@ import { OfflineService } from 'app/core/core-services/offline.service'; import { ProjectorDataService, SlideData } from 'app/core/core-services/projector-data.service'; import { ProjectorRepositoryService } from 'app/core/repositories/projector/projector-repository.service'; import { ConfigService } from 'app/core/ui-services/config.service'; +import { Projector } from 'app/shared/models/core/projector'; import { ViewProjector } from 'app/site/projector/models/view-projector'; import { Size } from 'app/site/projector/size'; @@ -27,41 +28,14 @@ export class ProjectorComponent extends BaseComponent implements OnDestroy { */ private projectorId: number | null = null; - /** - * The projector. Accessors are below. - */ - private _projector: ViewProjector; - @Input() public set projector(projector: ViewProjector) { this._projector = projector; - // check, if ID changed: - const newId = projector ? projector.id : null; - if (this.projectorId !== newId) { - this.projectorIdChanged(this.projectorId, newId); - this.projectorId = newId; - } - - // Update scaling, if projector is set. - if (projector) { - const oldSize: Size = { ...this.currentProjectorSize }; - this.currentProjectorSize.height = projector.height; - this.currentProjectorSize.width = projector.width; - if ( - oldSize.height !== this.currentProjectorSize.height || - oldSize.width !== this.currentProjectorSize.width - ) { - this.updateScaling(); - } - this.css.projector.color = projector.color; - this.css.projector.backgroundColor = projector.background_color; - this.css.projector.H1Color = this.projector.header_h1_color; - this.css.headerFooter.color = projector.header_font_color; - this.css.headerFooter.backgroundColor = projector.header_background_color; - this.updateCSS(); - } + this.setProjector(projector.projector); } + private _projector: ViewProjector; + public get projector(): ViewProjector { return this._projector; } @@ -240,6 +214,39 @@ export class ProjectorComponent extends BaseComponent implements OnDestroy { this.offlineSubscription = this.offlineService.isOffline().subscribe(isOffline => (this.isOffline = isOffline)); } + /** + * Regular routine to set a projector + * + * @param projector + */ + public setProjector(projector: Projector): void { + // check, if ID changed: + const newId = projector ? projector.id : null; + if (this.projectorId !== newId) { + this.projectorIdChanged(this.projectorId, newId); + this.projectorId = newId; + } + + // Update scaling, if projector is set. + if (projector) { + const oldSize: Size = { ...this.currentProjectorSize }; + this.currentProjectorSize.height = projector.height; + this.currentProjectorSize.width = projector.width; + if ( + oldSize.height !== this.currentProjectorSize.height || + oldSize.width !== this.currentProjectorSize.width + ) { + this.updateScaling(); + } + this.css.projector.color = projector.color; + this.css.projector.backgroundColor = projector.background_color; + this.css.projector.H1Color = projector.header_h1_color; + this.css.headerFooter.color = projector.header_font_color; + this.css.headerFooter.backgroundColor = projector.header_background_color; + this.updateCSS(); + } + } + /** * Scales the projector to the right format. */ diff --git a/client/src/app/site/projector/components/message-dialog/message-dialog.component.spec.ts b/client/src/app/site/projector/components/message-dialog/message-dialog.component.spec.ts index 6ae180c76..365d9fd66 100644 --- a/client/src/app/site/projector/components/message-dialog/message-dialog.component.spec.ts +++ b/client/src/app/site/projector/components/message-dialog/message-dialog.component.spec.ts @@ -3,15 +3,15 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { E2EImportsModule } from 'e2e-imports.module'; -import { MessageData, MessageDialogComponent } from './message-dialog.component'; +import { MessageDialogComponent } from './message-dialog.component'; describe('MessageDialogComponent', () => { let component: MessageDialogComponent; let fixture: ComponentFixture; - const dialogData: MessageData = { - text: '' - }; + // const dialogData: MessageData = { + // text: '' + // }; beforeEach(async(() => { TestBed.configureTestingModule({ @@ -21,7 +21,7 @@ describe('MessageDialogComponent', () => { { provide: MatDialogRef, useValue: {} }, { provide: MAT_DIALOG_DATA, - useValue: dialogData + useValue: {} } ] }).compileComponents(); diff --git a/client/src/app/site/projector/components/projector-detail/projector-detail.component.html b/client/src/app/site/projector/components/projector-detail/projector-detail.component.html index 497cf0deb..d1840e9ab 100644 --- a/client/src/app/site/projector/components/projector-detail/projector-detail.component.html +++ b/client/src/app/site/projector/components/projector-detail/projector-detail.component.html @@ -1,4 +1,4 @@ - +

{{ projector?.name | translate }}

diff --git a/client/src/app/site/projector/components/projector-detail/projector-detail.component.ts b/client/src/app/site/projector/components/projector-detail/projector-detail.component.ts index b67595f6e..abd4af1dc 100644 --- a/client/src/app/site/projector/components/projector-detail/projector-detail.component.ts +++ b/client/src/app/site/projector/components/projector-detail/projector-detail.component.ts @@ -20,7 +20,7 @@ import { SizeObject } from 'app/shared/components/tile/tile.component'; import { Countdown } from 'app/shared/models/core/countdown'; import { ProjectorElement } from 'app/shared/models/core/projector'; import { ProjectorMessage } from 'app/shared/models/core/projector-message'; -import { infoDialogSettings, mediumDialogSettings } from 'app/shared/utils/dialog-settings'; +import { infoDialogSettings, largeDialogSettings } from 'app/shared/utils/dialog-settings'; import { BaseViewComponent } from 'app/site/base/base-view'; import { Projectable } from 'app/site/base/projectable'; import { ViewCountdown } from 'app/site/projector/models/view-countdown'; @@ -30,6 +30,7 @@ import { CountdownData, CountdownDialogComponent } from '../countdown-dialog/cou import { CurrentListOfSpeakersSlideService } from '../../services/current-list-of-of-speakers-slide.service'; import { CurrentSpeakerChyronSlideService } from '../../services/current-speaker-chyron-slide.service'; import { MessageData, MessageDialogComponent } from '../message-dialog/message-dialog.component'; +import { ProjectorEditDialogComponent } from '../projector-edit-dialog/projector-edit-dialog.component'; import { ViewProjector } from '../../models/view-projector'; /** @@ -106,18 +107,34 @@ export class ProjectorDetailComponent extends BaseViewComponent implements OnIni public ngOnInit(): void { this.route.params.subscribe(params => { const projectorId = parseInt(params.id, 10) || 1; - this.repo.getViewModelObservable(projectorId).subscribe(projector => { - if (projector) { - const title = projector.name; - super.setTitle(title); - this.projector = projector; - } - }); + + this.subscriptions.push( + this.repo.getViewModelObservable(projectorId).subscribe(projector => { + if (projector) { + const title = projector.name; + super.setTitle(title); + this.projector = projector; + } + }) + ); }); this.subscriptions.push(timer(0, 500).subscribe(() => this.cd.detectChanges())); } + public editProjector(): void { + const dialogRef = this.dialog.open(ProjectorEditDialogComponent, { + data: this.projector, + ...largeDialogSettings + }); + + dialogRef.afterClosed().subscribe(event => { + if (event) { + this.cd.detectChanges(); + } + }); + } + /** * Change the scroll * @@ -252,7 +269,7 @@ export class ProjectorDetailComponent extends BaseViewComponent implements OnIni const dialogRef = this.dialog.open(MessageDialogComponent, { data: messageData, - ...mediumDialogSettings + ...largeDialogSettings }); dialogRef.afterClosed().subscribe(result => { diff --git a/client/src/app/site/projector/components/projector-edit-dialog/projector-edit-dialog.component.html b/client/src/app/site/projector/components/projector-edit-dialog/projector-edit-dialog.component.html new file mode 100644 index 000000000..5ea9fe1d7 --- /dev/null +++ b/client/src/app/site/projector/components/projector-edit-dialog/projector-edit-dialog.component.html @@ -0,0 +1,177 @@ +

+ Edit Projector +

+ +
+
+
+ + + + + Required + + + +

Resolution and size

+ + + + {{ ratio }} + + + +
+ +
+ + + +
+
+ + +
+
+ + Show header and footer + +
+
+ + Show title + +
+
+ + Show logo + +
+
+ + Show clock + +
+
+ + +

Projection defaults

+ + + {{ pd.getTitle() | translate }} + + + + +
+ + +
+
+ + {{ title | translate }} + + +
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ + + + +
+
+
+

Preview

+
+ +
+
+
diff --git a/client/src/app/site/projector/components/projector-edit-dialog/projector-edit-dialog.component.scss b/client/src/app/site/projector/components/projector-edit-dialog/projector-edit-dialog.component.scss new file mode 100644 index 000000000..eeb4cf610 --- /dev/null +++ b/client/src/app/site/projector/components/projector-edit-dialog/projector-edit-dialog.component.scss @@ -0,0 +1,56 @@ +@import '~assets/styles/variables.scss'; + +form { + overflow: hidden; +} + +.settings-grid { + display: grid; + grid-gap: 10px; + + @include desktop { + grid-template-columns: 40% 60%; + } + + .mat-form-field { + width: 100%; + } +} + +.grid-form { + width: 100%; + display: grid; + grid-template-columns: auto 50px; + + .grid-start { + grid-column-start: 1; + grid-column-end: 1; + width: 100%; + } + + .grid-end { + grid-column-start: 2; + grid-column-end: 2; + margin: auto 0; + } + + .color-picker-form { + .mat-form-field-wrapper { + padding-bottom: 0; + } + } + + .mat-form-field-underline { + display: none; + } +} + +.no-markup { + /* Do not let the a tag ruin the projector */ + color: inherit; + text-decoration: inherit; +} + +.mat-dialog-actions { + display: block; +} diff --git a/client/src/app/site/projector/components/projector-edit-dialog/projector-edit-dialog.component.spec.ts b/client/src/app/site/projector/components/projector-edit-dialog/projector-edit-dialog.component.spec.ts new file mode 100644 index 000000000..4553c82e8 --- /dev/null +++ b/client/src/app/site/projector/components/projector-edit-dialog/projector-edit-dialog.component.spec.ts @@ -0,0 +1,41 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; + +import { E2EImportsModule } from 'e2e-imports.module'; + +import { ProjectorEditDialogComponent } from './projector-edit-dialog.component'; + +describe('ProjectorEditDialogComponent', () => { + let component: ProjectorEditDialogComponent; + let fixture: ComponentFixture; + + /** + * A view model has to be injected here, hence it's currently not possbile (anymore) + * to mock the creation of view models + */ + const dialogData = null; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ProjectorEditDialogComponent], + imports: [E2EImportsModule], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { + provide: MAT_DIALOG_DATA, + useValue: dialogData + } + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ProjectorEditDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/site/projector/components/projector-edit-dialog/projector-edit-dialog.component.ts b/client/src/app/site/projector/components/projector-edit-dialog/projector-edit-dialog.component.ts new file mode 100644 index 000000000..9830933a4 --- /dev/null +++ b/client/src/app/site/projector/components/projector-edit-dialog/projector-edit-dialog.component.ts @@ -0,0 +1,258 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Inject, + OnInit, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { MAT_DIALOG_DATA, MatDialogRef, MatRadioChange, MatSnackBar } from '@angular/material'; +import { Title } from '@angular/platform-browser'; + +import { TranslateService } from '@ngx-translate/core'; +import { auditTime } from 'rxjs/operators'; + +import { ProjectionDefaultRepositoryService } from 'app/core/repositories/projector/projection-default-repository.service'; +import { ProjectorRepositoryService } from 'app/core/repositories/projector/projector-repository.service'; +import { ProjectorComponent } from 'app/shared/components/projector/projector.component'; +import { Projector } from 'app/shared/models/core/projector'; +import { BaseViewComponent } from 'app/site/base/base-view'; +import { ClockSlideService } from '../../services/clock-slide.service'; +import { ViewProjectionDefault } from '../../models/view-projection-default'; +import { ViewProjector } from '../../models/view-projector'; + +/** + * All supported aspect rations for projectors. + */ +const aspectRatios: { [ratio: string]: number } = { + '4:3': 4 / 3, + '16:9': 16 / 9, + '16:10': 16 / 10, + '30:9': 30 / 9 +}; + +const aspectRatio_30_9_MinWidth = 1150; + +/** + * Dialog to edit the given projector + * Shows a preview + */ +@Component({ + selector: 'os-projector-edit-dialog', + templateUrl: './projector-edit-dialog.component.html', + styleUrls: ['./projector-edit-dialog.component.scss'], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ProjectorEditDialogComponent extends BaseViewComponent implements OnInit { + /** + * import the projector as view child, to determine when to update + * the preview. + */ + @ViewChild('preview', { static: false }) + public preview: ProjectorComponent; + + /** + * The update form. Will be refreahed for each projector. Just one update + * form can be shown per time. + */ + public updateForm: FormGroup; + + /** + * All aspect ratio keys/strings for the UI. + */ + public aspectRatiosKeys: string[]; + + /** + * All ProjectionDefaults to select from. + */ + public projectionDefaults: ViewProjectionDefault[]; + + /** + * show a preview of the changes + */ + public previewProjector: Projector; + + /** + * define the maximum resolution + */ + public maxResolution = 2000; + + /** + * Define the step of resolution changes + */ + public resolutionChangeStep = 10; + + public constructor( + title: Title, + translate: TranslateService, + matSnackBar: MatSnackBar, + formBuilder: FormBuilder, + @Inject(MAT_DIALOG_DATA) public projector: ViewProjector, + private dialogRef: MatDialogRef, + private repo: ProjectorRepositoryService, + private projectionDefaultRepo: ProjectionDefaultRepositoryService, + private clockSlideService: ClockSlideService, + private cd: ChangeDetectorRef + ) { + super(title, translate, matSnackBar); + this.aspectRatiosKeys = Object.keys(aspectRatios); + + if (projector) { + this.previewProjector = new Projector(projector.getModel()); + } + + this.updateForm = formBuilder.group({ + name: ['', Validators.required], + aspectRatio: ['', Validators.required], + width: [0, Validators.required], + projectiondefaults_id: [[]], + clock: [true], + color: ['', Validators.required], + background_color: ['', Validators.required], + header_background_color: ['', Validators.required], + header_font_color: ['', Validators.required], + header_h1_color: ['', Validators.required], + chyron_background_color: ['', Validators.required], + chyron_font_color: ['', Validators.required], + show_header_footer: [], + show_title: [], + show_logo: [] + }); + + // react to form changes + this.subscriptions.push( + this.updateForm.valueChanges.pipe(auditTime(100)).subscribe(() => { + this.onChangeForm(); + }) + ); + } + + /** + * Watches all projection defaults + */ + public ngOnInit(): void { + this.projectionDefaults = this.projectionDefaultRepo.getViewModelList(); + this.subscriptions.push( + this.projectionDefaultRepo.getViewModelListObservable().subscribe(pds => (this.projectionDefaults = pds)) + ); + + if (this.projector) { + this.updateForm.patchValue(this.projector.projector); + this.updateForm.patchValue({ + name: this.translate.instant(this.projector.name), + aspectRatio: this.getAspectRatioKey(), + clock: this.clockSlideService.isProjectedOn(this.projector) + }); + + this.subscriptions.push( + this.repo.getViewModelObservable(this.projector.id).subscribe(update => { + // patches the projector with updated values + const projectorPatch = {}; + Object.keys(this.updateForm.controls).forEach(ctrl => { + if (update[ctrl]) { + projectorPatch[ctrl] = update[ctrl]; + } + }); + this.updateForm.patchValue(projectorPatch); + }) + ); + } + } + + /** + * Apply changes and close the dialog + */ + public async onSubmitProjector(): Promise { + await this.applyChanges(); + this.dialogRef.close(true); + } + + /** + * Saves the current changes on the projector + */ + public async applyChanges(): Promise { + const updateProjector: Partial = this.updateForm.value; + updateProjector.height = this.calcHeight(this.updateForm.value.width, this.updateForm.value.aspectRatio); + try { + await this.clockSlideService.setProjectedOn(this.projector, this.updateForm.value.clock); + await this.repo.update(updateProjector, this.projector); + } catch (e) { + this.raiseError(e); + } + } + + /** + * React to form changes to update the preview + * @param previewUpdate + */ + public onChangeForm(): void { + if (this.previewProjector && this.projector) { + Object.assign(this.previewProjector, this.updateForm.value); + this.previewProjector.height = this.calcHeight( + this.updateForm.value.width, + this.updateForm.value.aspectRatio + ); + this.preview.setProjector(this.previewProjector); + this.cd.markForCheck(); + } + } + + /** + * Helper to calc height + * @param width + * @param aspectRatio + */ + private calcHeight(width: number, aspectRatio: string): number { + return Math.round(width / aspectRatios[aspectRatio]); + } + + /** + * Resets the given form field to the given default. + */ + public resetField(field: string): void { + const patchValue = {}; + patchValue[field] = this.projector[field]; + this.updateForm.patchValue(patchValue); + } + + public aspectRatioChanged(event: MatRadioChange): void { + let width: number; + if (event.value === '30:9' && this.updateForm.value.width < aspectRatio_30_9_MinWidth) { + width = aspectRatio_30_9_MinWidth; + } else { + width = this.updateForm.value.width; + } + } + + /** + * Calculates the aspect ratio of the given projector. + * If no matching ratio is found, the first ratio is returned. + * + * @param projector The projector to check + * @returns the found ratio key. + */ + public getAspectRatioKey(): string { + const ratio = this.projector.width / this.projector.height; + const RATIO_ENVIRONMENT = 0.05; + const foundRatioKey = Object.keys(aspectRatios).find(key => { + const value = aspectRatios[key]; + return value >= ratio - RATIO_ENVIRONMENT && value <= ratio + RATIO_ENVIRONMENT; + }); + if (!foundRatioKey) { + return Object.keys(aspectRatios)[0]; + } else { + return foundRatioKey; + } + } + + public getMinWidth(): number { + if (this.updateForm.value.aspectRatio === '30:9') { + return aspectRatio_30_9_MinWidth; + } else { + return 800; + } + } +} diff --git a/client/src/app/site/projector/components/projector-list-entry/projector-list-entry.component.html b/client/src/app/site/projector/components/projector-list-entry/projector-list-entry.component.html index bed7fc524..84c1c442e 100644 --- a/client/src/app/site/projector/components/projector-list-entry/projector-list-entry.component.html +++ b/client/src/app/site/projector/components/projector-list-entry/projector-list-entry.component.html @@ -1,18 +1,20 @@ - + {{ projector.getTitle() | translate }} - - + - - - @@ -22,221 +24,5 @@
- -
- - - - - Required - - - -

Resolution and size

- - - - {{ ratio }} - - -
- - {{ updateForm.value.width }} -
- - -

Projection defaults

- - - {{ pd.getTitle() | translate }} - - - - -
-
- - Foreground color - - - Required - - -
-
- -
-
- -
-
- - Background color - - - Required - - -
-
- -
-
- -
-
- - Header background color - - - Required - - -
-
- -
-
- -
-
- - Header font color - - - Required - - -
-
- -
-
- -
-
- - Headline color - - - Required - - -
-
- -
-
- -
-
- - Chyron background color - - - Required - - -
-
- -
-
- -
-
- - Chyron font color - - - Required - - -
-
- -
-
- - -
- - Show header and footer - -
-
- - Show title - -
-
- - Show logo - -
-
- - Show clock - -
-
-
diff --git a/client/src/app/site/projector/components/projector-list-entry/projector-list-entry.component.scss b/client/src/app/site/projector/components/projector-list-entry/projector-list-entry.component.scss index c3f92052d..37e364fc3 100644 --- a/client/src/app/site/projector/components/projector-list-entry/projector-list-entry.component.scss +++ b/client/src/app/site/projector/components/projector-list-entry/projector-list-entry.component.scss @@ -1,31 +1,21 @@ +.projector-tile { + height: 100%; + display: block; + + > div { + height: 100%; + + .meta-text-block { + margin: 0 !important; + height: 100%; + } + } +} + +.no-markup > div { + border: 1px solid lightgray; +} + .projector { - width: 320px; color: black; - border: 1px solid lightgrey; -} - -form { - margin-top: 10px; -} - -.no-markup { - /* Do not let the a tag ruin the projector */ - color: inherit; - text-decoration: inherit; -} - -.color-field-wrapper { - width: 100%; - display: grid; - grid-template-columns: auto 30px; - - .form { - grid-column-start: 1; - grid-column-end: 1; - } - .reset-button { - grid-column-start: 2; - grid-column-end: 2; - margin-top: 30px; - } } diff --git a/client/src/app/site/projector/components/projector-list-entry/projector-list-entry.component.ts b/client/src/app/site/projector/components/projector-list-entry/projector-list-entry.component.ts index 860644cc0..2b059ad1d 100644 --- a/client/src/app/site/projector/components/projector-list-entry/projector-list-entry.component.ts +++ b/client/src/app/site/projector/components/projector-list-entry/projector-list-entry.component.ts @@ -1,71 +1,33 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { MatRadioChange } from '@angular/material'; -import { MatSliderChange } from '@angular/material/slider'; +import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core'; +import { MatDialog } from '@angular/material'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Title } from '@angular/platform-browser'; import { TranslateService } from '@ngx-translate/core'; -import { OperatorService } from 'app/core/core-services/operator.service'; -import { ProjectionDefaultRepositoryService } from 'app/core/repositories/projector/projection-default-repository.service'; import { ProjectorRepositoryService } from 'app/core/repositories/projector/projector-repository.service'; import { PromptService } from 'app/core/ui-services/prompt.service'; -import { Projector } from 'app/shared/models/core/projector'; +import { largeDialogSettings } from 'app/shared/utils/dialog-settings'; import { BaseViewComponent } from 'app/site/base/base-view'; -import { ClockSlideService } from '../../services/clock-slide.service'; -import { ViewProjectionDefault } from '../../models/view-projection-default'; +import { ProjectorEditDialogComponent } from '../projector-edit-dialog/projector-edit-dialog.component'; import { ViewProjector } from '../../models/view-projector'; -/** - * All supported aspect rations for projectors. - */ -const aspectRatios: { [ratio: string]: number } = { - '4:3': 4 / 3, - '16:9': 16 / 9, - '16:10': 16 / 10, - '30:9': 30 / 9 -}; - -const aspectRatio_30_9_MinWidth = 1150; - /** * List for all projectors. */ @Component({ selector: 'os-projector-list-entry', templateUrl: './projector-list-entry.component.html', - styleUrls: ['./projector-list-entry.component.scss'] + styleUrls: ['./projector-list-entry.component.scss'], + encapsulation: ViewEncapsulation.None }) export class ProjectorListEntryComponent extends BaseViewComponent implements OnInit { - /** - * The update form. Will be refreahed for each projector. Just one update - * form can be shown per time. - */ - public updateForm: FormGroup; - - /** - * Saves, if this projector currently is edited. - */ - public isEditing = false; - - /** - * All ProjectionDefaults to select from. - */ - public projectionDefaults: ViewProjectionDefault[]; - - /** - * All aspect ratio keys/strings for the UI. - */ - public aspectRatiosKeys: string[]; - /** * The projector shown by this entry. */ @Input() public set projector(value: ViewProjector) { this._projector = value; - this.updateForm.patchValue({ width: value.width }); } public get projector(): ViewProjector { @@ -74,15 +36,6 @@ export class ProjectorListEntryComponent extends BaseViewComponent implements On private _projector: ViewProjector; - /** - * Helper to check manage permissions - * - * @returns true if the user can manage projectors - */ - public get canManage(): boolean { - return this.operator.hasPerms('core.can_manage_projector'); - } - /** * Constructor. Initializes the update form. * @@ -100,142 +53,29 @@ export class ProjectorListEntryComponent extends BaseViewComponent implements On protected translate: TranslateService, // protected required for ng-translate-extract matSnackBar: MatSnackBar, private repo: ProjectorRepositoryService, - private formBuilder: FormBuilder, private promptService: PromptService, - private clockSlideService: ClockSlideService, - private operator: OperatorService, - private projectionDefaultRepo: ProjectionDefaultRepositoryService + private dialogService: MatDialog ) { super(titleService, translate, matSnackBar); - - this.aspectRatiosKeys = Object.keys(aspectRatios); - - this.updateForm = this.formBuilder.group({ - name: ['', Validators.required], - aspectRatio: ['', Validators.required], - width: [0, Validators.required], - projectiondefaults_id: [[]], - clock: [true], - color: ['', Validators.required], - background_color: ['', Validators.required], - header_background_color: ['', Validators.required], - header_font_color: ['', Validators.required], - header_h1_color: ['', Validators.required], - chyron_background_color: ['', Validators.required], - chyron_font_color: ['', Validators.required], - show_header_footer: [], - show_title: [], - show_logo: [] - }); } - /** - * Watches all projectiondefaults. - */ - public ngOnInit(): void { - this.projectionDefaults = this.projectionDefaultRepo.getViewModelList(); - this.subscriptions.push( - this.projectionDefaultRepo.getViewModelListObservable().subscribe(pds => (this.projectionDefaults = pds)) - ); - } - - /** - * Event on Key Down in update form. - * - * @param event the keyboard event - * @param the current view in scope - */ - public keyDownFunction(event: KeyboardEvent): void { - if (event.key === 'Enter') { - this.onSaveButton(); - } - if (event.key === 'Escape') { - this.onCancelButton(); - } - } - - /** - * Calculates the aspect ratio of the given projector. - * If no matching ratio is found, the first ratio is returned. - * - * @param projector The projector to check - * @returns the found ratio key. - */ - public getAspectRatioKey(): string { - const ratio = this.projector.width / this.projector.height; - const RATIO_ENVIRONMENT = 0.05; - const foundRatioKey = Object.keys(aspectRatios).find(key => { - const value = aspectRatios[key]; - return value >= ratio - RATIO_ENVIRONMENT && value <= ratio + RATIO_ENVIRONMENT; - }); - if (!foundRatioKey) { - return Object.keys(aspectRatios)[0]; - } else { - return foundRatioKey; - } - } + public ngOnInit(): void {} /** * Starts editing for the given projector. */ - public onEditButton(): void { - if (this.isEditing) { - return; - } - this.isEditing = true; - this.updateForm.reset(); - - this.updateForm.patchValue(this.projector.projector); - this.updateForm.patchValue({ - name: this.translate.instant(this.projector.name), - aspectRatio: this.getAspectRatioKey(), - clock: this.clockSlideService.isProjectedOn(this.projector) + public editProjector(): void { + this.dialogService.open(ProjectorEditDialogComponent, { + data: this.projector, + ...largeDialogSettings }); } /** - * Cancels the current editing. + * Handler to set the selected projector as CLOS reference */ - public onCancelButton(): void { - this.isEditing = false; - } - - /** - * Saves the projector - * - * @param projector The projector to save. - */ - public async onSaveButton(): Promise { - const updateProjector: Partial = this.updateForm.value; - updateProjector.height = Math.round( - this.updateForm.value.width / aspectRatios[this.updateForm.value.aspectRatio] - ); - - try { - await this.clockSlideService.setProjectedOn(this.projector, this.updateForm.value.clock); - await this.repo.update(updateProjector, this.projector); - this.isEditing = false; - } catch (e) { - this.raiseError(e); - } - } - - public aspectRatioChanged(event: MatRadioChange): void { - let width: number; - if (event.value === '30:9' && this.updateForm.value.width < aspectRatio_30_9_MinWidth) { - width = aspectRatio_30_9_MinWidth; - } else { - width = this.updateForm.value.width; - } - this.updateProjectorDimensions(width, event.value); - } - - public getMinWidth(): number { - if (this.updateForm.value.aspectRatio === '30:9') { - return aspectRatio_30_9_MinWidth; - } else { - return 800; - } + public onSetAsClosRef(): void { + this.repo.setDefaultProjector(this.projector.id); } /** @@ -247,30 +87,4 @@ export class ProjectorListEntryComponent extends BaseViewComponent implements On this.repo.delete(this.projector).catch(this.raiseError); } } - - /** - * Eventhandler for slider changes. Directly saves the new aspect ratio. - * - * @param event The slider value - */ - public widthSliderValueChanged(event: MatSliderChange): void { - this.updateProjectorDimensions(event.value, this.updateForm.value.aspectRatio); - } - - private updateProjectorDimensions(width: number, aspectRatioKey: string): void { - const updateProjector: Partial = { - width: width - }; - updateProjector.height = Math.round(width / aspectRatios[aspectRatioKey]); - this.repo.update(updateProjector, this.projector).catch(this.raiseError); - } - - /** - * Resets the given form field to the given default. - */ - public resetField(field: string, value: string): void { - const patchValue = {}; - patchValue[field] = value; - this.updateForm.patchValue(patchValue); - } } diff --git a/client/src/app/site/projector/components/projector-list/projector-list.component.html b/client/src/app/site/projector/components/projector-list/projector-list.component.html index a84e5d3d3..6eef16d0c 100644 --- a/client/src/app/site/projector/components/projector-list/projector-list.component.html +++ b/client/src/app/site/projector/components/projector-list/projector-list.component.html @@ -1,51 +1,39 @@ - +

Projectors

- - Reference projector for current list of speakers:   - - - - {{ projector.getTitle() | translate }} - - - - + + +

+ New Projector +

- - New Projector - -
-

- - - - Required - - -

-
-
- - - - -
+
+
+ + + + Required + + +
+ +
+ + +
+
+
-
+
diff --git a/client/src/app/site/projector/components/projector-list/projector-list.component.scss b/client/src/app/site/projector/components/projector-list/projector-list.component.scss index 5b3fb4aa9..ba5a7c9ee 100644 --- a/client/src/app/site/projector/components/projector-list/projector-list.component.scss +++ b/client/src/app/site/projector/components/projector-list/projector-list.component.scss @@ -1,10 +1,13 @@ #card-wrapper { - margin-top: 10px; - margin-left: 10px; + margin: 10px; + display: grid; + grid-gap: 10px; + // if there is only 1 and desktop size, use 0.5 fr + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); .projector-card { - width: 350px; - margin: 10px; - float: left; + width: 100%; + max-width: 100vmin; + max-height: 100vh; } } diff --git a/client/src/app/site/projector/components/projector-list/projector-list.component.ts b/client/src/app/site/projector/components/projector-list/projector-list.component.ts index 7852275c8..d78c3d2c8 100644 --- a/client/src/app/site/projector/components/projector-list/projector-list.component.ts +++ b/client/src/app/site/projector/components/projector-list/projector-list.component.ts @@ -5,19 +5,21 @@ import { Component, OnDestroy, OnInit, + TemplateRef, ViewEncapsulation } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { MatSelectChange } from '@angular/material/select'; +import { MatDialog } from '@angular/material'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Title } from '@angular/platform-browser'; import { TranslateService } from '@ngx-translate/core'; -import { timer } from 'rxjs'; +import { BehaviorSubject, timer } from 'rxjs'; import { OperatorService } from 'app/core/core-services/operator.service'; import { ProjectorRepositoryService } from 'app/core/repositories/projector/projector-repository.service'; import { Projector } from 'app/shared/models/core/projector'; +import { infoDialogSettings } from 'app/shared/utils/dialog-settings'; import { BaseViewComponent } from 'app/site/base/base-view'; import { ViewProjector } from '../../models/view-projector'; @@ -32,11 +34,6 @@ import { ViewProjector } from '../../models/view-projector'; encapsulation: ViewEncapsulation.None }) export class ProjectorListComponent extends BaseViewComponent implements OnInit, AfterViewInit, OnDestroy { - /** - * This member is set, if the user is creating a new projector. - */ - public showCreateForm = false; - /** * The create form. */ @@ -50,7 +47,7 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit, /** * All projectors. */ - public projectors: ViewProjector[]; + public projectors: BehaviorSubject; /** * Helper to check manage permissions @@ -80,6 +77,7 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit, private repo: ProjectorRepositoryService, private formBuilder: FormBuilder, private operator: OperatorService, + private dialogService: MatDialog, private cd: ChangeDetectorRef ) { super(titleService, translate, matSnackBar); @@ -103,8 +101,26 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit, */ public ngOnInit(): void { super.setTitle('Projectors'); - this.projectors = this.repo.getViewModelList(); - this.repo.getViewModelListObservable().subscribe(projectors => (this.projectors = projectors)); + this.projectors = this.repo.getViewModelListBehaviorSubject(); + } + + /** + * @param dialog + */ + public createNewProjector(dialog: TemplateRef): void { + this.createForm.reset(); + const dialogRef = this.dialogService.open(dialog, { ...infoDialogSettings, disableClose: true }); + dialogRef.afterClosed().subscribe(result => { + if (result) { + const projectorToCreate: Partial = { + name: this.createForm.value.name + }; + + this.repo.create(projectorToCreate).then(() => { + this.cd.detectChanges(); + }, this.raiseError); + } + }); } /** @@ -121,52 +137,4 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit, super.ngOnDestroy(); this.cd.detach(); } - - /** - * Opens the create form. - */ - public onPlusButton(): void { - if (!this.showCreateForm) { - this.showCreateForm = true; - this.createForm.setValue({ name: '' }); - } - } - - /** - * Creates the comment section from the create form. - */ - public create(): void { - if (this.createForm.valid && this.showCreateForm) { - const projector: Partial = { - name: this.createForm.value.name, - reference_projector_id: this.projectors[0].reference_projector_id - }; - this.repo.create(projector).then(() => { - this.showCreateForm = false; - this.cd.detectChanges(); - }, this.raiseError); - } - } - - /** - * Event on Key Down in update or create form. - * - * @param event the keyboard event - */ - public keyDownFunction(event: KeyboardEvent): void { - if (event.key === 'Enter') { - this.create(); - } - if (event.key === 'Escape') { - this.showCreateForm = null; - } - } - - /** - * Event handler when the reference projector is changed - * @param change the change event that contains the new id - */ - public onSelectReferenceProjector(change: MatSelectChange): void { - this.repo.setDefaultProjector(change.value).catch(this.raiseError); - } } diff --git a/client/src/app/site/projector/models/view-projector.ts b/client/src/app/site/projector/models/view-projector.ts index 2ab9b702c..60fba8e9c 100644 --- a/client/src/app/site/projector/models/view-projector.ts +++ b/client/src/app/site/projector/models/view-projector.ts @@ -16,6 +16,10 @@ export class ViewProjector extends BaseViewModel { public get non_stable_elements(): ProjectorElements { return this.projector.elements.filter(element => !element.stable); } + + public get isReferenceProjector(): boolean { + return this.id === this.reference_projector_id; + } } interface IProjectorRelations { referenceProjector: ViewProjector; diff --git a/client/src/app/site/projector/projector.module.ts b/client/src/app/site/projector/projector.module.ts index 92bfa0359..edb2397ae 100644 --- a/client/src/app/site/projector/projector.module.ts +++ b/client/src/app/site/projector/projector.module.ts @@ -7,6 +7,7 @@ import { MessageControlsComponent } from './components/message-controls/message- import { MessageDialogComponent } from './components/message-dialog/message-dialog.component'; import { PresentationControlComponent } from './components/presentation-control/presentation-control.component'; import { ProjectorDetailComponent } from './components/projector-detail/projector-detail.component'; +import { ProjectorEditDialogComponent } from './components/projector-edit-dialog/projector-edit-dialog.component'; import { ProjectorListEntryComponent } from './components/projector-list-entry/projector-list-entry.component'; import { ProjectorListComponent } from './components/projector-list/projector-list.component'; import { ProjectorRoutingModule } from './projector-routing.module'; @@ -22,13 +23,15 @@ import { SharedModule } from '../../shared/shared.module'; CountdownDialogComponent, MessageControlsComponent, MessageDialogComponent, - PresentationControlComponent + PresentationControlComponent, + ProjectorEditDialogComponent ], entryComponents: [ CountdownDialogComponent, MessageDialogComponent, PresentationControlComponent, - ProjectorListEntryComponent + ProjectorListEntryComponent, + ProjectorEditDialogComponent ] }) export class ProjectorModule {}