Merge pull request #5141 from tsiegleauq/custom-projector-resolution
Custom aspect ratio for projector
This commit is contained in:
commit
1b40cd74e0
@ -65,7 +65,8 @@ export class Projector extends BaseModel<Projector> {
|
||||
public scroll: number;
|
||||
public name: string;
|
||||
public width: number;
|
||||
public height: number;
|
||||
public aspect_ratio_numerator: number;
|
||||
public aspect_ratio_denominator: number;
|
||||
public reference_projector_id: number;
|
||||
public projectiondefaults_id: number[];
|
||||
public color: string;
|
||||
@ -79,6 +80,34 @@ export class Projector extends BaseModel<Projector> {
|
||||
public show_title: boolean;
|
||||
public show_logo: boolean;
|
||||
|
||||
/**
|
||||
* @returns Calculate the height of the projector
|
||||
*/
|
||||
public get height(): number {
|
||||
const ratio = this.aspect_ratio_numerator / this.aspect_ratio_denominator;
|
||||
return this.width / ratio;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the aspect ratio as string
|
||||
*/
|
||||
public get aspectRatio(): string {
|
||||
return [this.aspect_ratio_numerator, this.aspect_ratio_denominator].join(':');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the aspect ratio
|
||||
*/
|
||||
public set aspectRatio(ratioString: string) {
|
||||
const ratio = ratioString.split(':').map(x => +x);
|
||||
if (ratio.length === 2) {
|
||||
this.aspect_ratio_numerator = ratio[0];
|
||||
this.aspect_ratio_denominator = ratio[1];
|
||||
} else {
|
||||
throw new Error('Projector received unexpected aspect ratio! ' + ratio.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public constructor(input?: any) {
|
||||
super(Projector.COLLECTIONSTRING, input);
|
||||
}
|
||||
|
@ -15,22 +15,40 @@
|
||||
|
||||
<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>
|
||||
<mat-radio-group formControlName="aspectRatio" name="aspectRatio">
|
||||
<mat-radio-button
|
||||
*ngFor="let ratio of defaultAspectRatio"
|
||||
[value]="ratio"
|
||||
(change)="onCustomAspectRatio(false)"
|
||||
>
|
||||
{{ ratio }}
|
||||
</mat-radio-button>
|
||||
|
||||
<!-- Custom aspect ratio -->
|
||||
<mat-radio-button (change)="onCustomAspectRatio(true)">
|
||||
{{ 'custom' | translate }}
|
||||
</mat-radio-button>
|
||||
|
||||
<mat-form-field *ngIf="customAspectRatio">
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
formControlName="aspectRatio"
|
||||
[value]="previewProjector.aspectRatio"
|
||||
(change)="setCustomAspectRatio()"
|
||||
placeholder="{{ 'Custom aspect ratio' | translate }}"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="spacer-top-20 grid-form">
|
||||
<mat-slider
|
||||
class="grid-start"
|
||||
formControlName="width"
|
||||
[thumbLabel]="true"
|
||||
[min]="getMinWidth()"
|
||||
[min]="minWidth"
|
||||
[max]="maxResolution"
|
||||
[step]="resolutionChangeStep"
|
||||
[value]="updateForm.value.width"
|
||||
@ -41,7 +59,7 @@
|
||||
matInput
|
||||
type="number"
|
||||
formControlName="width"
|
||||
[min]="getMinWidth()"
|
||||
[min]="minWidth"
|
||||
[max]="maxResolution"
|
||||
[step]="resolutionChangeStep"
|
||||
[value]="updateForm.value.width"
|
||||
@ -170,7 +188,7 @@
|
||||
</form>
|
||||
<div>
|
||||
<h3 translate>Preview</h3>
|
||||
<div>
|
||||
<div class="preview-container">
|
||||
<os-projector #preview *ngIf="previewProjector" [projector]="previewProjector"></os-projector>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -25,6 +25,7 @@ form {
|
||||
.grid-start {
|
||||
grid-column-start: 1;
|
||||
grid-column-end: 1;
|
||||
margin: auto 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@ -45,6 +46,10 @@ form {
|
||||
}
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
border: 1px solid lightgray;
|
||||
}
|
||||
|
||||
.no-markup {
|
||||
/* Do not let the a tag ruin the projector */
|
||||
color: inherit;
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef, MatRadioChange, MatSnackBar } from '@angular/material';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef, MatSnackBar } from '@angular/material';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
@ -23,18 +23,6 @@ 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
|
||||
@ -54,17 +42,17 @@ export class ProjectorEditDialogComponent extends BaseViewComponent implements O
|
||||
@ViewChild('preview', { static: false })
|
||||
public preview: ProjectorComponent;
|
||||
|
||||
/**
|
||||
* aspect ratios
|
||||
*/
|
||||
public defaultAspectRatio: string[] = ['4:3', '16:9', '16:10'];
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@ -80,11 +68,26 @@ export class ProjectorEditDialogComponent extends BaseViewComponent implements O
|
||||
*/
|
||||
public maxResolution = 2000;
|
||||
|
||||
/**
|
||||
* define the minWidth
|
||||
*/
|
||||
public minWidth = 800;
|
||||
|
||||
/**
|
||||
* Define the step of resolution changes
|
||||
*/
|
||||
public resolutionChangeStep = 10;
|
||||
|
||||
/**
|
||||
* Determine to use custom aspect ratios
|
||||
*/
|
||||
public customAspectRatio: boolean;
|
||||
|
||||
/**
|
||||
* regular expression to check for aspect ratio strings
|
||||
*/
|
||||
private aspectRatioRe = RegExp('[1-9]+[0-9]*:[1-9]+[0-9]*');
|
||||
|
||||
public constructor(
|
||||
title: Title,
|
||||
translate: TranslateService,
|
||||
@ -98,15 +101,18 @@ export class ProjectorEditDialogComponent extends BaseViewComponent implements O
|
||||
private cd: ChangeDetectorRef
|
||||
) {
|
||||
super(title, translate, matSnackBar);
|
||||
this.aspectRatiosKeys = Object.keys(aspectRatios);
|
||||
|
||||
if (projector) {
|
||||
this.previewProjector = new Projector(projector.getModel());
|
||||
|
||||
if (!this.defaultAspectRatio.some(ratio => ratio === this.previewProjector.aspectRatio)) {
|
||||
this.customAspectRatio = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateForm = formBuilder.group({
|
||||
name: ['', Validators.required],
|
||||
aspectRatio: ['', Validators.required],
|
||||
aspectRatio: ['', [Validators.required, Validators.pattern(this.aspectRatioRe)]],
|
||||
width: [0, Validators.required],
|
||||
projectiondefaults_id: [[]],
|
||||
clock: [true],
|
||||
@ -143,7 +149,6 @@ export class ProjectorEditDialogComponent extends BaseViewComponent implements O
|
||||
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)
|
||||
});
|
||||
|
||||
@ -174,8 +179,8 @@ export class ProjectorEditDialogComponent extends BaseViewComponent implements O
|
||||
* 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);
|
||||
const updateProjector: Projector = new Projector();
|
||||
Object.assign(updateProjector, this.updateForm.value);
|
||||
try {
|
||||
await this.clockSlideService.setProjectedOn(this.projector, this.updateForm.value.clock);
|
||||
await this.repo.update(updateProjector, this.projector);
|
||||
@ -189,26 +194,13 @@ export class ProjectorEditDialogComponent extends BaseViewComponent implements O
|
||||
* @param previewUpdate
|
||||
*/
|
||||
public onChangeForm(): void {
|
||||
if (this.previewProjector && this.projector) {
|
||||
if (this.previewProjector && this.projector && this.updateForm.valid) {
|
||||
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.
|
||||
*/
|
||||
@ -218,41 +210,23 @@ export class ProjectorEditDialogComponent extends BaseViewComponent implements O
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* Sets the aspect Ratio to custom
|
||||
* @param event
|
||||
*/
|
||||
public onCustomAspectRatio(event: boolean): void {
|
||||
this.customAspectRatio = event;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Sets and validates custom aspect ratio values
|
||||
*/
|
||||
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;
|
||||
public setCustomAspectRatio(): void {
|
||||
const formRatio = this.updateForm.get('aspectRatio').value;
|
||||
const validatedRatio = formRatio.match(this.aspectRatioRe);
|
||||
if (validatedRatio && validatedRatio[0]) {
|
||||
const ratio = validatedRatio[0];
|
||||
this.updateForm.get('aspectRatio').setValue(ratio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
23
openslides/core/migrations/0026_projector_size_1.py
Normal file
23
openslides/core/migrations/0026_projector_size_1.py
Normal file
@ -0,0 +1,23 @@
|
||||
# Generated by Django 2.2.6 on 2019-11-22 11:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("core", "0025_projector_color"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="projector",
|
||||
name="aspect_ratio_numerator",
|
||||
field=models.PositiveIntegerField(default=16),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="projector",
|
||||
name="aspect_ratio_denominator",
|
||||
field=models.PositiveIntegerField(default=9),
|
||||
),
|
||||
]
|
45
openslides/core/migrations/0027_projector_size_2.py
Normal file
45
openslides/core/migrations/0027_projector_size_2.py
Normal file
@ -0,0 +1,45 @@
|
||||
# Generated by Finn Stutzenstein on 2019-11-22 11:42
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def calculate_aspect_ratios(apps, schema_editor):
|
||||
"""
|
||||
Assignes every projector one aspect ratio of the ones, that OS
|
||||
supported until this migration. If no matching ratio was found, the
|
||||
default of 16:9 is assigned.
|
||||
"""
|
||||
Projector = apps.get_model("core", "Projector")
|
||||
ratio_environment = 0.05
|
||||
aspect_ratios = {
|
||||
4 / 3: (4, 3),
|
||||
16 / 9: (16, 9),
|
||||
16 / 10: (16, 10),
|
||||
30 / 9: (30, 9),
|
||||
}
|
||||
|
||||
for projector in Projector.objects.all():
|
||||
projector_ratio = projector.width / projector.height
|
||||
ratio = (16, 9) # default, if no matching aspect ratio was found.
|
||||
# Search ratio, that fits to the projector_ratio. Take first one found.
|
||||
for value, _ratio in aspect_ratios.items():
|
||||
if (
|
||||
value >= projector_ratio - ratio_environment
|
||||
and value <= projector_ratio + ratio_environment
|
||||
):
|
||||
ratio = _ratio
|
||||
break
|
||||
projector.aspect_ratio_numerator = ratio[0]
|
||||
projector.aspect_ratio_denominator = ratio[1]
|
||||
projector.save(skip_autoupdate=True)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("core", "0026_projector_size_1"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(calculate_aspect_ratios),
|
||||
]
|
14
openslides/core/migrations/0028_projector_size_3.py
Normal file
14
openslides/core/migrations/0028_projector_size_3.py
Normal file
@ -0,0 +1,14 @@
|
||||
# Generated by Finn Stutzenstein on 2019-11-22 12:04
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("core", "0027_projector_size_2"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(model_name="projector", name="height",),
|
||||
]
|
@ -75,7 +75,8 @@ class Projector(RESTModelMixin, models.Model):
|
||||
scroll = models.IntegerField(default=0)
|
||||
|
||||
width = models.PositiveIntegerField(default=1024)
|
||||
height = models.PositiveIntegerField(default=768)
|
||||
aspect_ratio_numerator = models.PositiveIntegerField(default=16)
|
||||
aspect_ratio_denominator = models.PositiveIntegerField(default=9)
|
||||
|
||||
color = models.CharField(max_length=7, default="#000000")
|
||||
background_color = models.CharField(max_length=7, default="#ffffff")
|
||||
|
@ -88,7 +88,8 @@ class ProjectorSerializer(ModelSerializer):
|
||||
elements_history = JSONSerializerField(read_only=True)
|
||||
|
||||
width = IntegerField(min_value=800, max_value=3840, required=False)
|
||||
height = IntegerField(min_value=340, max_value=2880, required=False)
|
||||
aspect_ratio_numerator = IntegerField(min_value=1, required=False)
|
||||
aspect_ratio_denominator = IntegerField(min_value=1, required=False)
|
||||
|
||||
projectiondefaults = IdPrimaryKeyRelatedField(
|
||||
many=True, required=False, queryset=ProjectionDefault.objects.all()
|
||||
@ -105,7 +106,8 @@ class ProjectorSerializer(ModelSerializer):
|
||||
"scroll",
|
||||
"name",
|
||||
"width",
|
||||
"height",
|
||||
"aspect_ratio_numerator",
|
||||
"aspect_ratio_denominator",
|
||||
"reference_projector",
|
||||
"projectiondefaults",
|
||||
"color",
|
||||
|
Loading…
Reference in New Issue
Block a user