diff --git a/client/src/app/shared/models/core/projector.ts b/client/src/app/shared/models/core/projector.ts index 0e31ee8c9..feea21a54 100644 --- a/client/src/app/shared/models/core/projector.ts +++ b/client/src/app/shared/models/core/projector.ts @@ -65,7 +65,8 @@ export class Projector extends BaseModel { 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 { 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); } 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 index c6c7da5c3..77a9787a6 100644 --- 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 @@ -15,22 +15,40 @@

Resolution and size

- - - {{ ratio }} - - +
+ + + {{ ratio }} + + + + + {{ 'custom' | 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 index eeb4cf610..2b3e02af5 100644 --- 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 @@ -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; 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 index 9830933a4..28939128e 100644 --- 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 @@ -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 { - const updateProjector: Partial = 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); } } } diff --git a/openslides/core/migrations/0026_projector_size_1.py b/openslides/core/migrations/0026_projector_size_1.py new file mode 100644 index 000000000..07d1cec47 --- /dev/null +++ b/openslides/core/migrations/0026_projector_size_1.py @@ -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), + ), + ] diff --git a/openslides/core/migrations/0027_projector_size_2.py b/openslides/core/migrations/0027_projector_size_2.py new file mode 100644 index 000000000..7186e39c9 --- /dev/null +++ b/openslides/core/migrations/0027_projector_size_2.py @@ -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), + ] diff --git a/openslides/core/migrations/0028_projector_size_3.py b/openslides/core/migrations/0028_projector_size_3.py new file mode 100644 index 000000000..81b623232 --- /dev/null +++ b/openslides/core/migrations/0028_projector_size_3.py @@ -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",), + ] diff --git a/openslides/core/models.py b/openslides/core/models.py index 393ab0796..c2324609d 100644 --- a/openslides/core/models.py +++ b/openslides/core/models.py @@ -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") diff --git a/openslides/core/serializers.py b/openslides/core/serializers.py index c815b8f99..0f6217ef8 100644 --- a/openslides/core/serializers.py +++ b/openslides/core/serializers.py @@ -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",