Control the resolution of the projector
This commit is contained in:
parent
5afffaba84
commit
a8dcc2abdc
@ -4,13 +4,15 @@ from django.utils.translation import ugettext as _
|
|||||||
from .exceptions import ConfigError, ConfigNotFound
|
from .exceptions import ConfigError, ConfigNotFound
|
||||||
from .models import ConfigStore
|
from .models import ConfigStore
|
||||||
|
|
||||||
|
# remove resolution when changing to multiprojector
|
||||||
INPUT_TYPE_MAPPING = {
|
INPUT_TYPE_MAPPING = {
|
||||||
'string': str,
|
'string': str,
|
||||||
'text': str,
|
'text': str,
|
||||||
'integer': int,
|
'integer': int,
|
||||||
'boolean': bool,
|
'boolean': bool,
|
||||||
'choice': str,
|
'choice': str,
|
||||||
'colorpicker': str}
|
'colorpicker': str,
|
||||||
|
'resolution': dict}
|
||||||
|
|
||||||
|
|
||||||
class ConfigHandler:
|
class ConfigHandler:
|
||||||
@ -86,6 +88,16 @@ class ConfigHandler:
|
|||||||
except DjangoValidationError as e:
|
except DjangoValidationError as e:
|
||||||
raise ConfigError(e.messages[0])
|
raise ConfigError(e.messages[0])
|
||||||
|
|
||||||
|
# remove this block when changing to multiprojector
|
||||||
|
if config_variable.input_type == 'resolution':
|
||||||
|
if value.get('width') is None or value.get('height') is None:
|
||||||
|
raise ConfigError(_('A width and a height have to be given.'))
|
||||||
|
if not isinstance(value['width'], int) or not isinstance(value['height'], int):
|
||||||
|
raise ConfigError(_('Data has to be integers.'))
|
||||||
|
if (value['width'] < 800 or value['width'] > 3840 or
|
||||||
|
value['height'] < 600 or value['height'] > 2160):
|
||||||
|
raise ConfigError(_('The Resolution have to be between 800x600 and 3840x2160.'))
|
||||||
|
|
||||||
# Save the new value to the database.
|
# Save the new value to the database.
|
||||||
ConfigStore.objects.update_or_create(key=key, defaults={'value': value})
|
ConfigStore.objects.update_or_create(key=key, defaults={'value': value})
|
||||||
|
|
||||||
|
@ -155,3 +155,12 @@ def get_config_variables():
|
|||||||
label='Default countdown',
|
label='Default countdown',
|
||||||
weight=185,
|
weight=185,
|
||||||
group='Projector')
|
group='Projector')
|
||||||
|
|
||||||
|
# set the resolution for one projector. It can be removed with the multiprojector feature.
|
||||||
|
yield ConfigVariable(
|
||||||
|
name='projector_resolution',
|
||||||
|
default_value={'width': 1024, 'height': 768},
|
||||||
|
input_type='resolution',
|
||||||
|
label='Projector Resolution',
|
||||||
|
weight=200,
|
||||||
|
group='Projector')
|
||||||
|
25
openslides/core/migrations/0004_projector_resolution.py
Normal file
25
openslides/core/migrations/0004_projector_resolution.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.9 on 2016-08-25 11:56
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0003_auto_20160815_1911'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='projector',
|
||||||
|
name='height',
|
||||||
|
field=models.PositiveIntegerField(default=768),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='projector',
|
||||||
|
name='width',
|
||||||
|
field=models.PositiveIntegerField(default=1024),
|
||||||
|
),
|
||||||
|
]
|
@ -66,6 +66,11 @@ class Projector(RESTModelMixin, models.Model):
|
|||||||
|
|
||||||
scroll = models.IntegerField(default=0)
|
scroll = models.IntegerField(default=0)
|
||||||
|
|
||||||
|
# currently unused, but important for the multiprojector.
|
||||||
|
width = models.PositiveIntegerField(default=1024)
|
||||||
|
|
||||||
|
height = models.PositiveIntegerField(default=768)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""
|
"""
|
||||||
Contains general permissions that can not be placed in a specific app.
|
Contains general permissions that can not be placed in a specific app.
|
||||||
|
@ -30,7 +30,7 @@ class ProjectorSerializer(ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Projector
|
model = Projector
|
||||||
fields = ('id', 'config', 'elements', 'scale', 'scroll', )
|
fields = ('id', 'config', 'elements', 'scale', 'scroll', 'width', 'height',)
|
||||||
|
|
||||||
|
|
||||||
class CustomSlideSerializer(ModelSerializer):
|
class CustomSlideSerializer(ModelSerializer):
|
||||||
|
@ -322,6 +322,14 @@ img {
|
|||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* .resolution can be removed with the multiprojector, but maybe
|
||||||
|
* it could be reused for the settings in the projectormanage-view */
|
||||||
|
.col1 .input-group .resolution {
|
||||||
|
float: none;
|
||||||
|
display: inline-block;
|
||||||
|
width: 80px;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Toolbar to save motion in inline editing mode */
|
/* Toolbar to save motion in inline editing mode */
|
||||||
.motion-save-toolbar {
|
.motion-save-toolbar {
|
||||||
@ -633,23 +641,14 @@ img {
|
|||||||
|
|
||||||
/* iframe for live view */
|
/* iframe for live view */
|
||||||
.col2 #iframe {
|
.col2 #iframe {
|
||||||
width: 1024px;
|
|
||||||
height: 768px;
|
|
||||||
-moz-transform-origin: 0 0;
|
-moz-transform-origin: 0 0;
|
||||||
-webkit-transform-origin: 0 0;
|
-webkit-transform-origin: 0 0;
|
||||||
-o-transform-origin: 0 0;
|
-o-transform-origin: 0 0;
|
||||||
transform-origin: 0 0 0;
|
transform-origin: 0 0 0;
|
||||||
-moz-transform: scale(0.25);
|
|
||||||
-webkit-transform: scale(0.25);
|
|
||||||
-o-transform: scale(0.25);
|
|
||||||
transform: scale(0.25);
|
|
||||||
/* IE8+ - must be on one line, unfortunately */
|
|
||||||
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.25, M12=0, M21=0, M22=0.25, SizingMethod='auto expand')";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.col2 #iframewrapper {
|
.col2 #iframewrapper {
|
||||||
width: 256px;
|
width: 256px;
|
||||||
height: 192px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid #D5D5D5;
|
border: 1px solid #D5D5D5;
|
||||||
@ -658,7 +657,6 @@ img {
|
|||||||
|
|
||||||
.col2 #iframeoverlay {
|
.col2 #iframeoverlay {
|
||||||
width: 256px;
|
width: 256px;
|
||||||
height: 192px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
@ -666,9 +664,7 @@ img {
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Footer **/
|
/** Footer **/
|
||||||
|
|
||||||
#footer {
|
#footer {
|
||||||
float: left;
|
float: left;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
@ -684,6 +680,16 @@ img {
|
|||||||
background-color: #317796;
|
background-color: #317796;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-entries {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-entries > li {
|
||||||
|
padding: 5px 10px 5px 10px;
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.slimDropDown {
|
.slimDropDown {
|
||||||
padding-left: 4px !important;
|
padding-left: 4px !important;
|
||||||
padding-right: 4px !important;
|
padding-right: 4px !important;
|
||||||
|
@ -11,6 +11,44 @@ body{
|
|||||||
color: #222;
|
color: #222;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*** ProjectorContainer ***/
|
||||||
|
.pContainer {
|
||||||
|
background-color: #222;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pContainer > div {
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pContainer #iframe {
|
||||||
|
-moz-transform-origin: 0 0;
|
||||||
|
-webkit-transform-origin: 0 0;
|
||||||
|
-o-transform-origin: 0 0;
|
||||||
|
transform-origin: 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pContainer #iframewrapper {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pContainer #iframeoverlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
display: block;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
/*** HEADER ***/
|
/*** HEADER ***/
|
||||||
#header {
|
#header {
|
||||||
box-shadow: 0 0 7px rgba(0,0,0,0.6);
|
box-shadow: 0 0 7px rgba(0,0,0,0.6);
|
||||||
|
@ -286,7 +286,6 @@ angular.module('OpenSlidesApp.core', [
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
.factory('jsDataModel', [
|
.factory('jsDataModel', [
|
||||||
'$http',
|
'$http',
|
||||||
'Projector',
|
'Projector',
|
||||||
|
@ -57,6 +57,65 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// Projector Container Controller
|
||||||
|
.controller('ProjectorContainerCtrl', [
|
||||||
|
'$scope',
|
||||||
|
'Config',
|
||||||
|
function($scope, Config) {
|
||||||
|
// watch for changes in Config
|
||||||
|
var last_conf;
|
||||||
|
$scope.$watch(function () {
|
||||||
|
return Config.lastModified();
|
||||||
|
}, function () {
|
||||||
|
var conf = Config.get('projector_resolution').value;
|
||||||
|
// With multiprojector, get the resolution from Prjector.get(pk).{width; height}
|
||||||
|
if(!last_conf || last_conf.width != conf.width || last-conf.height != conf.height) {
|
||||||
|
last_conf = conf;
|
||||||
|
$scope.projectorWidth = conf.width;
|
||||||
|
$scope.projectorHeight = conf.height;
|
||||||
|
$scope.recalculateIframe();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// recalculate the actual Iframesize and scale
|
||||||
|
$scope.recalculateIframe = function () {
|
||||||
|
var scale_width = window.innerWidth / $scope.projectorWidth;
|
||||||
|
var scale_height = window.innerHeight / $scope.projectorHeight;
|
||||||
|
|
||||||
|
if (scale_width > 1 && scale_height > 1) {
|
||||||
|
// Iframe fits in full size in the window
|
||||||
|
$scope.scale = 1;
|
||||||
|
$scope.iframeWidth = $scope.projectorWidth;
|
||||||
|
$scope.iframeHeight = $scope.projectorHeight;
|
||||||
|
} else {
|
||||||
|
// Iframe has to be scaled down
|
||||||
|
if (scale_width <= scale_height) {
|
||||||
|
// width is the reference
|
||||||
|
$scope.iframeWidth = window.innerWidth;
|
||||||
|
$scope.scale = scale_width;
|
||||||
|
$scope.iframeHeight = $scope.projectorHeight * scale_width;
|
||||||
|
} else {
|
||||||
|
// height is the reference
|
||||||
|
$scope.iframeHeight = window.innerHeight;
|
||||||
|
$scope.scale = scale_height;
|
||||||
|
$scope.iframeWidth = $scope.projectorWidth * scale_height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// watch for changes in the windowsize
|
||||||
|
$(window).on("resize.doResize", function () {
|
||||||
|
$scope.$apply(function() {
|
||||||
|
$scope.recalculateIframe();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.$on("$destroy",function (){
|
||||||
|
$(window).off("resize.doResize");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
.controller('ProjectorCtrl', [
|
.controller('ProjectorCtrl', [
|
||||||
'$scope',
|
'$scope',
|
||||||
'Projector',
|
'Projector',
|
||||||
|
@ -602,6 +602,15 @@ angular.module('OpenSlidesApp.core.site', [
|
|||||||
})
|
})
|
||||||
.state('projector', {
|
.state('projector', {
|
||||||
url: '/projector',
|
url: '/projector',
|
||||||
|
templateUrl: 'static/templates/projector-container.html',
|
||||||
|
data: {extern: true},
|
||||||
|
onEnter: function($window) {
|
||||||
|
$window.location.href = this.url;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('real-projector', {
|
||||||
|
url: '/real-projector',
|
||||||
|
templateUrl: 'static/templates/projector.html',
|
||||||
data: {extern: true},
|
data: {extern: true},
|
||||||
onEnter: function($window) {
|
onEnter: function($window) {
|
||||||
$window.location.href = this.url;
|
$window.location.href = this.url;
|
||||||
@ -802,6 +811,7 @@ angular.module('OpenSlidesApp.core.site', [
|
|||||||
'Config',
|
'Config',
|
||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
function($parse, Config, gettextCatalog) {
|
function($parse, Config, gettextCatalog) {
|
||||||
|
// remove resolution when changing to multiprojector
|
||||||
function getHtmlType(type) {
|
function getHtmlType(type) {
|
||||||
return {
|
return {
|
||||||
string: 'text',
|
string: 'text',
|
||||||
@ -810,6 +820,7 @@ angular.module('OpenSlidesApp.core.site', [
|
|||||||
boolean: 'checkbox',
|
boolean: 'checkbox',
|
||||||
choice: 'choice',
|
choice: 'choice',
|
||||||
colorpicker: 'colorpicker',
|
colorpicker: 'colorpicker',
|
||||||
|
resolution: 'resolution',
|
||||||
}[type];
|
}[type];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1076,6 +1087,22 @@ angular.module('OpenSlidesApp.core.site', [
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// watch for changes in Config
|
||||||
|
var last_conf;
|
||||||
|
$scope.$watch(function () {
|
||||||
|
return Config.lastModified();
|
||||||
|
}, function () {
|
||||||
|
var conf = Config.get('projector_resolution').value;
|
||||||
|
// With multiprojector, get the resolution from Prjector.get(pk).{width; height}
|
||||||
|
if(!last_conf || last_conf.width != conf.width || last-conf.height != conf.height) {
|
||||||
|
last_conf = conf;
|
||||||
|
$scope.projectorWidth = conf.width;
|
||||||
|
$scope.projectorHeight = conf.height;
|
||||||
|
$scope.scale = 256.0 / $scope.projectorWidth;
|
||||||
|
$scope.iframeHeight = $scope.scale * $scope.projectorHeight;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// *** countdown functions ***
|
// *** countdown functions ***
|
||||||
$scope.calculateCountdownTime = function (countdown) {
|
$scope.calculateCountdownTime = function (countdown) {
|
||||||
countdown.seconds = Math.floor( countdown.countdown_time - Date.now() / 1000 + $scope.serverOffset );
|
countdown.seconds = Math.floor( countdown.countdown_time - Date.now() / 1000 + $scope.serverOffset );
|
||||||
|
@ -11,6 +11,26 @@
|
|||||||
id="{{ key }}"
|
id="{{ key }}"
|
||||||
type="{{ type }}">
|
type="{{ type }}">
|
||||||
|
|
||||||
|
<!-- resolution -->
|
||||||
|
<!-- Can be removed with multiprojector, but maybe it could be reused in the projectormanage-view -->
|
||||||
|
<!-- if removed, remember to delete the class resolution -->
|
||||||
|
<span ng-if="type == 'resolution'">
|
||||||
|
<translate>Width</translate>:
|
||||||
|
<input ng-model="$parent.value.width"
|
||||||
|
ng-model-option="{debounce: 1000}"
|
||||||
|
ng-change="save(configOption.key, $parent.value)"
|
||||||
|
class="form-control resolution"
|
||||||
|
id="{{ key }}_width"
|
||||||
|
type="number">
|
||||||
|
<translate>Height</translate>:
|
||||||
|
<input ng-model="$parent.value.height"
|
||||||
|
ng-model-option="{debounce: 1000}"
|
||||||
|
ng-change="save(configOption.key, $parent.value)"
|
||||||
|
class="form-control resolution"
|
||||||
|
id="{{ key }}_height"
|
||||||
|
type="number">
|
||||||
|
</span>
|
||||||
|
|
||||||
<!-- colorpicker -->
|
<!-- colorpicker -->
|
||||||
<input ng-if="type == 'colorpicker'"
|
<input ng-if="type == 'colorpicker'"
|
||||||
colorpicker
|
colorpicker
|
||||||
|
@ -9,9 +9,30 @@
|
|||||||
<h4 translate>Live view</h4>
|
<h4 translate>Live view</h4>
|
||||||
</a>
|
</a>
|
||||||
<div uib-collapse="isLiveViewClosed" ng-cloak>
|
<div uib-collapse="isLiveViewClosed" ng-cloak>
|
||||||
|
<style>
|
||||||
|
/* iframe for live view */
|
||||||
|
.col2 #iframe {
|
||||||
|
width: {{ projectorWidth }}px;
|
||||||
|
height: {{ projectorHeight }}px;
|
||||||
|
-moz-transform: scale({{ scale }});
|
||||||
|
-webkit-transform: scale({{ scale }});
|
||||||
|
-o-transform: scale({{ scale }});
|
||||||
|
transform: scale({{ scale }});
|
||||||
|
/* IE8+ - must be on one line, unfortunately */
|
||||||
|
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11={{ scale }}, M12=0, M21=0, M22={{ scale }}, SizingMethod='auto expand')";
|
||||||
|
}
|
||||||
|
|
||||||
|
.col2 #iframewrapper {
|
||||||
|
height: {{ iframeHeight }}px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col2 #iframeoverlay {
|
||||||
|
height: {{ iframeHeight }}px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<a ui-sref="projector" target="_blank">
|
<a ui-sref="projector" target="_blank">
|
||||||
<div id="iframewrapper">
|
<div id="iframewrapper">
|
||||||
<iframe id="iframe" src="/projector" frameborder="0"></iframe>
|
<iframe id="iframe" src="/real-projector" frameborder="0"></iframe>
|
||||||
<div id="iframeoverlay"></div>
|
<div id="iframeoverlay"></div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
45
openslides/core/static/templates/projector-container.html
Normal file
45
openslides/core/static/templates/projector-container.html
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" class="no-js">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<base href="/">
|
||||||
|
<title>OpenSlides – Projector</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;">
|
||||||
|
<link rel="stylesheet" href="static/css/openslides-libs.css">
|
||||||
|
<link rel="stylesheet" href="static/css/projector.css">
|
||||||
|
<link rel="icon" href="/static/img/favicon.png">
|
||||||
|
<script src="static/js/openslides-libs.js"></script>
|
||||||
|
|
||||||
|
<div ng-controller="ProjectorContainerCtrl" class="pContainer">
|
||||||
|
<div>
|
||||||
|
<style>
|
||||||
|
.pContainer #iframe {
|
||||||
|
width: {{ projectorWidth }}px;
|
||||||
|
height: {{ projectorHeight }}px;
|
||||||
|
-moz-transform: scale({{ scale }});
|
||||||
|
-webkit-transform: scale({{ scale }});
|
||||||
|
-o-transform: scale({{ scale }});
|
||||||
|
transform: scale({{ scale }});
|
||||||
|
/* IE8+ - must be on one line, unfortunately */
|
||||||
|
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11={{ scale }}, M12=0, M21=0, M22={{ scale }}, SizingMethod='auto expand')";
|
||||||
|
}
|
||||||
|
|
||||||
|
.pContainer #iframewrapper {
|
||||||
|
width: {{ iframeWidth }}px;
|
||||||
|
height: {{ iframeHeight }}px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pContainer #iframeoverlay {
|
||||||
|
width: {{ iframeWidth }}px;
|
||||||
|
height: {{ iframeHeight }}px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div id="iframewrapper">
|
||||||
|
<iframe id="iframe" src="/real-projector" frameborder="0"></iframe>
|
||||||
|
<div id="iframeoverlay"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/angular_js/projector/"></script>
|
@ -1,6 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="no-js">
|
<html lang="en" class="no-js">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<base href="/">
|
<base href="/">
|
||||||
<title>OpenSlides – Projector</title>
|
<title>OpenSlides – Projector</title>
|
||||||
<link rel="stylesheet" href="static/css/openslides-libs.css">
|
<link rel="stylesheet" href="static/css/openslides-libs.css">
|
||||||
|
@ -27,9 +27,12 @@ urlpatterns = [
|
|||||||
views.AppsJsView.as_view(),
|
views.AppsJsView.as_view(),
|
||||||
name='core_apps_js'),
|
name='core_apps_js'),
|
||||||
|
|
||||||
# View for the projectors are handelt by angular.
|
# View for the projectors are handled by angular.
|
||||||
url(r'^projector.*$', views.ProjectorView.as_view()),
|
url(r'^projector.*$', views.ProjectorView.as_view()),
|
||||||
|
|
||||||
|
# Original view without resolutioncontrol for the projectors are handled by angular.
|
||||||
|
url(r'^real-projector.*$', views.RealProjectorView.as_view()),
|
||||||
|
|
||||||
# Main entry point for all angular pages.
|
# Main entry point for all angular pages.
|
||||||
# Has to be the last entry in the urls.py
|
# Has to be the last entry in the urls.py
|
||||||
url(r'^.*$', views.IndexView.as_view()),
|
url(r'^.*$', views.IndexView.as_view()),
|
||||||
|
@ -66,6 +66,20 @@ class ProjectorView(utils_views.View):
|
|||||||
"""
|
"""
|
||||||
The primary view for OpenSlides projector using AngularJS.
|
The primary view for OpenSlides projector using AngularJS.
|
||||||
|
|
||||||
|
The projector container template is 'openslides/core/static/templates/projector-container.html'.
|
||||||
|
This container is for controlling the projector resolution.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
with open(finders.find('templates/projector-container.html')) as f:
|
||||||
|
content = f.read()
|
||||||
|
return HttpResponse(content)
|
||||||
|
|
||||||
|
|
||||||
|
class RealProjectorView(utils_views.View):
|
||||||
|
"""
|
||||||
|
The original view without resolutioncontrol for OpenSlides projector using AngularJS.
|
||||||
|
|
||||||
The default base template is 'openslides/core/static/templates/projector.html'.
|
The default base template is 'openslides/core/static/templates/projector.html'.
|
||||||
You can override it by simply adding a custom 'templates/projector.html'
|
You can override it by simply adding a custom 'templates/projector.html'
|
||||||
file to the custom staticfiles directory. See STATICFILES_DIRS in
|
file to the custom staticfiles directory. See STATICFILES_DIRS in
|
||||||
@ -168,7 +182,7 @@ class ProjectorViewSet(ReadOnlyModelViewSet):
|
|||||||
elif self.action in ('metadata', 'list'):
|
elif self.action in ('metadata', 'list'):
|
||||||
result = self.request.user.has_perm('core.can_see_projector')
|
result = self.request.user.has_perm('core.can_see_projector')
|
||||||
elif self.action in ('activate_elements', 'prune_elements', 'update_elements',
|
elif self.action in ('activate_elements', 'prune_elements', 'update_elements',
|
||||||
'deactivate_elements', 'clear_elements', 'control_view'):
|
'deactivate_elements', 'clear_elements', 'control_view', 'set_resolution'):
|
||||||
result = (self.request.user.has_perm('core.can_see_projector') and
|
result = (self.request.user.has_perm('core.can_see_projector') and
|
||||||
self.request.user.has_perm('core.can_manage_projector'))
|
self.request.user.has_perm('core.can_manage_projector'))
|
||||||
else:
|
else:
|
||||||
@ -314,6 +328,46 @@ class ProjectorViewSet(ReadOnlyModelViewSet):
|
|||||||
serializer.save()
|
serializer.save()
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@detail_route(methods=['post'])
|
||||||
|
def set_resolution(self, request, pk):
|
||||||
|
"""
|
||||||
|
REST API operation to set the resolution.
|
||||||
|
|
||||||
|
It is actually unused, because the resolution is currently set in the config.
|
||||||
|
But with the multiprojector feature this will become importent to set the
|
||||||
|
resolution per projector individually.
|
||||||
|
|
||||||
|
It expects a POST request to
|
||||||
|
/rest/core/projector/<pk>/set_resolution/ with a dictionary with the width
|
||||||
|
and height and the values.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
{
|
||||||
|
"width": "1024",
|
||||||
|
"height": "768"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
if not isinstance(request.data, dict):
|
||||||
|
raise ValidationError({'detail': 'Data must be a dictionary.'})
|
||||||
|
if request.data.get('width') is None or request.data.get('height') is None:
|
||||||
|
raise ValidationError({'detail': 'A width and a height have to be given.'})
|
||||||
|
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.'})
|
||||||
|
|
||||||
|
projector_instance = self.get_object()
|
||||||
|
projector_instance.width = request.data['width']
|
||||||
|
projector_instance.height = request.data['height']
|
||||||
|
projector_instance.save()
|
||||||
|
|
||||||
|
message = 'Changing resolution to {width}x{height} was successful.'.format(
|
||||||
|
width=request.data['width'],
|
||||||
|
height=request.data['height'])
|
||||||
|
return Response({'detail': message})
|
||||||
|
|
||||||
@detail_route(methods=['post'])
|
@detail_route(methods=['post'])
|
||||||
def control_view(self, request, pk):
|
def control_view(self, request, pk):
|
||||||
"""
|
"""
|
||||||
|
@ -34,7 +34,9 @@ class ProjectorAPI(TestCase):
|
|||||||
'uuid': 'aae4a07b26534cfb9af4232f361dce73',
|
'uuid': 'aae4a07b26534cfb9af4232f361dce73',
|
||||||
'name': 'core/customslide'}},
|
'name': 'core/customslide'}},
|
||||||
'scale': 0,
|
'scale': 0,
|
||||||
'scroll': 0})
|
'scroll': 0,
|
||||||
|
'width': 1024,
|
||||||
|
'height': 768})
|
||||||
|
|
||||||
def test_invalid_slide_on_default_projector(self):
|
def test_invalid_slide_on_default_projector(self):
|
||||||
self.client.login(username='admin', password='admin')
|
self.client.login(username='admin', password='admin')
|
||||||
@ -54,7 +56,9 @@ class ProjectorAPI(TestCase):
|
|||||||
'uuid': 'fc6ef43b624043068c8e6e7a86c5a1b0',
|
'uuid': 'fc6ef43b624043068c8e6e7a86c5a1b0',
|
||||||
'error': 'Projector element does not exist.'}},
|
'error': 'Projector element does not exist.'}},
|
||||||
'scale': 0,
|
'scale': 0,
|
||||||
'scroll': 0})
|
'scroll': 0,
|
||||||
|
'width': 1024,
|
||||||
|
'height': 768})
|
||||||
|
|
||||||
|
|
||||||
class VersionView(TestCase):
|
class VersionView(TestCase):
|
||||||
|
Loading…
Reference in New Issue
Block a user