diff --git a/CHANGELOG b/CHANGELOG index 6f80a2ec4..10682dc56 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -52,6 +52,7 @@ Core: - Improved reconnect handling if the server was flushed [#3297]. - Highlight list entries in a light blue, if a related object is projected (e.g. a list of speakers of a motion) [#3301] +- Select the projector resolution with a slider and an aspect ratio [#3311]. Mediafiles: - Fixed reloading of PDF on page change [#3274]. diff --git a/README.rst b/README.rst index b6444c5ff..04cbe5758 100644 --- a/README.rst +++ b/README.rst @@ -185,6 +185,7 @@ OpenSlides uses the following projects or parts of them: * `angular-ui-router `_, License: MIT * `angular-ui-tree `_, License: MIT * `angular-xeditable `_, License: MIT + * `angularjs-slider `_, License: MIT * `api-check `_, License: MIT * `bootstrap `_, License: MIT * `bootstrap-ui-datetime-picker `_, License: MIT diff --git a/bower.json b/bower.json index 526ae4266..71927d95f 100644 --- a/bower.json +++ b/bower.json @@ -20,6 +20,7 @@ "angular-ui-router": "~0.3.1", "angular-ui-tree": "~2.22.0", "angular-xeditable": "~0.5.0", + "angularjs-slider": "~6.2.2", "bootstrap-css-only": "~3.3.6", "bootstrap-ui-datetime-picker": "~2.4.0", "ckeditor": "4.6.1", diff --git a/openslides/core/static/css/app.css b/openslides/core/static/css/app.css index 249b6591a..da95f4e76 100644 --- a/openslides/core/static/css/app.css +++ b/openslides/core/static/css/app.css @@ -1444,6 +1444,31 @@ img { font-size: 85%; } +/* Custom OpenSlides slider classes */ +.os-slider { + margin-top: 40px; + margin-bottom: 12px; +} +.os-slider.rzslider .rz-bar { + background: #317796; + opacity: 0.3; + height: 2px; +} +.os-slider.rzslider .rz-selection { + background: #317796; +} +.os-slider.rzslider .rz-pointer { + width: 14px; + height: 14px; + top: -7px; + bottom: 0; + background-color: #317796; + border-radius: 7px; +} +.os-slider.rzslider .rz-pointer:after { + display: none; +} + /* ngDialog: override ngdialog-theme-default */ .ngdialog.ngdialog-theme-default { diff --git a/openslides/core/static/js/core/site.js b/openslides/core/static/js/core/site.js index ec7f697a5..53cb220b9 100644 --- a/openslides/core/static/js/core/site.js +++ b/openslides/core/static/js/core/site.js @@ -20,6 +20,7 @@ angular.module('OpenSlidesApp.core.site', [ 'ckeditor', 'luegg.directives', 'xeditable', + 'rzModule', ]) // Can be used to find out if the projector or the side is used @@ -1428,6 +1429,20 @@ angular.module('OpenSlidesApp.core.site', [ ProjectorMessage, ngDialog) { ProjectionDefault.bindAll({}, $scope, 'projectiondefaults'); + /* Info on resolution calculating: + * Internally the resolution is saved as (width, height) but the user has + * an aspect ratio to choose and a width from 800 to 3840 (4K).*/ + $scope.aspectRatios = { + '4:3': 4/3, + '16:9': 16/9, + '16:10': 16/10, + }; + // when converting (x,y) -> (ratio, percentage) round issues may occur + // (e.g. 800/600 != 4/3 with internal calculation issues). With this environment + // is tested, if the calculated value is in the following interval: + // [expected-environment; expected+environment] + var RATIO_ENVIRONMENT = 0.05; + // watch for changes in projector_broadcast // and projector_currentListOfSpeakers_reference var last_broadcast, last_clos; @@ -1448,8 +1463,9 @@ angular.module('OpenSlidesApp.core.site', [ // watch for changes in Projector, and recalc scale and iframeHeight var first_watch = true; - $scope.resolutions = []; + $scope.resolutions = {}; $scope.edit = []; + $scope.sliders = {}; $scope.$watch(function () { return Projector.lastModified(); }, function () { @@ -1463,6 +1479,25 @@ angular.module('OpenSlidesApp.core.site', [ height: projector.height }; $scope.edit[projector.id] = false; + $scope.sliders[projector.id] = { + value: projector.width, + options: { + id: projector.id, + floor: 800, + ceil: 3840, + translate: function (value) { + return value + 'px'; + }, + onChange: function (v) { + $scope.calcResolution(projector); + }, + onEnd: function (v) { + $scope.saveResolution(projector); + }, + hideLimitLabels: true, + }, + }; + $scope.setAspectRatio(projector, $scope.getAspectRatio(projector)); } }); if ($scope.projectors.length) { @@ -1470,6 +1505,36 @@ angular.module('OpenSlidesApp.core.site', [ } }); + $scope.getAspectRatio = function (projector) { + var ratio = projector.width/projector.height; + var foundRatio = _.findKey($scope.aspectRatios, function (value) { + return value >= (ratio-RATIO_ENVIRONMENT) && value <= (ratio+RATIO_ENVIRONMENT); + }); + if (foundRatio === undefined) { + return _.keys($scope.aspectRatios)[0]; + } else { + return foundRatio; + } + }; + $scope.setAspectRatio = function (projector, aspectRatio) { + $scope.resolutions[projector.id].aspectRatio = aspectRatio; + $scope.resolutions[projector.id].aspectRatioNumber = $scope.aspectRatios[aspectRatio]; + $scope.calcResolution(projector); + }; + $scope.calcResolution = function (projector) { + var ratio = $scope.resolutions[projector.id].aspectRatioNumber; + var width = $scope.sliders[projector.id].value; + $scope.resolutions[projector.id].width = width; + $scope.resolutions[projector.id].height = Math.round(width/ratio); + }; + + $scope.toggleEditMenu = function (projectorId) { + $scope.edit[projectorId] = !$scope.edit[projectorId]; + $timeout(function () { + $scope.$broadcast('rzSliderForceRender'); + }); + }; + // Set list of speakers reference $scope.setListOfSpeakers = function (projector) { Config.get('projector_currentListOfSpeakers_reference').value = projector.id; @@ -1521,14 +1586,18 @@ angular.module('OpenSlidesApp.core.site', [ projector.config = projector.elements; Projector.save(projector); }; - $scope.changeResolution = function (projectorId) { + $scope.saveResolution = function (projector) { $http.post( - '/rest/core/projector/' + projectorId + '/set_resolution/', - $scope.resolutions[projectorId] + '/rest/core/projector/' + projector.id + '/set_resolution/', + $scope.resolutions[projector.id] ).then(function (success) { - $scope.resolutions[projectorId].error = null; + $scope.resolutions[projector.id].error = null; }, function (error) { - $scope.resolutions[projectorId].error = error.data.detail; + if (error.data) { + $scope.resolutions[projector.id].error = error.data.detail; + } else { + $scope.resolutions[projector.id].error = null; + } }); }; diff --git a/openslides/core/static/templates/core/manage-projectors.html b/openslides/core/static/templates/core/manage-projectors.html index 6104c6bb3..d59c0bf0e 100644 --- a/openslides/core/static/templates/core/manage-projectors.html +++ b/openslides/core/static/templates/core/manage-projectors.html @@ -45,21 +45,22 @@ {{ projector.id }}: {{ projector.name | translate }} - +
@@ -82,19 +83,30 @@
- - x - +
+ + {{ resolutions[projector.id].width }}×{{ resolutions[projector.id].height }} + + +
+
+ +

{{ resolutions[projector.id].error }} diff --git a/openslides/core/views.py b/openslides/core/views.py index 856920b9b..8a90b6187 100644 --- a/openslides/core/views.py +++ b/openslides/core/views.py @@ -404,8 +404,8 @@ class ProjectorViewSet(ModelViewSet): if not isinstance(request.data['width'], int) or not isinstance(request.data['height'], int): raise ValidationError({'detail': 'Data has to be integers.'}) if (request.data['width'] < 800 or request.data['width'] > 3840 or - request.data['height'] < 600 or request.data['height'] > 2160): - raise ValidationError({'detail': 'The Resolution have to be between 800x600 and 3840x2160.'}) + request.data['height'] < 340 or request.data['height'] > 2880): + raise ValidationError({'detail': 'The Resolution have to be between 800x340 and 3840x2880.'}) projector_instance = self.get_object() projector_instance.width = request.data['width']