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
This commit is contained in:
parent
f62b506dee
commit
233961b466
@ -1,5 +1,5 @@
|
||||
<div id="container" #container [osResized]="resizeSubject">
|
||||
<div id="projector" [ngStyle]="projectorStyle">
|
||||
<os-projector [projector]="projector"></os-projector>
|
||||
<os-projector *ngIf="projector" [projector]="projector"></os-projector>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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<MessageDialogComponent>;
|
||||
|
||||
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();
|
||||
|
@ -1,4 +1,4 @@
|
||||
<os-head-bar [nav]="false" [goBack]="true">
|
||||
<os-head-bar [nav]="false" [hasMainButton]="true" mainButtonIcon="edit" [goBack]="true" (mainEvent)="editProjector()">
|
||||
<!-- Title -->
|
||||
<div class="title-slot">
|
||||
<h2>{{ projector?.name | translate }}</h2>
|
||||
|
@ -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 => {
|
||||
|
@ -0,0 +1,177 @@
|
||||
<h1 mat-dialog-title>
|
||||
<span translate>Edit Projector</span>
|
||||
</h1>
|
||||
|
||||
<div class="settings-grid">
|
||||
<form [formGroup]="updateForm" (ngSubmit)="onSubmitProjector()">
|
||||
<div mat-dialog-content *ngIf="projector">
|
||||
<!-- Name field -->
|
||||
<mat-form-field>
|
||||
<input formControlName="name" matInput placeholder="{{ 'Name' | translate }}" required />
|
||||
<mat-hint *ngIf="!updateForm.controls.name.valid">
|
||||
<span translate>Required</span>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<h3 translate>Resolution and size</h3>
|
||||
<!-- Aspect ratio field -->
|
||||
<mat-radio-group formControlName="aspectRatio" name="aspectRatio">
|
||||
<mat-radio-button
|
||||
*ngFor="let ratio of aspectRatiosKeys"
|
||||
[value]="ratio"
|
||||
(change)="aspectRatioChanged($event)"
|
||||
>
|
||||
{{ ratio }}
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
|
||||
<div class="spacer-top-20 grid-form">
|
||||
<mat-slider
|
||||
class="grid-start"
|
||||
formControlName="width"
|
||||
[thumbLabel]="true"
|
||||
[min]="getMinWidth()"
|
||||
[max]="maxResolution"
|
||||
[step]="resolutionChangeStep"
|
||||
[value]="updateForm.value.width"
|
||||
></mat-slider>
|
||||
<div class="grid-end">
|
||||
<mat-form-field>
|
||||
<input
|
||||
matInput
|
||||
type="number"
|
||||
formControlName="width"
|
||||
[min]="getMinWidth()"
|
||||
[max]="maxResolution"
|
||||
[step]="resolutionChangeStep"
|
||||
[value]="updateForm.value.width"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- checkboxes -->
|
||||
<div>
|
||||
<div>
|
||||
<mat-checkbox formControlName="show_header_footer">
|
||||
<span translate>Show header and footer</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div>
|
||||
<mat-checkbox formControlName="show_title">
|
||||
<span translate>Show title</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div>
|
||||
<mat-checkbox formControlName="show_logo">
|
||||
<span translate>Show logo</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div>
|
||||
<mat-checkbox formControlName="clock">
|
||||
<span translate>Show clock</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- projection defaults -->
|
||||
<h3 translate>Projection defaults</h3>
|
||||
<mat-select
|
||||
formControlName="projectiondefaults_id"
|
||||
placeholder="{{ 'Projection defaults' | translate }}"
|
||||
[multiple]="true"
|
||||
>
|
||||
<mat-option *ngFor="let pd of projectionDefaults" [value]="pd.id">
|
||||
{{ pd.getTitle() | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
|
||||
<!-- colors -->
|
||||
<div class="spacer-top-10">
|
||||
<!-- Template to streamline all the color forms -->
|
||||
<ng-template #colorFormField let-title="title" let-form="form">
|
||||
<div class="grid-form">
|
||||
<div class="grid-start">
|
||||
<mat-form-field class="color-picker-form">
|
||||
<span>{{ title | translate }}</span>
|
||||
<input matInput [formControlName]="form" type="color" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="grid-end">
|
||||
<button
|
||||
type="button"
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'Reset' | translate }}"
|
||||
(click)="resetField(form)"
|
||||
>
|
||||
<mat-icon>replay</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template
|
||||
[ngTemplateOutlet]="colorFormField"
|
||||
[ngTemplateOutletContext]="{ title: 'Foreground color', form: 'color' }"
|
||||
>
|
||||
</ng-template>
|
||||
|
||||
<ng-template
|
||||
[ngTemplateOutlet]="colorFormField"
|
||||
[ngTemplateOutletContext]="{ title: 'Background color', form: 'background_color' }"
|
||||
>
|
||||
</ng-template>
|
||||
|
||||
<ng-template
|
||||
[ngTemplateOutlet]="colorFormField"
|
||||
[ngTemplateOutletContext]="{ title: 'Header background color', form: 'header_background_color' }"
|
||||
>
|
||||
</ng-template>
|
||||
|
||||
<ng-template
|
||||
[ngTemplateOutlet]="colorFormField"
|
||||
[ngTemplateOutletContext]="{ title: 'Header font color', form: 'header_font_color' }"
|
||||
>
|
||||
</ng-template>
|
||||
|
||||
<ng-template
|
||||
[ngTemplateOutlet]="colorFormField"
|
||||
[ngTemplateOutletContext]="{ title: 'Headline color', form: 'header_h1_color' }"
|
||||
>
|
||||
</ng-template>
|
||||
|
||||
<ng-template
|
||||
[ngTemplateOutlet]="colorFormField"
|
||||
[ngTemplateOutletContext]="{ title: 'Chyron background color', form: 'chyron_background_color' }"
|
||||
>
|
||||
</ng-template>
|
||||
|
||||
<ng-template
|
||||
[ngTemplateOutlet]="colorFormField"
|
||||
[ngTemplateOutletContext]="{ title: 'Chyron font color', form: 'chyron_font_color' }"
|
||||
>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div mat-dialog-actions>
|
||||
<mat-divider></mat-divider>
|
||||
<button type="submit" mat-button color="primary">
|
||||
<span translate>Save</span>
|
||||
</button>
|
||||
<button type="button" mat-button [mat-dialog-close]="null">
|
||||
<span translate>Cancel</span>
|
||||
</button>
|
||||
<button type="button" mat-button (click)="applyChanges()">
|
||||
<span translate>Apply</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div>
|
||||
<h3 translate>Preview</h3>
|
||||
<div>
|
||||
<os-projector #preview *ngIf="previewProjector" [projector]="previewProjector"></os-projector>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -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;
|
||||
}
|
@ -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<ProjectorEditDialogComponent>;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
});
|
||||
});
|
@ -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<ProjectorEditDialogComponent>,
|
||||
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<void> {
|
||||
await this.applyChanges();
|
||||
this.dialogRef.close(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current changes on the projector
|
||||
*/
|
||||
public async applyChanges(): Promise<void> {
|
||||
const updateProjector: Partial<Projector> = 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +1,20 @@
|
||||
<os-meta-text-block showActionRow="false" *ngIf="projector" [disableExpandControl]="true">
|
||||
<os-meta-text-block class="projector-tile" showActionRow="false" *ngIf="projector" [disableExpandControl]="true">
|
||||
<ng-container class="meta-text-block-title">
|
||||
{{ projector.getTitle() | translate }}
|
||||
</ng-container>
|
||||
<ng-container class="meta-text-block-action-row" *ngIf="canManage">
|
||||
<button mat-icon-button *ngIf="!isEditing" (click)="onEditButton()">
|
||||
<ng-container class="meta-text-block-action-row" *osPerms="'core.can_manage_projector'">
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="onSetAsClosRef()"
|
||||
matTooltip="{{ 'Sets this projector as the reference for the current list of speakers' | translate }}"
|
||||
>
|
||||
<mat-icon *ngIf="this.projector.isReferenceProjector">star</mat-icon>
|
||||
<mat-icon *ngIf="!this.projector.isReferenceProjector">star_border</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button (click)="editProjector()" matTooltip="{{ 'Edit this projector' | translate }}">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button *ngIf="isEditing" (click)="onCancelButton()">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button *ngIf="isEditing" (click)="onSaveButton()">
|
||||
<mat-icon>save</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" (click)="onDeleteButton()">
|
||||
<button mat-icon-button color="warn" (click)="onDeleteButton()" matTooltip="{{ 'Deletes this projector' | translate }}">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</ng-container>
|
||||
@ -22,221 +24,5 @@
|
||||
<os-projector [projector]="projector"></os-projector>
|
||||
</div>
|
||||
</a>
|
||||
<ng-container *ngIf="isEditing">
|
||||
<form [formGroup]="updateForm" (keydown)="keyDownFunction($event, projector)">
|
||||
<!-- Name field -->
|
||||
<mat-form-field>
|
||||
<input formControlName="name" matInput placeholder="{{ 'Name' | translate }}" required />
|
||||
<mat-hint *ngIf="!updateForm.controls.name.valid">
|
||||
<span translate>Required</span>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<h3 translate>Resolution and size</h3>
|
||||
<!-- Aspect ratio field -->
|
||||
<mat-radio-group formControlName="aspectRatio" [name]="projector.id">
|
||||
<mat-radio-button
|
||||
*ngFor="let ratio of aspectRatiosKeys"
|
||||
[value]="ratio"
|
||||
(change)="aspectRatioChanged($event)"
|
||||
>
|
||||
{{ ratio }}
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
<div class="spacer-top-20">
|
||||
<mat-slider
|
||||
[thumbLabel]="true"
|
||||
[min]="getMinWidth()"
|
||||
max="2000"
|
||||
step="10"
|
||||
value="{{ updateForm.value.width }}"
|
||||
(change)="widthSliderValueChanged($event)"
|
||||
></mat-slider>
|
||||
{{ updateForm.value.width }}
|
||||
</div>
|
||||
|
||||
<!-- projection defaults -->
|
||||
<h3 translate>Projection defaults</h3>
|
||||
<mat-select
|
||||
formControlName="projectiondefaults_id"
|
||||
placeholder="{{ 'Projection defaults' | translate }}"
|
||||
[multiple]="true"
|
||||
>
|
||||
<mat-option *ngFor="let pd of projectionDefaults" [value]="pd.id">
|
||||
{{ pd.getTitle() | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
|
||||
<!-- colors -->
|
||||
<div class="color-field-wrapper">
|
||||
<div class="form">
|
||||
<mat-form-field>
|
||||
<span translate>Foreground color</span>
|
||||
<input matInput formControlName="color" type="color" />
|
||||
<mat-hint *ngIf="!updateForm.controls.color.valid">
|
||||
<span translate>Required</span>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="reset-button">
|
||||
<button
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'Reset' | translate }}"
|
||||
(click)="resetField('color', '#000000')"
|
||||
>
|
||||
<mat-icon>replay</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="color-field-wrapper">
|
||||
<div class="form">
|
||||
<mat-form-field>
|
||||
<span translate>Background color</span>
|
||||
<input matInput formControlName="background_color" type="color" />
|
||||
<mat-hint *ngIf="!updateForm.controls.background_color.valid">
|
||||
<span translate>Required</span>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="reset-button">
|
||||
<button
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'Reset' | translate }}"
|
||||
(click)="resetField('background_color', '#ffffff')"
|
||||
>
|
||||
<mat-icon>replay</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="color-field-wrapper">
|
||||
<div class="form">
|
||||
<mat-form-field>
|
||||
<span translate>Header background color</span>
|
||||
<input matInput formControlName="header_background_color" type="color" />
|
||||
<mat-hint *ngIf="!updateForm.controls.header_background_color.valid">
|
||||
<span translate>Required</span>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="reset-button">
|
||||
<button
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'Reset' | translate }}"
|
||||
(click)="resetField('header_background_color', '#317796')"
|
||||
>
|
||||
<mat-icon>replay</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="color-field-wrapper">
|
||||
<div class="form">
|
||||
<mat-form-field>
|
||||
<span translate>Header font color</span>
|
||||
<input matInput formControlName="header_font_color" type="color" />
|
||||
<mat-hint *ngIf="!updateForm.controls.header_font_color.valid">
|
||||
<span translate>Required</span>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="reset-button">
|
||||
<button
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'Reset' | translate }}"
|
||||
(click)="resetField('header_font_color', '#f5f5f5')"
|
||||
>
|
||||
<mat-icon>replay</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="color-field-wrapper">
|
||||
<div class="form">
|
||||
<mat-form-field>
|
||||
<span translate>Headline color</span>
|
||||
<input matInput formControlName="header_h1_color" type="color" />
|
||||
<mat-hint *ngIf="!updateForm.controls.header_h1_color.valid">
|
||||
<span translate>Required</span>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="reset-button">
|
||||
<button
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'Reset' | translate }}"
|
||||
(click)="resetField('header_h1_color', '#317796')"
|
||||
>
|
||||
<mat-icon>replay</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="color-field-wrapper">
|
||||
<div class="form">
|
||||
<mat-form-field>
|
||||
<span translate>Chyron background color</span>
|
||||
<input matInput formControlName="chyron_background_color" type="color" />
|
||||
<mat-hint *ngIf="!updateForm.controls.chyron_background_color.valid">
|
||||
<span translate>Required</span>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="reset-button">
|
||||
<button
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'Reset' | translate }}"
|
||||
(click)="resetField('chyron_background_color', '#317796')"
|
||||
>
|
||||
<mat-icon>replay</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="color-field-wrapper">
|
||||
<div class="form">
|
||||
<mat-form-field>
|
||||
<span translate>Chyron font color</span>
|
||||
<input matInput formControlName="chyron_font_color" type="color" />
|
||||
<mat-hint *ngIf="!updateForm.controls.chyron_font_color.valid">
|
||||
<span translate>Required</span>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="reset-button">
|
||||
<button
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'Reset' | translate }}"
|
||||
(click)="resetField('chyron_font_color', '#ffffff')"
|
||||
>
|
||||
<mat-icon>replay</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- checkboxes -->
|
||||
<div>
|
||||
<mat-checkbox formControlName="show_header_footer">
|
||||
<span translate>Show header and footer</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div>
|
||||
<mat-checkbox formControlName="show_title">
|
||||
<span translate>Show title</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div>
|
||||
<mat-checkbox formControlName="show_logo">
|
||||
<span translate>Show logo</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div>
|
||||
<mat-checkbox formControlName="clock">
|
||||
<span translate>Show clock</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</form>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</os-meta-text-block>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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<void> {
|
||||
const updateProjector: Partial<Projector> = 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<Projector> = {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1,51 +1,39 @@
|
||||
<os-head-bar [nav]="true" [hasMainButton]="canManage" (mainEvent)="onPlusButton()">
|
||||
<os-head-bar [nav]="true" [hasMainButton]="canManage" (mainEvent)="createNewProjector(projectorDialog)">
|
||||
<!-- Title -->
|
||||
<div class="title-slot">
|
||||
<h2 translate>Projectors</h2>
|
||||
</div>
|
||||
</os-head-bar>
|
||||
|
||||
<mat-card *ngIf="!showCreateForm && projectors && projectors.length > 1">
|
||||
<span translate> Reference projector for current list of speakers: </span>
|
||||
<mat-form-field>
|
||||
<mat-select
|
||||
[disabled]="!!editId"
|
||||
[value]="projectors.length ? projectors[0].reference_projector_id : null"
|
||||
(selectionChange)="onSelectReferenceProjector($event)"
|
||||
>
|
||||
<mat-option *ngFor="let projector of projectors" [value]="projector.id">
|
||||
{{ projector.getTitle() | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</mat-card>
|
||||
<!-- Create projector dialog -->
|
||||
<ng-template #projectorDialog>
|
||||
<h1 mat-dialog-title>
|
||||
<span translate>New Projector</span>
|
||||
</h1>
|
||||
|
||||
<mat-card *ngIf="showCreateForm">
|
||||
<mat-card-title translate>New Projector</mat-card-title>
|
||||
<mat-card-content>
|
||||
<form [formGroup]="createForm" (keydown)="keyDownFunction($event)">
|
||||
<p>
|
||||
<mat-form-field>
|
||||
<input formControlName="name" matInput placeholder="{{ 'Name' | translate }}" required />
|
||||
<mat-hint *ngIf="!createForm.controls.name.valid">
|
||||
<span translate>Required</span>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
</p>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-button (click)="create()">
|
||||
<span translate>Create</span>
|
||||
</button>
|
||||
<button mat-button (click)="showCreateForm = false">
|
||||
<span translate>Cancel</span>
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
<form [formGroup]="createForm">
|
||||
<div mat-dialog-content>
|
||||
<mat-form-field>
|
||||
<input formControlName="name" matInput placeholder="{{ 'Name' | translate }}" required />
|
||||
<mat-hint *ngIf="!createForm.controls.name.valid">
|
||||
<span translate>Required</span>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div mat-dialog-actions>
|
||||
<button type="submit" mat-button [disabled]="!createForm.valid" color="primary" [mat-dialog-close]="true">
|
||||
<span translate>Create</span>
|
||||
</button>
|
||||
<button type="button" mat-button [mat-dialog-close]="null">
|
||||
<span translate>Cancel</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</ng-template>
|
||||
|
||||
<div id="card-wrapper">
|
||||
<div class="projector-card" *ngFor="let projector of projectors; trackBy: trackByIndex">
|
||||
<div class="projector-card" *ngFor="let projector of projectors | async; trackBy: trackByIndex">
|
||||
<os-projector-list-entry [projector]="projector"></os-projector-list-entry>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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<ViewProjector[]>;
|
||||
|
||||
/**
|
||||
* 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<string>): void {
|
||||
this.createForm.reset();
|
||||
const dialogRef = this.dialogService.open(dialog, { ...infoDialogSettings, disableClose: true });
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
const projectorToCreate: Partial<Projector> = {
|
||||
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<Projector> = {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,10 @@ export class ViewProjector extends BaseViewModel<Projector> {
|
||||
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;
|
||||
|
@ -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 {}
|
||||
|
Loading…
Reference in New Issue
Block a user