Added support for multiple projectors.
This commit is contained in:
parent
447d475321
commit
e6b9b21d41
@ -20,6 +20,7 @@ Core:
|
||||
- Added support for big assemblies with lots of users.
|
||||
- Added HTML support for messages on the projector.
|
||||
- Moved custom slides to own app "topics". Renamed it to "Topic".
|
||||
- Added support for multiple projectors
|
||||
|
||||
Motions:
|
||||
- Added origin field.
|
||||
|
@ -64,3 +64,32 @@ class ListOfSpeakersSlide(ProjectorElement):
|
||||
# Full update if item changes because then we may have new speakers
|
||||
# and therefor need new users.
|
||||
return collection_element.collection_string == Item.get_collection_string()
|
||||
|
||||
|
||||
class CurrentListOfSpeakersSlide(ProjectorElement):
|
||||
"""
|
||||
Slide for the current list of speakers.
|
||||
|
||||
Nothing special to check.
|
||||
"""
|
||||
name = 'agenda/current-list-of-speakers'
|
||||
|
||||
def get_requirements(self, config_entry):
|
||||
pk = config['projector_currentListOfSpeakers_reference']
|
||||
if pk is not None:
|
||||
# List of speakers slide.
|
||||
try:
|
||||
item = Item.objects.get(pk=pk)
|
||||
except Item.DoesNotExist:
|
||||
# Item does not exist. Just do nothing.
|
||||
pass
|
||||
else:
|
||||
yield item
|
||||
for speaker in item.speakers.filter(end_time=None):
|
||||
# Yield current speaker and next speakers
|
||||
yield speaker.user
|
||||
query = (item.speakers.exclude(end_time=None)
|
||||
.order_by('-end_time')[:config['agenda_show_last_speakers']])
|
||||
for speaker in query:
|
||||
# Yield last speakers
|
||||
yield speaker.user
|
||||
|
@ -112,61 +112,84 @@ angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users'])
|
||||
}
|
||||
},
|
||||
// override project function of jsDataModel factory
|
||||
project: function() {
|
||||
return $http.post(
|
||||
'/rest/core/projector/1/prune_elements/',
|
||||
[{name: this.content_object.collection, id: this.content_object.id}]
|
||||
);
|
||||
project: function (projectorId, tree) {
|
||||
var isProjectedId = this.isProjected(tree);
|
||||
if (isProjectedId > 0) {
|
||||
// Deactivate
|
||||
$http.post('/rest/core/projector/' + isProjectedId + '/clear_elements/');
|
||||
}
|
||||
// Activate, if the projector_id is a new projector.
|
||||
if (isProjectedId != projectorId) {
|
||||
var name = tree ? 'agenda/item-list' : this.content_object.collection;
|
||||
var id = tree ? this.id : this.content_object.id;
|
||||
return $http.post(
|
||||
'/rest/core/projector/' + projectorId + '/prune_elements/',
|
||||
[{name: name, tree: tree, id: id}]
|
||||
);
|
||||
}
|
||||
},
|
||||
// override isProjected function of jsDataModel factory
|
||||
isProjected: function (list) {
|
||||
// Returns true if there is a projector element with the same
|
||||
// name and the same id.
|
||||
var projector = Projector.get(1);
|
||||
var isProjected;
|
||||
if (typeof projector !== 'undefined') {
|
||||
var self = this;
|
||||
var predicate = function (element) {
|
||||
var value;
|
||||
if (typeof list === 'undefined') {
|
||||
// Releated item detail slide
|
||||
value = element.name == self.content_object.collection &&
|
||||
typeof element.id !== 'undefined' &&
|
||||
element.id == self.content_object.id;
|
||||
} else {
|
||||
// Item list slide for sub tree
|
||||
value = element.name == 'agenda/item-list' &&
|
||||
typeof element.id !== 'undefined' &&
|
||||
element.id == self.id;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
isProjected = typeof _.findKey(projector.elements, predicate) === 'string';
|
||||
} else {
|
||||
isProjected = false;
|
||||
isProjected: function (tree) {
|
||||
// Returns the id of the last projector with an agenda-item element. Else return 0.
|
||||
if (typeof tree === 'undefined') {
|
||||
tree = false;
|
||||
}
|
||||
var self = this;
|
||||
var predicate = function (element) {
|
||||
var value;
|
||||
if (tree) {
|
||||
// Item tree slide for sub tree
|
||||
value = element.name == 'agenda/item-list' &&
|
||||
typeof element.id !== 'undefined' &&
|
||||
element.id == self.id;
|
||||
} else {
|
||||
// Releated item detail slide
|
||||
value = element.name == self.content_object.collection &&
|
||||
typeof element.id !== 'undefined' &&
|
||||
element.id == self.content_object.id;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
var isProjected = 0;
|
||||
Projector.getAll().forEach(function (projector) {
|
||||
if (typeof _.findKey(projector.elements, predicate) === 'string') {
|
||||
isProjected = projector.id;
|
||||
}
|
||||
});
|
||||
return isProjected;
|
||||
},
|
||||
// project list of speakers
|
||||
projectListOfSpeakers: function() {
|
||||
return $http.post(
|
||||
'/rest/core/projector/1/prune_elements/',
|
||||
[{name: 'agenda/list-of-speakers', id: this.id}]
|
||||
);
|
||||
projectListOfSpeakers: function(projectorId) {
|
||||
var isProjectedId = this.isListOfSpeakersProjected();
|
||||
if (isProjectedId > 0) {
|
||||
// Deactivate
|
||||
$http.post('/rest/core/projector/' + isProjectedId + '/clear_elements/');
|
||||
}
|
||||
// Activate
|
||||
if (isProjectedId != projectorId) {
|
||||
return $http.post(
|
||||
'/rest/core/projector/' + projectorId + '/prune_elements/',
|
||||
[{name: 'agenda/list-of-speakers', id: this.id}]
|
||||
);
|
||||
}
|
||||
},
|
||||
// check if list of speakers is projected
|
||||
isListOfSpeakersProjected: function () {
|
||||
// Returns true if there is a projector element with the
|
||||
// Returns the id of the last projector with an element with the
|
||||
// name 'agenda/list-of-speakers' and the same id.
|
||||
var projector = Projector.get(1);
|
||||
if (typeof projector === 'undefined') return false;
|
||||
var self = this;
|
||||
var predicate = function (element) {
|
||||
return element.name == 'agenda/list-of-speakers' &&
|
||||
typeof element.id !== 'undefined' &&
|
||||
element.id == self.id;
|
||||
};
|
||||
return typeof _.findKey(projector.elements, predicate) === 'string';
|
||||
var isProjected = 0;
|
||||
Projector.getAll().forEach(function (projector) {
|
||||
if (typeof _.findKey(projector.elements, predicate) === 'string') {
|
||||
isProjected = projector.id;
|
||||
}
|
||||
});
|
||||
return isProjected;
|
||||
},
|
||||
hasSubitems: function(items) {
|
||||
var self = this;
|
||||
@ -265,6 +288,56 @@ angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users'])
|
||||
}
|
||||
])
|
||||
|
||||
// TODO: Remove all find() calls from the projector logic. It is also used on the site so this has to be
|
||||
// changed with the refactoring of the site autoupdate.
|
||||
.factory('CurrentListOfSpeakersItem', [
|
||||
'Projector',
|
||||
'Assignment', // TODO: Remove this after refactoring of data loading on start.
|
||||
'Topic', // TODO: Remove this after refactoring of data loading on start.
|
||||
'Motion', // TODO: Remove this after refactoring of data loading on start.
|
||||
'Agenda',
|
||||
function (Projector, Assignment, Topic, Motion, Agenda) {
|
||||
return {
|
||||
getItem: function (projectorId) {
|
||||
var elementPromise;
|
||||
return Projector.find(projectorId).then(function (projector) {
|
||||
// scan all elements
|
||||
_.forEach(projector.elements, function(element) {
|
||||
switch(element.name) {
|
||||
case 'motions/motion':
|
||||
elementPromise = Motion.find(element.id).then(function(motion) {
|
||||
return Motion.loadRelations(motion, 'agenda_item').then(function() {
|
||||
return motion.agenda_item;
|
||||
});
|
||||
});
|
||||
break;
|
||||
case 'topics/topic':
|
||||
elementPromise = Topic.find(element.id).then(function(topic) {
|
||||
return Topic.loadRelations(topic, 'agenda_item').then(function() {
|
||||
return topic.agenda_item;
|
||||
});
|
||||
});
|
||||
break;
|
||||
case 'assignments/assignment':
|
||||
elementPromise = Assignment.find(element.id).then(function(assignment) {
|
||||
return Assignment.loadRelations(assignment, 'agenda_item').then(function() {
|
||||
return assignment.agenda_item;
|
||||
});
|
||||
});
|
||||
break;
|
||||
case 'agenda/list-of-speakers':
|
||||
elementPromise = Agenda.find(element.id).then(function(item) {
|
||||
return item;
|
||||
});
|
||||
}
|
||||
});
|
||||
return elementPromise;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
// Make sure that the Agenda resource is loaded.
|
||||
.run(['Agenda', function(Agenda) {}]);
|
||||
|
||||
|
@ -13,6 +13,39 @@ angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda'])
|
||||
slidesProvider.registerSlide('agenda/item-list', {
|
||||
template: 'static/templates/agenda/slide-item-list.html',
|
||||
});
|
||||
slidesProvider.registerSlide('agenda/current-list-of-speakers', {
|
||||
template: 'static/templates/agenda/slide-current-list-of-speakers.html',
|
||||
});
|
||||
}
|
||||
])
|
||||
|
||||
.controller('SlideCurrentListOfSpeakersCtrl', [
|
||||
'$scope',
|
||||
'Agenda',
|
||||
'CurrentListOfSpeakersItem',
|
||||
'Config',
|
||||
function ($scope, Agenda, CurrentListOfSpeakersItem, Config) {
|
||||
// Watch for changes in the current list of speakers reference
|
||||
$scope.$watch(function () {
|
||||
return Config.lastModified('projector_currentListOfSpeakers_reference');
|
||||
}, function () {
|
||||
$scope.currentListOfSpeakersReference = $scope.config('projector_currentListOfSpeakers_reference');
|
||||
$scope.updateCurrentListOfSpeakers();
|
||||
});
|
||||
// Watch for changes in the current item.
|
||||
$scope.$watch(function () {
|
||||
return Agenda.lastModified();
|
||||
}, function () {
|
||||
$scope.updateCurrentListOfSpeakers();
|
||||
});
|
||||
$scope.updateCurrentListOfSpeakers = function () {
|
||||
var itemPromise = CurrentListOfSpeakersItem.getItem($scope.currentListOfSpeakersReference);
|
||||
if (itemPromise) {
|
||||
itemPromise.then(function(item) {
|
||||
$scope.agendaItem = item;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
@ -20,7 +53,7 @@ angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda'])
|
||||
'$scope',
|
||||
'Agenda',
|
||||
'User',
|
||||
function($scope, Agenda, User) {
|
||||
function ($scope, Agenda, User) {
|
||||
// Attention! Each object that is used here has to be dealt on server side.
|
||||
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||
// class.
|
||||
@ -35,7 +68,7 @@ angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda'])
|
||||
'$filter',
|
||||
'Agenda',
|
||||
'AgendaTree',
|
||||
function($scope, $http, $filter, Agenda, AgendaTree) {
|
||||
function ($scope, $http, $filter, Agenda, AgendaTree) {
|
||||
// Attention! Each object that is used here has to be dealt on server side.
|
||||
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||
// class.
|
||||
|
@ -99,7 +99,8 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
||||
'TopicForm', // TODO: Remove this dependency. Use template hook for "New" and "Import" buttons.
|
||||
'AgendaTree',
|
||||
'Projector',
|
||||
function($scope, $filter, $http, $state, DS, operator, ngDialog, Agenda, TopicForm, AgendaTree, Projector) {
|
||||
'ProjectionDefault',
|
||||
function($scope, $filter, $http, $state, DS, operator, ngDialog, Agenda, TopicForm, AgendaTree, Projector, ProjectionDefault) {
|
||||
// Bind agenda tree to the scope
|
||||
$scope.$watch(function () {
|
||||
return Agenda.lastModified();
|
||||
@ -110,6 +111,17 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
||||
$scope.agendaHasSubitems = true;
|
||||
}
|
||||
});
|
||||
Projector.bindAll({}, $scope, 'projectors');
|
||||
$scope.mainListTree = true;
|
||||
$scope.$watch(function () {
|
||||
return Projector.lastModified();
|
||||
}, function () {
|
||||
var projectiondefault = ProjectionDefault.filter({name: 'agenda_all_items'})[0];
|
||||
if (projectiondefault) {
|
||||
$scope.defaultProjectorId_all_items = projectiondefault.projector_id;
|
||||
}
|
||||
$scope.projectionDefaults = ProjectionDefault.getAll();
|
||||
});
|
||||
$scope.alert = {};
|
||||
|
||||
$scope.sumDurations = function () {
|
||||
@ -218,34 +230,78 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
||||
$scope.uncheckAll();
|
||||
};
|
||||
|
||||
/** Project functions **/
|
||||
// get ProjectionDefault for item
|
||||
$scope.getProjectionDefault = function (item) {
|
||||
if (item.tree) {
|
||||
return $scope.defaultProjectorId_all_items;
|
||||
} else {
|
||||
var app_name = item.content_object.collection.split('/')[0];
|
||||
var id = 1;
|
||||
$scope.projectionDefaults.forEach(function (projectionDefault) {
|
||||
if (projectionDefault.name == app_name) {
|
||||
id = projectionDefault.projector_id;
|
||||
}
|
||||
});
|
||||
return id;
|
||||
}
|
||||
};
|
||||
// project agenda
|
||||
$scope.projectAgenda = function (tree, id) {
|
||||
$http.post('/rest/core/projector/1/prune_elements/',
|
||||
$scope.projectAgenda = function (projectorId, tree, id) {
|
||||
var isAgendaProjectedId = $scope.isAgendaProjected($scope.mainListTree);
|
||||
if (isAgendaProjectedId > 0) {
|
||||
// Deactivate
|
||||
$http.post('/rest/core/projector/' + isAgendaProjectedId + '/prune_elements/', []);
|
||||
}
|
||||
if (isAgendaProjectedId != projectorId) {
|
||||
$http.post('/rest/core/projector/' + projectorId + '/prune_elements/',
|
||||
[{name: 'agenda/item-list', tree: tree, id: id}]);
|
||||
}
|
||||
};
|
||||
// change whether all items or only main items should be projected
|
||||
$scope.changeMainListTree = function () {
|
||||
var isAgendaProjectedId = $scope.isAgendaProjected($scope.mainListTree);
|
||||
$scope.mainListTree = !$scope.mainListTree;
|
||||
if (isAgendaProjectedId > 0) {
|
||||
$scope.projectAgenda(isAgendaProjectedId, $scope.mainListTree);
|
||||
}
|
||||
};
|
||||
// change whether one item or all subitems should be projected
|
||||
$scope.changeItemTree = function (item) {
|
||||
var isProjected = item.isProjected(item.tree);
|
||||
if (isProjected > 0) {
|
||||
// Deactivate and reactivate
|
||||
item.project(isProjected, item.tree);
|
||||
item.project(isProjected, !item.tree);
|
||||
}
|
||||
item.tree = !item.tree;
|
||||
};
|
||||
// check if agenda is projected
|
||||
$scope.isAgendaProjected = function (tree) {
|
||||
// Returns true if there is a projector element with the name
|
||||
// 'agenda/item-list'.
|
||||
var projector = Projector.get(1);
|
||||
if (typeof projector === 'undefined') return false;
|
||||
var self = this;
|
||||
var predicate = function (element) {
|
||||
var value;
|
||||
if (typeof tree === 'undefined') {
|
||||
// only main agenda items
|
||||
value = element.name == 'agenda/item-list' &&
|
||||
typeof element.id === 'undefined' &&
|
||||
!element.tree;
|
||||
} else {
|
||||
if (tree) {
|
||||
// tree with all agenda items
|
||||
value = element.name == 'agenda/item-list' &&
|
||||
typeof element.id === 'undefined' &&
|
||||
element.tree;
|
||||
} else {
|
||||
// only main agenda items
|
||||
value = element.name == 'agenda/item-list' &&
|
||||
typeof element.id === 'undefined' &&
|
||||
!element.tree;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
return typeof _.findKey(projector.elements, predicate) === 'string';
|
||||
var projectorId = 0;
|
||||
$scope.projectors.forEach(function (projector) {
|
||||
if (typeof _.findKey(projector.elements, predicate) === 'string') {
|
||||
projectorId = projector.id;
|
||||
}
|
||||
});
|
||||
return projectorId;
|
||||
};
|
||||
// auto numbering of agenda items
|
||||
$scope.autoNumbering = function() {
|
||||
@ -263,9 +319,24 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
||||
'Agenda',
|
||||
'User',
|
||||
'item',
|
||||
function ($scope, $filter, $http, $state, operator, Agenda, User, item) {
|
||||
'Projector',
|
||||
'ProjectionDefault',
|
||||
function ($scope, $filter, $http, $state, operator, Agenda, User, item, Projector, ProjectionDefault) {
|
||||
Agenda.bindOne(item.id, $scope, 'item');
|
||||
User.bindAll({}, $scope, 'users');
|
||||
$scope.$watch(function () {
|
||||
return Projector.lastModified();
|
||||
}, function () {
|
||||
var item_app_name = item.content_object.collection.split('/')[0];
|
||||
var projectiondefaultItem = ProjectionDefault.filter({name: item_app_name})[0];
|
||||
if (projectiondefaultItem) {
|
||||
$scope.defaultProjectorItemId = projectiondefaultItem.projector_id;
|
||||
}
|
||||
var projectiondefaultListOfSpeakers = ProjectionDefault.filter({name: 'list_of_speakers'})[0];
|
||||
if (projectiondefaultListOfSpeakers) {
|
||||
$scope.defaultProjectorListOfSpeakersId = projectiondefaultListOfSpeakers.projector_id;
|
||||
}
|
||||
});
|
||||
$scope.speakerSelectBox = {};
|
||||
$scope.alert = {};
|
||||
$scope.speakers = $filter('orderBy')(item.speakers, 'weight');
|
||||
@ -428,50 +499,69 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
||||
'$state',
|
||||
'$http',
|
||||
'Projector',
|
||||
'Assignment', // TODO: Remove this after refactoring of data loading on start.
|
||||
'Topic', // TODO: Remove this after refactoring of data loading on start.
|
||||
'Motion', // TODO: Remove this after refactoring of data loading on start.
|
||||
'Agenda',
|
||||
function($scope, $state, $http, Projector, Assignment, Topic, Motion, Agenda) {
|
||||
$scope.$watch(
|
||||
function() {
|
||||
return Projector.lastModified(1);
|
||||
},
|
||||
function() {
|
||||
Projector.find(1).then( function(projector) {
|
||||
$scope.AgendaItem = null;
|
||||
_.forEach(projector.elements, function(element) {
|
||||
switch(element.name) {
|
||||
case 'motions/motion':
|
||||
Motion.find(element.id).then(function(motion) {
|
||||
Motion.loadRelations(motion, 'agenda_item').then(function() {
|
||||
$scope.AgendaItem = motion.agenda_item;
|
||||
});
|
||||
});
|
||||
break;
|
||||
case 'topics/topic':
|
||||
Topic.find(element.id).then(function(topic) {
|
||||
Topic.loadRelations(topic, 'agenda_item').then(function() {
|
||||
$scope.AgendaItem = topic.agenda_item;
|
||||
});
|
||||
});
|
||||
break;
|
||||
case 'assignments/assignment':
|
||||
Assignment.find(element.id).then(function(assignment) {
|
||||
Assignment.loadRelations(assignment, 'agenda_item').then(function() {
|
||||
$scope.AgendaItem = assignment.agenda_item;
|
||||
});
|
||||
});
|
||||
break;
|
||||
case 'agenda/list-of-speakers':
|
||||
Agenda.find(element.id).then(function(item) {
|
||||
$scope.AgendaItem = item;
|
||||
});
|
||||
}
|
||||
});
|
||||
'ProjectionDefault',
|
||||
'Config',
|
||||
'CurrentListOfSpeakersItem',
|
||||
function($scope, $state, $http, Projector, ProjectionDefault, Config, CurrentListOfSpeakersItem) {
|
||||
// Watch for changes in the current list of speakers reference
|
||||
$scope.$watch(function () {
|
||||
return Config.lastModified('projector_currentListOfSpeakers_reference');
|
||||
}, function () {
|
||||
$scope.currentListOfSpeakersReference = $scope.config('projector_currentListOfSpeakers_reference');
|
||||
$scope.updateCurrentListOfSpeakers();
|
||||
});
|
||||
$scope.$watch(function() {
|
||||
return Projector.lastModified();
|
||||
}, function() {
|
||||
$scope.projectors = Projector.getAll();
|
||||
$scope.updateCurrentListOfSpeakers();
|
||||
});
|
||||
$scope.$watch(function () {
|
||||
return Projector.lastModified();
|
||||
}, function () {
|
||||
var projectiondefault = ProjectionDefault.filter({name: 'current_list_of_speakers'})[0];
|
||||
if (projectiondefault) {
|
||||
$scope.defaultProjectorId = projectiondefault.projector_id;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.updateCurrentListOfSpeakers = function () {
|
||||
var itemPromise = CurrentListOfSpeakersItem.getItem($scope.currentListOfSpeakersReference);
|
||||
if (itemPromise) {
|
||||
itemPromise.then(function(item) {
|
||||
$scope.AgendaItem = item;
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
// Project current list of speakers
|
||||
// same logic as in core/base.js
|
||||
$scope.projectCurrentLoS = function (projectorId) {
|
||||
var isCurrentLoSProjectedId = $scope.isCurrentLoSProjected($scope.mainListTree);
|
||||
if (isCurrentLoSProjectedId > 0) {
|
||||
// Deactivate
|
||||
$http.post('/rest/core/projector/' + isCurrentLoSProjectedId + '/prune_elements/', []);
|
||||
}
|
||||
if (isCurrentLoSProjectedId != projectorId) {
|
||||
$http.post('/rest/core/projector/' + projectorId + '/prune_elements/',
|
||||
[{name: 'agenda/current-list-of-speakers'}]);
|
||||
}
|
||||
};
|
||||
// same logic as in core/base.js
|
||||
$scope.isCurrentLoSProjected = function () {
|
||||
// Returns the projector id if there is a projector element with the name
|
||||
// 'agenda/current-list-of-speakers'. Elsewise returns 0.
|
||||
var projectorId = 0;
|
||||
$scope.projectors.forEach(function (projector) {
|
||||
var key = _.findKey(projector.elements, function (element) {
|
||||
return element.name == 'agenda/current-list-of-speakers';
|
||||
});
|
||||
if (typeof key === 'string') {
|
||||
projectorId = projector.id;
|
||||
}
|
||||
});
|
||||
return projectorId;
|
||||
};
|
||||
|
||||
// go to the list of speakers (management) of the currently
|
||||
// displayed projector slide
|
||||
$scope.goToListOfSpeakers = function() {
|
||||
|
@ -1,48 +1,61 @@
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
<div class="submenu">
|
||||
<button ng-click="isFullScreen = !isFullScreen"
|
||||
class="btn btn-sm btn-default">
|
||||
<i class="fa fa-expand fa-lg"></i>
|
||||
<translate>Fullscreen</translate>
|
||||
</button>
|
||||
<button os-perms="agenda.can_manage"
|
||||
ng-click="goToListOfSpeakers()"
|
||||
class="btn btn-sm btn-default">
|
||||
<i class="fa fa-microphone"></i>
|
||||
<translate>Manage list</translate>
|
||||
</button>
|
||||
<div class="form-inline">
|
||||
<div os-perms="core.can_manage_projector" class="btn-group" uib-dropdown
|
||||
uib-tooltip="{{ 'Projector' | translate }} {{ isCurrentLoSProjected() }}"
|
||||
tooltip-enable="isCurrentLoSProjected() > 0">
|
||||
<button type="button" class="btn btn-default btn-sm"
|
||||
title="{{ 'Project current list of speakers' | translate }}"
|
||||
ng-click="projectCurrentLoS(defaultProjectorId)"
|
||||
ng-class="{ 'btn-primary': isCurrentLoSProjected() > 0 && isCurrentLoSProjected() == defaultProjectorId}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
<translate>Current list of speakers</translate>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-sm" uib-dropdown-toggle
|
||||
ng-class="{ 'btn-primary': isCurrentLoSProjected() > 0 && isCurrentLoSProjected() != defaultProjectorId}">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="split-button">
|
||||
<li role="menuitem" ng-repeat="projector in projectors">
|
||||
<a href="" ng-click="projectCurrentLoS(projector.id)"
|
||||
ng-class="{ 'projected': isCurrentLoSProjected() == projector.id }">
|
||||
<i class="fa fa-video-camera" ng-show="isCurrentLoSProjected() == projector.id"></i>
|
||||
{{ projector.name }}
|
||||
<span ng-if="projector.id == defaultProjectorId">(<translate>Standard</translate>)</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button os-perms="agenda.can_manage"
|
||||
ng-click="goToListOfSpeakers()"
|
||||
class="btn btn-sm btn-default">
|
||||
<i class="fa fa-microphone"></i>
|
||||
<translate>Manage list</translate>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<h1 translate>List of speakers</h1>
|
||||
<h1 translate>Current list of speakers</h1>
|
||||
<h2> {{ AgendaItem.getTitle() }}
|
||||
<span class="slimlabel label label-danger ng-scope" style="" ng-if="AgendaItem.speaker_list_closed" translate>
|
||||
Closed
|
||||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content" ng-class="isFullScreen ? 'fullscreendiv' : 'details'"
|
||||
ng-click="isFullScreen? (isFullScreen = !isFullScreen) : a">
|
||||
<div ng-if="isFullScreen" class="fullscreendiv-title">
|
||||
<h1 translate>List of speakers</h1>
|
||||
<h2> {{ AgendaItem.getTitle() }}
|
||||
<span class="slimlabel label label-danger ng-scope" style=""
|
||||
ng-if="AgendaItem.speaker_list_closed" translate>Closed</span>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="content">
|
||||
<!-- Last speakers -->
|
||||
<p ng-repeat="speaker in lastSpeakers = (AgendaItem.speakers | filter: {end_time: '!!', begin_time: '!!'}) |
|
||||
limitTo: config('agenda_show_last_speakers') : (lastSpeakers.length - config('agenda_show_last_speakers'))" class="lastSpeakers">
|
||||
{{ speaker.user.get_full_name() }}
|
||||
<!-- Current speaker -->
|
||||
<p ng-repeat="speaker in currentspeakers = (AgendaItem.speakers| filter: {end_time: null, begin_time: '!!'})"
|
||||
class="currentSpeaker">
|
||||
<i class="fa fa-microphone fa-lg"></i> {{ speaker.user.get_full_name() }}
|
||||
<!-- Next speakers -->
|
||||
<ol class="nextSpeakers">
|
||||
<li ng-repeat="speaker in AgendaItem.speakers | filter: {begin_time: null} | orderBy:'weight'">
|
||||
{{ speaker.user.get_full_name() }}
|
||||
</ol>
|
||||
</div>
|
||||
<div class="details">
|
||||
<!-- Last speakers -->
|
||||
<p ng-repeat="speaker in lastSpeakers = (AgendaItem.speakers | filter: {end_time: '!!', begin_time: '!!'}) |
|
||||
limitTo: config('agenda_show_last_speakers') : (lastSpeakers.length - config('agenda_show_last_speakers'))" class="lastSpeakers">
|
||||
{{ speaker.user.get_full_name() }}
|
||||
<!-- Current speaker -->
|
||||
<p ng-repeat="speaker in currentspeakers = (AgendaItem.speakers| filter: {end_time: null, begin_time: '!!'})"
|
||||
class="currentSpeaker">
|
||||
<i class="fa fa-microphone fa-lg"></i> {{ speaker.user.get_full_name() }}
|
||||
<!-- Next speakers -->
|
||||
<ol class="nextSpeakers">
|
||||
<li ng-repeat="speaker in AgendaItem.speakers | filter: {begin_time: null} | orderBy:'weight'">
|
||||
{{ speaker.user.get_full_name() }}
|
||||
</ol>
|
||||
</div>
|
||||
|
@ -10,20 +10,37 @@
|
||||
{{ item.getContentResource().verboseName | translate }}
|
||||
</a>
|
||||
<!-- project list of speakers -->
|
||||
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
||||
ng-class="{ 'btn-primary': item.isListOfSpeakersProjected() }"
|
||||
ng-click="item.projectListOfSpeakers()">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
<translate>List of speakers</translate>
|
||||
</a>
|
||||
<span class="btn-group" style="min-width:54px;" uib-dropdown
|
||||
uib-tooltip="{{ 'Projektor' | translate }} {{ item.isListOfSpeakersProjected() }}"
|
||||
tooltip-enable="item.isListofSpeakersProjected() > 0"
|
||||
os-perms="core.can_manage_projector">
|
||||
<button type="button" class="btn btn-default btn-sm"
|
||||
ng-click="item.projectListOfSpeakers(defaultProjectorListOfSpeakersId)"
|
||||
ng-class="{ 'btn-primary': item.isListOfSpeakersProjected() == defaultProjectorListOfSpeakersId }">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
<translate>List of speakers</translate>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-sm slimDropDown"
|
||||
ng-class="{ 'btn-primary': (item.isListOfSpeakersProjected() > 0 && item.isListOfSpeakersProjected() != defaultProjectorListOfSpeakersId) }"
|
||||
ng-if="projectors.length > 1"
|
||||
uib-dropdown-toggle>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li role="menuitem" ng-repeat="projector in projectors">
|
||||
<a href="" ng-click="item.projectListOfSpeakers(projector.id)"
|
||||
ng-class="{ 'projected': (item.isListOfSpeakersProjected() == projector.id) }">
|
||||
<i class="fa fa-video-camera" ng-show="item.isListOfSpeakersProjected() == projector.id"></i>
|
||||
{{ projector.name }}
|
||||
<span ng-if="defaultProjectorListOfSpeakersId == projector.id">(<translate>Standard</translate>)</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
<!-- project -->
|
||||
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
||||
ng-class="{ 'btn-primary': item.isProjected() }"
|
||||
ng-click="item.project()"
|
||||
title="{{ 'Project item' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
{{ item.getContentResource().verboseName | translate }}
|
||||
</a>
|
||||
<projector-button model="item" default-projector-id="defaultProjectorItemId"
|
||||
content="{{ item.getContentResource().verboseName | translate }}">
|
||||
</projector-button>
|
||||
</div>
|
||||
<h1>{{ item.getTitle() }}</h1>
|
||||
<h2>
|
||||
|
@ -38,26 +38,37 @@
|
||||
<translate>Select ...</translate>
|
||||
</button>
|
||||
<!-- project agenda button -->
|
||||
<div os-perms="core.can_manage_projector" class="btn-group" uib-dropdown>
|
||||
<button
|
||||
id="project-agenda-button"
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
<div class="btn-group" uib-dropdown
|
||||
uib-tooltip="{{ 'Projector' | translate }} {{ isAgendaProjected(mainListTree) }}"
|
||||
tooltip-enable="isAgendaProjected(mainListTree) > 0"
|
||||
os-perms="core.can_manage_projector">
|
||||
<button type="button" class="btn btn-default"
|
||||
title="{{ 'Project agenda' | translate }}"
|
||||
ng-click="projectAgenda(tree=true)"
|
||||
ng-class="{ 'btn-primary': isAgendaProjected(tree=true) }">
|
||||
ng-click="projectAgenda(defaultProjectorId_all_items, mainListTree)"
|
||||
ng-class="{ 'btn-primary': isAgendaProjected(mainListTree) > 0 && isAgendaProjected(mainListTree) == defaultProjectorId_all_items}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
<translate>Agenda</translate>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default"
|
||||
ng-if="agendaHasSubitems"
|
||||
ng-class="{ 'btn-primary': isAgendaProjected() }"
|
||||
uib-dropdown-toggle>
|
||||
<button type="button" class="btn btn-default" uib-dropdown-toggle
|
||||
ng-class="{ 'btn-primary': isAgendaProjected(mainListTree) > 0 && isAgendaProjected(mainListTree) != defaultProjectorId_all_items}">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="project-agenda-button">
|
||||
<li role="menuitem"><a href="" ng-click="projectAgenda(tree=true)" translate>All agenda items (Default)</a>
|
||||
<li role="menuitem"><a href="" ng-click="projectAgenda(tree=false)" translate>Only main agenda items</a>
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="split-button">
|
||||
<li role="menuitem" ng-show="agendaHasSubitems">
|
||||
<a href="" ng-click="changeMainListTree(); $event.stopPropagation();">
|
||||
<i class="fa" ng-class="mainListTree ? 'fa-square-o' : 'fa-check-square-o'"></i>
|
||||
<translate>Only main agenda items</translate>
|
||||
</a>
|
||||
</li>
|
||||
<li class="divider" ng-show="agendaHasSubitems"></li>
|
||||
<li role="menuitem" ng-repeat="projector in projectors">
|
||||
<a href="" ng-click="projectAgenda(projectorId=projector.id, tree=mainListTree)"
|
||||
ng-class="{ 'projected': isAgendaProjected(mainListTree) == projector.id }">
|
||||
<i class="fa fa-video-camera" ng-show="isAgendaProjected(mainListTree) == projector.id"></i>
|
||||
{{ projector.name }}
|
||||
<span ng-if="projector.id == defaultProjectorId_all_items">(<translate>Standard</translate>)</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- auto numbering button -->
|
||||
@ -71,7 +82,7 @@
|
||||
<a os-perms="users.can_see_name" class="btn btn-default"
|
||||
ui-sref="agenda.current-list-of-speakers">
|
||||
<i class="fa fa-microphone"></i>
|
||||
<translate>List of speakers</translate>
|
||||
<translate>Current list of speakers</translate>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -150,25 +161,37 @@
|
||||
ng-class="{ 'activeline': item.isProjected(), 'selected': item.selected, 'hiddenrow': item.is_hidden}">
|
||||
<!-- projector column -->
|
||||
<td ng-show="!isDeleteMode" os-perms="core.can_manage_projector">
|
||||
<div class="btn-group" style="width:54px;" uib-dropdown>
|
||||
<button os-perms="core.can_manage_projector"
|
||||
id="project-item"
|
||||
type="button"
|
||||
class="btn btn-default btn-sm"
|
||||
<div class="btn-group" style="min-width:54px;" uib-dropdown
|
||||
uib-tooltip="{{ 'Projector' | translate }} {{ item.isProjected(item.tree) }}"
|
||||
tooltip-enable="item.isProjected(item.tree) > 0">
|
||||
<button class="btn btn-default btn-sm"
|
||||
title="{{ 'Project item' | translate }}"
|
||||
ng-click="item.project()"
|
||||
ng-class="{ 'btn-primary': item.isProjected() }">
|
||||
ng-click="item.project(getProjectionDefault(item), item.tree)"
|
||||
ng-class="{ 'btn-primary': item.isProjected(item.tree) > 0 && item.isProjected(item.tree) == getProjectionDefault(item)}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-sm slimDropDown"
|
||||
ng-if="item.hasSubitems(items)"
|
||||
ng-class="{ 'btn-primary': item.isProjected(list=true) }"
|
||||
ng-class="{ 'btn-primary': item.isProjected(item.tree) > 0 && item.isProjected(item.tree) != getProjectionDefault(item)}"
|
||||
ng-show="item.hasSubitems(items) || projectors.length > 1"
|
||||
uib-dropdown-toggle>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="project-item">
|
||||
<li role="menuitem"><a href="" ng-click="item.project()" translate>Project item (Default)</a>
|
||||
<li role="menuitem"><a href="" ng-click="projectAgenda(tree=true, id=item.id)" translate>Project all sub items</a>
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="split-button">
|
||||
<li role="menuitem" ng-show="item.hasSubitems(items)">
|
||||
<a href="" ng-click="changeItemTree(item); $event.stopPropagation();">
|
||||
<i class="fa" ng-class="item.tree ? 'fa-check-square-o' : 'fa-square-o'"></i>
|
||||
<translate>Include all sub items</translate>
|
||||
</a>
|
||||
</li>
|
||||
<li class="divider" ng-show="item.hasSubitems(items)"></li>
|
||||
<li role="menuitem" ng-repeat="projector in projectors">
|
||||
<a href="" ng-click="item.project(projector.id, item.tree)"
|
||||
ng-class="{ 'projected': item.isProjected(item.tree) == projector.id }">
|
||||
<i class="fa fa-video-camera" ng-show="item.isProjected(item.tree) == projector.id"></i>
|
||||
{{ projector.name }}
|
||||
<span ng-if="projector.id == getProjectionDefault(item)">(<translate>Standard</translate>)</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- delete selection column -->
|
||||
|
@ -0,0 +1,23 @@
|
||||
<div ng-controller="SlideCurrentListOfSpeakersCtrl" class="content scrollcontent">
|
||||
<div class="title">
|
||||
<h1 translate>Current List of speakers</h1>
|
||||
<h2> {{ agendaItem.getTitle() }}
|
||||
<span class="slimlabel label label-danger ng-scope" style=""
|
||||
ng-if="agendaItem.speaker_list_closed" translate>Closed</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- Last speakers -->
|
||||
<p ng-repeat="speaker in lastSpeakers = (agendaItem.speakers | filter: {end_time: '!!', begin_time: '!!'}) |
|
||||
limitTo: config('agenda_show_last_speakers') : (lastSpeakers.length - config('agenda_show_last_speakers'))" class="lastSpeakers">
|
||||
{{ speaker.user.get_full_name() }}
|
||||
<!-- Current speaker -->
|
||||
<p ng-repeat="speaker in currentspeakers = (agendaItem.speakers| filter: {end_time: null, begin_time: '!!'})"
|
||||
class="currentSpeaker">
|
||||
<i class="fa fa-microphone fa-lg"></i> {{ speaker.user.get_full_name() }}
|
||||
<!-- Next speakers -->
|
||||
<ol class="nextSpeakers">
|
||||
<li ng-repeat="speaker in agendaItem.speakers | filter: {begin_time: null} | orderBy:'weight'">
|
||||
{{ speaker.user.get_full_name() }}
|
||||
</ol>
|
||||
</div>
|
@ -267,42 +267,47 @@ angular.module('OpenSlidesApp.assignments', [])
|
||||
return "Election";
|
||||
},
|
||||
// override project function of jsDataModel factory
|
||||
project: function (poll_id) {
|
||||
return $http.post(
|
||||
'/rest/core/projector/1/prune_elements/',
|
||||
[{name: 'assignments/assignment', id: this.id, poll: poll_id}]
|
||||
);
|
||||
project: function (projectorId, pollId) {
|
||||
var isProjectedId = this.isProjected(pollId);
|
||||
if (isProjectedId > 0) {
|
||||
$http.post('/rest/core/projector/' + isProjectedId + '/clear_elements/');
|
||||
}
|
||||
if (isProjectedId != projectorId) {
|
||||
return $http.post(
|
||||
'/rest/core/projector/' + projectorId + '/prune_elements/',
|
||||
[{name: 'assignments/assignment', id: this.id, poll: pollId}]
|
||||
);
|
||||
}
|
||||
},
|
||||
// override isProjected function of jsDataModel factory
|
||||
isProjected: function (poll_id) {
|
||||
// Returns true if there is a projector element with the name
|
||||
// 'assignments/assignment'.
|
||||
var projector = Projector.get(1);
|
||||
var isProjected;
|
||||
if (typeof projector !== 'undefined') {
|
||||
var self = this;
|
||||
var predicate = function (element) {
|
||||
var value;
|
||||
if (typeof poll_id === 'undefined') {
|
||||
// Assignment detail slide without poll
|
||||
value = element.name == 'assignments/assignment' &&
|
||||
typeof element.id !== 'undefined' &&
|
||||
element.id == self.id &&
|
||||
typeof element.poll === 'undefined';
|
||||
} else {
|
||||
// Assignment detail slide with specific poll
|
||||
value = element.name == 'assignments/assignment' &&
|
||||
typeof element.id !== 'undefined' &&
|
||||
element.id == self.id &&
|
||||
typeof element.poll !== 'undefined' &&
|
||||
element.poll == poll_id;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
isProjected = typeof _.findKey(projector.elements, predicate) === 'string';
|
||||
} else {
|
||||
isProjected = false;
|
||||
}
|
||||
// Returns the id of the last projector found with an element
|
||||
// with the name 'assignments/assignment'.
|
||||
var self = this;
|
||||
var predicate = function (element) {
|
||||
var value;
|
||||
if (typeof poll_id === 'undefined') {
|
||||
// Assignment detail slide without poll
|
||||
value = element.name == 'assignments/assignment' &&
|
||||
typeof element.id !== 'undefined' &&
|
||||
element.id == self.id &&
|
||||
typeof element.poll === 'undefined';
|
||||
} else {
|
||||
// Assignment detail slide with specific poll
|
||||
value = element.name == 'assignments/assignment' &&
|
||||
typeof element.id !== 'undefined' &&
|
||||
element.id == self.id &&
|
||||
typeof element.poll !== 'undefined' &&
|
||||
element.poll == poll_id;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
var isProjected = 0;
|
||||
Projector.getAll().forEach(function (projector) {
|
||||
if (typeof _.findKey(projector.elements, predicate) === 'string') {
|
||||
isProjected = projector.id;
|
||||
}
|
||||
});
|
||||
return isProjected;
|
||||
}
|
||||
},
|
||||
|
@ -233,9 +233,19 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
'Tag',
|
||||
'Agenda',
|
||||
'phases',
|
||||
function($scope, ngDialog, AssignmentForm, Assignment, Tag, Agenda, phases) {
|
||||
'Projector',
|
||||
'ProjectionDefault',
|
||||
function($scope, ngDialog, AssignmentForm, Assignment, Tag, Agenda, phases, Projector, ProjectionDefault) {
|
||||
Assignment.bindAll({}, $scope, 'assignments');
|
||||
Tag.bindAll({}, $scope, 'tags');
|
||||
$scope.$watch(function () {
|
||||
return Projector.lastModified();
|
||||
}, function () {
|
||||
var projectiondefault = ProjectionDefault.filter({name: 'assignments'})[0];
|
||||
if (projectiondefault) {
|
||||
$scope.defaultProjectorId = projectiondefault.projector_id;
|
||||
}
|
||||
});
|
||||
$scope.phases = phases;
|
||||
$scope.alert = {};
|
||||
|
||||
@ -339,10 +349,21 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
'User',
|
||||
'assignment',
|
||||
'phases',
|
||||
function($scope, $http, filterFilter, gettext, ngDialog, AssignmentForm, operator, Assignment, User, assignment, phases) {
|
||||
'Projector',
|
||||
'ProjectionDefault',
|
||||
function($scope, $http, filterFilter, gettext, ngDialog, AssignmentForm, operator, Assignment, User,
|
||||
assignment, phases, Projector, ProjectionDefault) {
|
||||
User.bindAll({}, $scope, 'users');
|
||||
Assignment.bindOne(assignment.id, $scope, 'assignment');
|
||||
Assignment.loadRelations(assignment, 'agenda_item');
|
||||
$scope.$watch(function () {
|
||||
return Projector.lastModified();
|
||||
}, function () {
|
||||
var projectiondefault = ProjectionDefault.filter({name: 'assignments'})[0];
|
||||
if (projectiondefault) {
|
||||
$scope.defaultProjectorId = projectiondefault.projector_id;
|
||||
}
|
||||
});
|
||||
$scope.candidateSelectBox = {};
|
||||
$scope.phases = phases;
|
||||
$scope.alert = {};
|
||||
|
@ -16,12 +16,8 @@
|
||||
<translate>List of speakers</translate>
|
||||
</a>
|
||||
<!-- project -->
|
||||
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
||||
ng-class="{ 'btn-primary': assignment.isProjected() }"
|
||||
ng-click="assignment.project()"
|
||||
title="{{ 'Project election' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</a>
|
||||
<projector-button model="assignment", default-projector-id="defaultProjectorId">
|
||||
</projector-button>
|
||||
<!-- edit -->
|
||||
<a os-perms="assignments.can_manage" ng-click="openDialog(assignment)"
|
||||
class="btn btn-default btn-sm"
|
||||
@ -155,13 +151,9 @@
|
||||
3. <translate>Published</translate>
|
||||
</button>
|
||||
<i class="fa fa-arrow-right"></i>
|
||||
<button os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
||||
ng-class="{ 'btn-primary': assignment.isProjected(poll.id) }"
|
||||
ng-click="assignment.project(poll.id)"
|
||||
title="{{ 'Project ballot' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
4. <translate>Project</translate>
|
||||
</button>
|
||||
<projector-button model="assignment" default-projector-id="defaultProjectorId"
|
||||
additional-id="poll.id" content="4. {{ 'Project' | translate }}">
|
||||
</projector-button>
|
||||
<a class="btn btn-danger btn-sm"
|
||||
ng-bootbox-confirm="{{ 'Are you sure you want to delete this ballot?' | translate }}"
|
||||
ng-bootbox-confirm-action="deleteBallot(poll)">
|
||||
|
@ -121,12 +121,8 @@
|
||||
|
||||
<!-- projector -->
|
||||
<td ng-show="!isDeleteMode" os-perms="core.can_manage_projector">
|
||||
<a class="btn btn-default btn-sm"
|
||||
ng-class="{ 'btn-primary': assignment.isProjected() }"
|
||||
ng-click="assignment.project()"
|
||||
title="{{ 'Project election' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</a>
|
||||
<projector-button model="assignment" default-projector-id="defaultProjectorId">
|
||||
</projector-button>
|
||||
|
||||
<!-- delete selection -->
|
||||
<td ng-show="isDeleteMode" os-perms="assignments.can_manage" class="deleteColumn">
|
||||
|
@ -20,7 +20,7 @@ class CoreAppConfig(AppConfig):
|
||||
from openslides.utils.rest_api import router
|
||||
from openslides.utils.search import index_add_instance, index_del_instance
|
||||
from .config_variables import get_config_variables
|
||||
from .signals import delete_django_app_permissions
|
||||
from .signals import delete_django_app_permissions, create_builtin_projection_defaults
|
||||
from .views import (
|
||||
ChatMessageViewSet,
|
||||
ConfigViewSet,
|
||||
@ -35,6 +35,9 @@ class CoreAppConfig(AppConfig):
|
||||
post_permission_creation.connect(
|
||||
delete_django_app_permissions,
|
||||
dispatch_uid='delete_django_app_permissions')
|
||||
post_permission_creation.connect(
|
||||
create_builtin_projection_defaults,
|
||||
dispatch_uid='create_builtin_projection_defaults')
|
||||
|
||||
# Register viewsets.
|
||||
router.register(self.get_model('Projector').get_collection_string(), ProjectorViewSet)
|
||||
|
@ -4,16 +4,14 @@ from django.utils.translation import ugettext as _
|
||||
from .exceptions import ConfigError, ConfigNotFound
|
||||
from .models import ConfigStore
|
||||
|
||||
# remove resolution when changing to multiprojector
|
||||
INPUT_TYPE_MAPPING = {
|
||||
'string': str,
|
||||
'text': str,
|
||||
'integer': int,
|
||||
'boolean': bool,
|
||||
'choice': str,
|
||||
'colorpicker': str,
|
||||
'comments': list,
|
||||
'resolution': dict}
|
||||
'colorpicker': str}
|
||||
|
||||
|
||||
class ConfigHandler:
|
||||
@ -89,16 +87,6 @@ class ConfigHandler:
|
||||
except DjangoValidationError as e:
|
||||
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.'))
|
||||
|
||||
if config_variable.input_type == 'comments':
|
||||
if not isinstance(value, list):
|
||||
raise ConfigError(_('motions_comments has to be a list.'))
|
||||
|
@ -157,11 +157,28 @@ def get_config_variables():
|
||||
weight=185,
|
||||
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,
|
||||
name='projector_blank_color',
|
||||
default_value='#FFFFFF',
|
||||
input_type='colorpicker',
|
||||
label='Color for blanked projector',
|
||||
weight=190,
|
||||
group='Projector')
|
||||
|
||||
yield ConfigVariable(
|
||||
name='projector_broadcast',
|
||||
default_value=0,
|
||||
input_type='integer',
|
||||
label='Projector which is broadcasted',
|
||||
weight=200,
|
||||
group='Projector',
|
||||
hidden=True)
|
||||
|
||||
yield ConfigVariable(
|
||||
name='projector_currentListOfSpeakers_reference',
|
||||
default_value=1,
|
||||
input_type='integer',
|
||||
label='Projector reference for list of speakers',
|
||||
weight=201,
|
||||
group='Projector',
|
||||
hidden=True)
|
||||
|
54
openslides/core/migrations/0006_multiprojector.py
Normal file
54
openslides/core/migrations/0006_multiprojector.py
Normal file
@ -0,0 +1,54 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.9 on 2016-08-29 09:37
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import openslides.utils.models
|
||||
|
||||
|
||||
def name_default_projector(apps, schema_editor):
|
||||
"""
|
||||
Set the name of the default projector to 'Defaultprojector'
|
||||
"""
|
||||
Projector = apps.get_model('core', 'Projector')
|
||||
Projector.objects.filter(pk=1).update(name='Defaultprojector')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0005_auto_20160918_2104'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ProjectionDefault',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=256)),
|
||||
('display_name', models.CharField(max_length=256)),
|
||||
],
|
||||
options={
|
||||
'default_permissions': (),
|
||||
},
|
||||
bases=(openslides.utils.models.RESTModelMixin, models.Model),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='projector',
|
||||
name='name',
|
||||
field=models.CharField(blank=True, max_length=255, unique=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='projector',
|
||||
name='blank',
|
||||
field=models.BooleanField(blank=False, default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='projectiondefault',
|
||||
name='projector',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='projectiondefaults', to='core.Projector'),
|
||||
),
|
||||
migrations.RunPython(name_default_projector),
|
||||
]
|
@ -63,11 +63,19 @@ class Projector(RESTModelMixin, models.Model):
|
||||
|
||||
scroll = models.IntegerField(default=0)
|
||||
|
||||
# currently unused, but important for the multiprojector.
|
||||
width = models.PositiveIntegerField(default=1024)
|
||||
|
||||
height = models.PositiveIntegerField(default=768)
|
||||
|
||||
name = models.CharField(
|
||||
max_length=255,
|
||||
unique=True,
|
||||
blank=True)
|
||||
|
||||
blank = models.BooleanField(
|
||||
blank=False,
|
||||
default=False)
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Contains general permissions that can not be placed in a specific app.
|
||||
@ -169,6 +177,33 @@ class Projector(RESTModelMixin, models.Model):
|
||||
return result
|
||||
|
||||
|
||||
class ProjectionDefault(RESTModelMixin, models.Model):
|
||||
"""
|
||||
Model for the ProjectionDefaults like Motion, Agenda, List of speakers,...
|
||||
The name is the technical name like 'topics', 'motions'. For apps the name should
|
||||
be the app name to get keep the ProjectionDefault for apps generic. But it is
|
||||
possible to give some special name like 'list_of_speakers'.
|
||||
The display_name is the shown name on the front end for the user.
|
||||
"""
|
||||
name = models.CharField(max_length=256)
|
||||
|
||||
display_name = models.CharField(max_length=256)
|
||||
|
||||
projector = models.ForeignKey(
|
||||
'Projector',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='projectiondefaults')
|
||||
|
||||
def get_root_rest_element(self):
|
||||
return self.projector
|
||||
|
||||
class Meta:
|
||||
default_permissions = ()
|
||||
|
||||
def __str__(self):
|
||||
return self.display_name
|
||||
|
||||
|
||||
class Tag(RESTModelMixin, models.Model):
|
||||
"""
|
||||
Model for tags. This tags can be used for other models like agenda items,
|
||||
|
@ -1,3 +1,5 @@
|
||||
import uuid
|
||||
|
||||
from django.utils.timezone import now
|
||||
|
||||
from ..utils.projector import ProjectorElement
|
||||
@ -20,7 +22,7 @@ class Countdown(ProjectorElement):
|
||||
To start the countdown write into the config field:
|
||||
|
||||
{
|
||||
"status": "running",
|
||||
"running": True,
|
||||
"countdown_time": <timestamp>,
|
||||
}
|
||||
|
||||
@ -30,10 +32,10 @@ class Countdown(ProjectorElement):
|
||||
|
||||
To stop the countdown set the countdown time to the current value of the
|
||||
countdown (countdown_time = countdown_time - now + serverTimeOffset)
|
||||
and set status to "stop".
|
||||
and set running to False.
|
||||
|
||||
To reset the countdown (it is not a reset in a functional way) just
|
||||
change the countdown time. The status value remains "stop".
|
||||
change the countdown time. The running value remains False.
|
||||
|
||||
Do not forget to send values for additional keywords like "stable" if
|
||||
you do not want to use the default.
|
||||
@ -63,50 +65,76 @@ class Countdown(ProjectorElement):
|
||||
"""
|
||||
if not isinstance(config_data.get('countdown_time'), (int, float)):
|
||||
raise ProjectorException('Invalid countdown time. Use integer or float.')
|
||||
if config_data.get('status') not in ('running', 'stop'):
|
||||
raise ProjectorException("Invalid status. Use 'running' or 'stop'.")
|
||||
if not isinstance(config_data.get('running'), bool):
|
||||
raise ProjectorException("Invalid running status. Has to be a boolean.")
|
||||
if config_data.get('default') is not None and not isinstance(config_data.get('default'), int):
|
||||
raise ProjectorException('Invalid default value. Use integer.')
|
||||
|
||||
@classmethod
|
||||
def control(cls, action, projector_id=1, index=0):
|
||||
"""
|
||||
Starts, stops or resets the countdown with the given index on the
|
||||
given projector.
|
||||
|
||||
Action must be 'start', 'stop' or 'reset'.
|
||||
"""
|
||||
def control(cls, action):
|
||||
if action not in ('start', 'stop', 'reset'):
|
||||
raise ValueError("Action must be 'start', 'stop' or 'reset', not {}.".format(action))
|
||||
raise ValueError("Action must be 'start', 'stop' or 'reset', not {}.".format(action))
|
||||
|
||||
projector_instance = Projector.objects.get(pk=projector_id)
|
||||
projector_config = {}
|
||||
found = False
|
||||
for key, value in projector_instance.config.items():
|
||||
if value['name'] == cls.name:
|
||||
if index == 0:
|
||||
try:
|
||||
cls.validate_config(value)
|
||||
except ProjectorException:
|
||||
# Do not proceed if the specific procjector config data is invalid.
|
||||
# The variable found remains False.
|
||||
break
|
||||
found = True
|
||||
if action == 'start' and value['status'] == 'stop':
|
||||
value['status'] = 'running'
|
||||
value['countdown_time'] = now().timestamp() + value['countdown_time']
|
||||
elif action == 'stop' and value['status'] == 'running':
|
||||
value['status'] = 'stop'
|
||||
value['countdown_time'] = value['countdown_time'] - now().timestamp()
|
||||
elif action == 'reset':
|
||||
value['status'] = 'stop'
|
||||
value['countdown_time'] = value.get('default', config['projector_default_countdown'])
|
||||
else:
|
||||
index += -1
|
||||
projector_config[key] = value
|
||||
if found:
|
||||
projector_instance.config = projector_config
|
||||
projector_instance.save()
|
||||
# Use the countdown with the lowest index
|
||||
projectors = Projector.objects.all()
|
||||
lowest_index = None
|
||||
if projectors[0]:
|
||||
for key, value in projectors[0].config.items():
|
||||
if value['name'] == cls.name:
|
||||
if lowest_index is None or value['index'] < lowest_index:
|
||||
lowest_index = value['index']
|
||||
|
||||
if lowest_index is None:
|
||||
# create a countdown
|
||||
for projector in projectors:
|
||||
projector_config = {}
|
||||
for key, value in projector.config.items():
|
||||
projector_config[key] = value
|
||||
# new countdown
|
||||
countdown = {
|
||||
'name': 'core/countdown',
|
||||
'stable': True,
|
||||
'index': 1,
|
||||
'default_time': config['projector_default_countdown'],
|
||||
'visible': False,
|
||||
'selected': True,
|
||||
}
|
||||
if action == 'start':
|
||||
countdown['running'] = True
|
||||
countdown['countdown_time'] = now().timestamp() + countdown['default_time']
|
||||
elif action == 'reset' or action == 'stop':
|
||||
countdown['running'] = False
|
||||
countdown['countdown_time'] = countdown['default_time']
|
||||
projector_config[uuid.uuid4().hex] = countdown
|
||||
projector.config = projector_config
|
||||
projector.save()
|
||||
else:
|
||||
# search for the countdown and modify it.
|
||||
for projector in projectors:
|
||||
projector_config = {}
|
||||
found = False
|
||||
for key, value in projector.config.items():
|
||||
if value['name'] == cls.name and value['index'] == lowest_index:
|
||||
try:
|
||||
cls.validate_config(value)
|
||||
except ProjectorException:
|
||||
# Do not proceed if the specific procjector config data is invalid.
|
||||
# The variable found remains False.
|
||||
break
|
||||
found = True
|
||||
if action == 'start':
|
||||
value['running'] = True
|
||||
value['countdown_time'] = now().timestamp() + value['default_time']
|
||||
elif action == 'stop' and value['running']:
|
||||
value['running'] = False
|
||||
value['countdown_time'] = value['countdown_time'] - now().timestamp()
|
||||
elif action == 'reset':
|
||||
value['running'] = False
|
||||
value['countdown_time'] = value['default_time']
|
||||
projector_config[key] = value
|
||||
if found:
|
||||
projector.config = projector_config
|
||||
projector.save()
|
||||
|
||||
|
||||
class Message(ProjectorElement):
|
||||
|
@ -1,6 +1,6 @@
|
||||
from openslides.utils.rest_api import Field, ModelSerializer, ValidationError
|
||||
|
||||
from .models import ChatMessage, Projector, Tag
|
||||
from .models import ChatMessage, ProjectionDefault, Projector, Tag
|
||||
|
||||
|
||||
class JSONSerializerField(Field):
|
||||
@ -22,15 +22,26 @@ class JSONSerializerField(Field):
|
||||
return data
|
||||
|
||||
|
||||
class ProjectionDefaultSerializer(ModelSerializer):
|
||||
"""
|
||||
Serializer for core.models.ProjectionDefault objects.
|
||||
"""
|
||||
class Meta:
|
||||
model = ProjectionDefault
|
||||
fields = ('id', 'name', 'display_name', 'projector', )
|
||||
|
||||
|
||||
class ProjectorSerializer(ModelSerializer):
|
||||
"""
|
||||
Serializer for core.models.Projector objects.
|
||||
"""
|
||||
config = JSONSerializerField(write_only=True)
|
||||
projectiondefaults = ProjectionDefaultSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Projector
|
||||
fields = ('id', 'config', 'elements', 'scale', 'scroll', 'width', 'height',)
|
||||
fields = ('id', 'config', 'elements', 'scale', 'scroll', 'name', 'blank', 'width', 'height', 'projectiondefaults')
|
||||
read_only_fields = ('scale', 'scroll', 'blank', 'width', 'height', 'projectiondefaults')
|
||||
|
||||
|
||||
class TagSerializer(ModelSerializer):
|
||||
|
@ -3,6 +3,8 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import Q
|
||||
from django.dispatch import Signal
|
||||
|
||||
from .models import ProjectionDefault, Projector
|
||||
|
||||
# This signal is sent when the migrate command is done. That means it is sent
|
||||
# after post_migrate sending and creating all Permission objects. Don't use it
|
||||
# for other things than dealing with Permission objects.
|
||||
@ -20,3 +22,56 @@ def delete_django_app_permissions(sender, **kwargs):
|
||||
Q(app_label='sessions'))
|
||||
for permission in Permission.objects.filter(content_type__in=contenttypes):
|
||||
permission.delete()
|
||||
|
||||
|
||||
def create_builtin_projection_defaults(**kwargs):
|
||||
"""
|
||||
Creates the builtin defaults:
|
||||
- agenda_all_items, agenda_item
|
||||
- assignments
|
||||
- mediafiles
|
||||
- motion
|
||||
- users
|
||||
- list_of_speakers
|
||||
- current_list_of_speakers
|
||||
"""
|
||||
|
||||
# Check whether ProjectionDefaults exists
|
||||
if ProjectionDefault.objects.all().exists():
|
||||
# Do completely nothing if the defaults are already in the database.
|
||||
return
|
||||
|
||||
default_projector = Projector.objects.get(pk=1)
|
||||
|
||||
ProjectionDefault.objects.create(
|
||||
name='agenda_all_items',
|
||||
display_name='Agenda',
|
||||
projector=default_projector)
|
||||
ProjectionDefault.objects.create(
|
||||
name='topics',
|
||||
display_name='Topics',
|
||||
projector=default_projector)
|
||||
ProjectionDefault.objects.create(
|
||||
name='list_of_speakers',
|
||||
display_name='List of speakers',
|
||||
projector=default_projector)
|
||||
ProjectionDefault.objects.create(
|
||||
name='current_list_of_speakers',
|
||||
display_name='Current list of speakers',
|
||||
projector=default_projector)
|
||||
ProjectionDefault.objects.create(
|
||||
name='motions',
|
||||
display_name='Motions',
|
||||
projector=default_projector)
|
||||
ProjectionDefault.objects.create(
|
||||
name='assignments',
|
||||
display_name='Elections',
|
||||
projector=default_projector)
|
||||
ProjectionDefault.objects.create(
|
||||
name='users',
|
||||
display_name='Participants',
|
||||
projector=default_projector)
|
||||
ProjectionDefault.objects.create(
|
||||
name='mediafiles',
|
||||
display_name='Files',
|
||||
projector=default_projector)
|
||||
|
@ -270,6 +270,12 @@ img {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.col1 .header .submenu > div {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.col1 .meta .title {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
@ -331,15 +337,6 @@ img {
|
||||
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 */
|
||||
.motion-save-toolbar {
|
||||
position: fixed;
|
||||
@ -586,7 +583,6 @@ img {
|
||||
|
||||
#content .col2 .section a:hover {
|
||||
text-decoration: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
#content .toggle-icon {
|
||||
@ -652,20 +648,20 @@ img {
|
||||
color: #CC0000;
|
||||
}
|
||||
|
||||
.col2 .notNull {
|
||||
.notNull {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* iframe for live view */
|
||||
.col2 #iframe {
|
||||
.iframe {
|
||||
-moz-transform-origin: 0 0;
|
||||
-webkit-transform-origin: 0 0;
|
||||
-o-transform-origin: 0 0;
|
||||
transform-origin: 0 0 0;
|
||||
}
|
||||
|
||||
.col2 #iframewrapper {
|
||||
.iframewrapper {
|
||||
width: 256px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
@ -673,7 +669,7 @@ img {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.col2 #iframeoverlay {
|
||||
.iframeoverlay {
|
||||
width: 256px;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
@ -782,6 +778,77 @@ img {
|
||||
margin-bottom: 5px
|
||||
}
|
||||
|
||||
/** Pojector sidebar **/
|
||||
.col2 .projectorSelector {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.col2 .projectorSelector > div > div {
|
||||
width: 65%;
|
||||
padding-right: 5px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.col2 .projectorSelector > div > div > button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.col2 .projectorSelector .manageBtn {
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
.col2 .projectorSelector .btn-group {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: table;
|
||||
}
|
||||
|
||||
.col2 .projectorSelector > div {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/** manage-projectors **/
|
||||
|
||||
.projectorContainer > div {
|
||||
display: inline-table;
|
||||
width: 256px;
|
||||
margin: 10px 20px 35px 10px;
|
||||
}
|
||||
|
||||
.projectorContainer > div > div {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.projectorContainer .middle {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.projectorContainer .middle > div {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: table;
|
||||
}
|
||||
|
||||
.projectorContainer .dropdown {
|
||||
width: 65%;
|
||||
padding-right: 5px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.projectorContainer .dropdown > button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.projectorContainer .btn-danger {
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
.projectorContainer .resolution {
|
||||
display: inline-block;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
/** Footer **/
|
||||
#footer {
|
||||
float: left;
|
||||
@ -988,21 +1055,6 @@ img {
|
||||
}
|
||||
|
||||
/* List of speakers view */
|
||||
.fullscreendiv {
|
||||
position: absolute;
|
||||
font-size:120%;
|
||||
top:0;
|
||||
bottom:0;
|
||||
right:0;
|
||||
left:0;
|
||||
padding: 50px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.fullscreendiv-title {
|
||||
border-bottom: 5px solid #d3d3d3;
|
||||
margin-bottom: 40px;
|
||||
|
@ -3,7 +3,11 @@
|
||||
*
|
||||
*/
|
||||
|
||||
body{
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 20px !important;
|
||||
line-height: 24px !important;
|
||||
overflow: hidden;
|
||||
@ -34,7 +38,7 @@ body{
|
||||
transform-origin: 0 0 0;
|
||||
}
|
||||
|
||||
.pContainer #iframewrapper {
|
||||
.pContainer #iframewrapper, .pContainer .error {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin-left: auto;
|
||||
@ -49,6 +53,12 @@ body{
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.pContainer .error > p {
|
||||
color: #f00;
|
||||
font-size: 150%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/*** HEADER ***/
|
||||
#header {
|
||||
box-shadow: 0 0 7px rgba(0,0,0,0.6);
|
||||
@ -278,6 +288,10 @@ hr {
|
||||
line-height: normal !important;
|
||||
z-index: 301;
|
||||
}
|
||||
.identify {
|
||||
background-color: #D9F8C4;
|
||||
z-index: 400;
|
||||
}
|
||||
|
||||
/*** PDF presentation ***/
|
||||
.rotate0 {
|
||||
@ -379,7 +393,6 @@ tr.elected td {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*** Line numbers ***/
|
||||
.motion-text .highlight {
|
||||
background-color: #ff0;
|
||||
|
@ -33,11 +33,20 @@ angular.module('OpenSlidesApp.core', [
|
||||
}
|
||||
])
|
||||
|
||||
.factory('ProjectorID', [
|
||||
function () {
|
||||
return function () {
|
||||
return /projector\/(\d+)\//.exec(location.pathname)[1];
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.factory('autoupdate', [
|
||||
'DS',
|
||||
'$rootScope',
|
||||
'REALM',
|
||||
function (DS, $rootScope, REALM) {
|
||||
'ProjectorID',
|
||||
function (DS, $rootScope, REALM, ProjectorID) {
|
||||
var socket = null;
|
||||
var recInterval = null;
|
||||
$rootScope.connected = false;
|
||||
@ -46,8 +55,7 @@ angular.module('OpenSlidesApp.core', [
|
||||
if (REALM == 'site') {
|
||||
websocketPath = '/ws/site/';
|
||||
} else if (REALM == 'projector') {
|
||||
// TODO: At the moment there is only one projector. Find out which one is requested
|
||||
websocketPath = '/ws/projector/1/';
|
||||
websocketPath = '/ws/projector/' + ProjectorID() + '/';
|
||||
} else {
|
||||
console.error('The constant REALM is not set properly.');
|
||||
}
|
||||
@ -246,7 +254,7 @@ angular.module('OpenSlidesApp.core', [
|
||||
return function () {
|
||||
Config.findAll();
|
||||
|
||||
// Loads all projector data
|
||||
// Loads all projector data and the projectiondefaults
|
||||
Projector.findAll();
|
||||
|
||||
// Loads all chat messages data and their user_ids
|
||||
@ -305,34 +313,91 @@ angular.module('OpenSlidesApp.core', [
|
||||
}
|
||||
])
|
||||
|
||||
// This places a Projectorbutton in the document. Example:
|
||||
// <projector-button model="motion" default-projector.id="defPrId" additional-id="2"
|
||||
// content="{{ 'project' | translate }}"></projector-button>
|
||||
//
|
||||
// This button references to model (in this case 'motion'). Also a defaultProjectionId has to
|
||||
// be given. In the Exable its a scope variable. The next two parameters are additional:
|
||||
// - additional-id: Then the model.project and model.isProjected will be called whith this
|
||||
// argument (ex.: model.project(2))
|
||||
// - content: A not trusted text placed behind the projector symbol.
|
||||
.directive('projectorButton', [
|
||||
'Projector',
|
||||
function (Projector) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'static/templates/projector-button.html',
|
||||
link: function (scope, element, attributes) {
|
||||
if (!attributes.model) {
|
||||
throw 'A model has to be given!';
|
||||
} else if (!attributes.defaultProjectorId) {
|
||||
throw 'A default-projector-id has to be given!';
|
||||
}
|
||||
|
||||
Projector.bindAll({}, scope, 'projectors');
|
||||
|
||||
scope.$watch(attributes.model, function (model) {
|
||||
scope.model = model;
|
||||
});
|
||||
|
||||
scope.$watch(attributes.defaultProjectorId, function (defaultProjectorId) {
|
||||
scope.defaultProjectorId = defaultProjectorId;
|
||||
});
|
||||
|
||||
if (attributes.additionalId) {
|
||||
scope.$watch(attributes.additionalId, function (id) {
|
||||
scope.additionalId = id;
|
||||
});
|
||||
}
|
||||
|
||||
if (attributes.content) {
|
||||
attributes.$observe('content', function (content) {
|
||||
scope.content = content;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.factory('jsDataModel', [
|
||||
'$http',
|
||||
'Projector',
|
||||
function($http, Projector) {
|
||||
var BaseModel = function() {};
|
||||
BaseModel.prototype.project = function() {
|
||||
return $http.post(
|
||||
'/rest/core/projector/1/prune_elements/',
|
||||
[{name: this.getResourceName(), id: this.id}]
|
||||
);
|
||||
BaseModel.prototype.project = function(projectorId) {
|
||||
// if this object is already projected on projectorId, delete this element from this projector
|
||||
var isProjectedId = this.isProjected();
|
||||
if (isProjectedId > 0) {
|
||||
$http.post('/rest/core/projector/' + isProjectedId + '/prune_elements/');
|
||||
}
|
||||
// if it was the same projector before, just delete it but not show again
|
||||
if (isProjectedId != projectorId) {
|
||||
return $http.post(
|
||||
'/rest/core/projector/' + projectorId + '/prune_elements/',
|
||||
[{name: this.getResourceName(), id: this.id}]
|
||||
);
|
||||
}
|
||||
};
|
||||
BaseModel.prototype.isProjected = function() {
|
||||
// Returns true if there is a projector element with the same
|
||||
// name and the same id.
|
||||
var projector = Projector.get(1);
|
||||
var isProjected;
|
||||
if (typeof projector !== 'undefined') {
|
||||
var self = this;
|
||||
var predicate = function (element) {
|
||||
return element.name == self.getResourceName() &&
|
||||
typeof element.id !== 'undefined' &&
|
||||
element.id == self.id;
|
||||
};
|
||||
isProjected = typeof _.findKey(projector.elements, predicate) === 'string';
|
||||
} else {
|
||||
isProjected = false;
|
||||
}
|
||||
return isProjected;
|
||||
// Returns the projector id if there is a projector element
|
||||
// with the same name and the same id. Else returns 0.
|
||||
// Attention: if this element is projected multiple times, only the
|
||||
// id of the last projector is returned.
|
||||
var self = this;
|
||||
var predicate = function (element) {
|
||||
return element.name == self.getResourceName() &&
|
||||
typeof element.id !== 'undefined' &&
|
||||
element.id == self.id;
|
||||
};
|
||||
var isProjectedId = 0;
|
||||
Projector.getAll().forEach(function (projector) {
|
||||
if (typeof _.findKey(projector.elements, predicate) === 'string') {
|
||||
isProjectedId = projector.id;
|
||||
}
|
||||
});
|
||||
return isProjectedId;
|
||||
};
|
||||
return BaseModel;
|
||||
}
|
||||
@ -396,10 +461,74 @@ angular.module('OpenSlidesApp.core', [
|
||||
*/
|
||||
.factory('Projector', [
|
||||
'DS',
|
||||
function(DS) {
|
||||
'$http',
|
||||
'Config',
|
||||
function(DS, $http, Config) {
|
||||
return DS.defineResource({
|
||||
name: 'core/projector',
|
||||
onConflict: 'replace',
|
||||
relations: {
|
||||
hasMany: {
|
||||
'core/projectiondefault': {
|
||||
localField: 'projectiondefaults',
|
||||
foreignKey: 'projector_id',
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
controlProjector: function(action, direction) {
|
||||
$http.post('/rest/core/projector/' + this.id + '/control_view/',
|
||||
{"action": action, "direction": direction}
|
||||
);
|
||||
},
|
||||
getStateForCurrentSlide: function () {
|
||||
var return_dict;
|
||||
$.each(this.elements, function(key, value) {
|
||||
if (value.name == 'agenda/list-of-speakers') {
|
||||
return_dict = {
|
||||
'state': 'agenda.item.detail',
|
||||
'param': {id: value.id}
|
||||
};
|
||||
} else if (
|
||||
value.name != 'agenda/item-list' &&
|
||||
value.name != 'core/clock' &&
|
||||
value.name != 'core/countdown' &&
|
||||
value.name != 'core/message' ) {
|
||||
return_dict = {
|
||||
'state': value.name.replace('/', '.')+'.detail.update',
|
||||
'param': {id: value.id}
|
||||
};
|
||||
}
|
||||
});
|
||||
return return_dict;
|
||||
},
|
||||
toggleBlank: function () {
|
||||
$http.post('/rest/core/projector/' + this.id + '/control_blank/',
|
||||
!this.blank
|
||||
);
|
||||
},
|
||||
toggleBroadcast: function () {
|
||||
$http.post('/rest/core/projector/' + this.id + '/broadcast/');
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
])
|
||||
|
||||
/* Model for all projection defaults */
|
||||
.factory('ProjectionDefault', [
|
||||
'DS',
|
||||
function(DS) {
|
||||
return DS.defineResource({
|
||||
name: 'core/projectiondefault',
|
||||
relations: {
|
||||
belongsTo: {
|
||||
'core/projector': {
|
||||
localField: 'projector',
|
||||
localKey: 'projector_id',
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
])
|
||||
@ -522,8 +651,9 @@ angular.module('OpenSlidesApp.core', [
|
||||
'ChatMessage',
|
||||
'Config',
|
||||
'Projector',
|
||||
'ProjectionDefault',
|
||||
'Tag',
|
||||
function (ChatMessage, Config, Projector, Tag) {}
|
||||
function (ChatMessage, Config, Projector, ProjectionDefault, Tag) {}
|
||||
]);
|
||||
|
||||
}());
|
||||
|
@ -59,25 +59,31 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
||||
// Projector Container Controller
|
||||
.controller('ProjectorContainerCtrl', [
|
||||
'$scope',
|
||||
'Config',
|
||||
'$location',
|
||||
'loadGlobalData',
|
||||
function($scope, Config, loadGlobalData) {
|
||||
'Projector',
|
||||
'ProjectorID',
|
||||
function($scope, $location, loadGlobalData, Projector, ProjectorID) {
|
||||
loadGlobalData();
|
||||
// watch for changes in Config
|
||||
var last_conf;
|
||||
|
||||
$scope.projector_id = ProjectorID();
|
||||
$scope.error = '';
|
||||
|
||||
// watch for changes in Projector
|
||||
$scope.$watch(function () {
|
||||
return Config.lastModified();
|
||||
return Projector.lastModified($scope.projector_id);
|
||||
}, function () {
|
||||
// With multiprojector, get the resolution from Prjector.get(pk).{width; height}
|
||||
if (typeof $scope.config === 'function') {
|
||||
var conf = $scope.config('projector_resolution');
|
||||
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();
|
||||
Projector.find($scope.projector_id).then(function (projector) {
|
||||
$scope.projectorWidth = projector.width;
|
||||
$scope.projectorHeight = projector.height;
|
||||
$scope.recalculateIframe();
|
||||
}, function (error) {
|
||||
if (error.status == 404) {
|
||||
$scope.error = 'Projector not found.';
|
||||
} else if (error.status == 403) {
|
||||
$scope.error = 'You have to login to see the projector.';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// recalculate the actual Iframesize and scale
|
||||
@ -114,27 +120,84 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
||||
|
||||
.controller('ProjectorCtrl', [
|
||||
'$scope',
|
||||
'$location',
|
||||
'Projector',
|
||||
'slides',
|
||||
function($scope, Projector, slides) {
|
||||
'Config',
|
||||
'ProjectorID',
|
||||
function($scope, $location, Projector, slides, Config, ProjectorID) {
|
||||
var projector_id = ProjectorID();
|
||||
|
||||
$scope.broadcast = 0;
|
||||
|
||||
var setElements = function (projector) {
|
||||
$scope.elements = [];
|
||||
_.forEach(slides.getElements(projector), function(element) {
|
||||
if (!element.error) {
|
||||
$scope.elements.push(element);
|
||||
} else {
|
||||
console.error("Error for slide " + element.name + ": " + element.error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$watch(function () {
|
||||
// TODO: Use the current projector. At the moment there is only one.
|
||||
return Projector.lastModified(1);
|
||||
return Projector.lastModified(projector_id);
|
||||
}, function () {
|
||||
// TODO: Use the current projector. At the moment there is only one
|
||||
var projector = Projector.get(1);
|
||||
if (projector) {
|
||||
$scope.projector = Projector.get(projector_id);
|
||||
if ($scope.projector) {
|
||||
if ($scope.broadcast === 0) {
|
||||
setElements($scope.projector);
|
||||
$scope.blank = $scope.projector.blank;
|
||||
}
|
||||
} else {
|
||||
// Blank projector on error
|
||||
$scope.elements = [];
|
||||
_.forEach(slides.getElements(projector), function(element) {
|
||||
if (!element.error) {
|
||||
$scope.elements.push(element);
|
||||
} else {
|
||||
console.error("Error for slide " + element.name + ": " + element.error);
|
||||
$scope.projector = {
|
||||
scroll: 0,
|
||||
scale: 0,
|
||||
blank: true
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch(function () {
|
||||
return Config.lastModified('projector_broadcast');
|
||||
}, function () {
|
||||
Config.findAll().then(function () {
|
||||
var bc = Config.get('projector_broadcast').value;
|
||||
if ($scope.broadcast != bc) {
|
||||
$scope.broadcast = bc;
|
||||
if ($scope.broadcastDeregister) {
|
||||
// revert to original $scope.projector
|
||||
$scope.broadcastDeregister();
|
||||
$scope.broadcastDeregister = null;
|
||||
setElements($scope.projector);
|
||||
$scope.blank = $scope.projector.blank;
|
||||
}
|
||||
});
|
||||
// TODO: Use the current projector. At the moment there is only one
|
||||
$scope.scroll = -80 * Projector.get(1).scroll;
|
||||
$scope.scale = 100 + 20 * Projector.get(1).scale;
|
||||
}
|
||||
|
||||
if ($scope.broadcast > 0) {
|
||||
// get elements and blank from broadcast projector
|
||||
$scope.broadcastDeregister = $scope.$watch(function () {
|
||||
return Projector.lastModified($scope.broadcast);
|
||||
}, function () {
|
||||
if ($scope.broadcast > 0) {
|
||||
// var broadcast_projector = Projector.get($scope.broadcast);
|
||||
Projector.find($scope.broadcast).then(function (broadcast_projector) {
|
||||
setElements(broadcast_projector);
|
||||
$scope.blank = broadcast_projector.blank;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
if ($scope.broadcastDeregister) {
|
||||
$scope.broadcastDeregister();
|
||||
$scope.broadcastDeregister = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -158,13 +221,14 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
||||
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||
// class.
|
||||
$scope.seconds = Math.floor( $scope.element.countdown_time - Date.now() / 1000 + $scope.serverOffset );
|
||||
$scope.status = $scope.element.status;
|
||||
$scope.running = $scope.element.running;
|
||||
$scope.visible = $scope.element.visible;
|
||||
$scope.selected = $scope.element.selected;
|
||||
$scope.index = $scope.element.index;
|
||||
$scope.description = $scope.element.description;
|
||||
// start interval timer if countdown status is running
|
||||
var interval;
|
||||
if ($scope.status == "running") {
|
||||
if ($scope.running) {
|
||||
interval = $interval( function() {
|
||||
$scope.seconds = Math.floor( $scope.element.countdown_time - Date.now() / 1000 + $scope.serverOffset );
|
||||
}, 1000);
|
||||
@ -186,6 +250,8 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
||||
// class.
|
||||
$scope.message = $scope.element.message;
|
||||
$scope.visible = $scope.element.visible;
|
||||
$scope.selected = $scope.element.selected;
|
||||
$scope.type = $scope.element.type;
|
||||
}
|
||||
]);
|
||||
|
||||
|
@ -717,7 +717,7 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
templateUrl: 'static/templates/home.html'
|
||||
})
|
||||
.state('projector', {
|
||||
url: '/projector',
|
||||
url: '/projector/{id:int}',
|
||||
templateUrl: 'static/templates/projector-container.html',
|
||||
data: {extern: true},
|
||||
onEnter: function($window) {
|
||||
@ -725,13 +725,18 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
}
|
||||
})
|
||||
.state('real-projector', {
|
||||
url: '/real-projector',
|
||||
url: '/real-projector/{id:int}',
|
||||
templateUrl: 'static/templates/projector.html',
|
||||
data: {extern: true},
|
||||
onEnter: function($window) {
|
||||
$window.location.href = this.url;
|
||||
}
|
||||
})
|
||||
.state('manage-projectors', {
|
||||
url: '/manage-projectors',
|
||||
templateUrl: 'static/templates/core/manage-projectors.html',
|
||||
controller: 'ManageProjectorsCtrl'
|
||||
})
|
||||
.state('core', {
|
||||
url: '/core',
|
||||
abstract: true,
|
||||
@ -901,7 +906,6 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
'Config',
|
||||
'gettextCatalog',
|
||||
function($parse, Config, gettextCatalog) {
|
||||
// remove resolution when changing to multiprojector
|
||||
function getHtmlType(type) {
|
||||
return {
|
||||
string: 'text',
|
||||
@ -911,7 +915,6 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
choice: 'choice',
|
||||
colorpicker: 'colorpicker',
|
||||
comments: 'comments',
|
||||
resolution: 'resolution',
|
||||
}[type];
|
||||
}
|
||||
|
||||
@ -1187,209 +1190,439 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
'$http',
|
||||
'$interval',
|
||||
'$state',
|
||||
'$q',
|
||||
'Config',
|
||||
'Projector',
|
||||
function($scope, $http, $interval, $state, Config, Projector) {
|
||||
// bind projector elements to the scope, update after projector changed
|
||||
$scope.$watch(function () {
|
||||
return Projector.lastModified(1);
|
||||
}, function () {
|
||||
// stop ALL interval timer
|
||||
for (var i=0; i<$scope.countdowns.length; i++) {
|
||||
if ( $scope.countdowns[i].interval ) {
|
||||
$interval.cancel($scope.countdowns[i].interval);
|
||||
function($scope, $http, $interval, $state, $q, Config, Projector) {
|
||||
$scope.countdowns = [];
|
||||
$scope.highestCountdownIndex = 0;
|
||||
$scope.messages = [];
|
||||
$scope.highestMessageIndex = 0;
|
||||
|
||||
var cancelIntervalTimers = function () {
|
||||
$scope.countdowns.forEach(function (countdown) {
|
||||
$interval.cancel(countdown.interval);
|
||||
});
|
||||
};
|
||||
|
||||
// Get all message and countdown data from the defaultprojector (id=1)
|
||||
var rebuildAllElements = function () {
|
||||
$scope.countdowns = [];
|
||||
$scope.messages = [];
|
||||
|
||||
_.forEach(Projector.get(1).elements, function (element, uuid) {
|
||||
if (element.name == 'core/countdown') {
|
||||
$scope.countdowns.push(element);
|
||||
|
||||
if (element.running) {
|
||||
// calculate remaining seconds directly because interval starts with 1 second delay
|
||||
$scope.calculateCountdownTime(element);
|
||||
// start interval timer (every second)
|
||||
element.interval = $interval(function () { $scope.calculateCountdownTime(element); }, 1000);
|
||||
} else {
|
||||
element.seconds = element.countdown_time;
|
||||
}
|
||||
|
||||
if (element.index > $scope.highestCountdownIndex) {
|
||||
$scope.highestCountdownIndex = element.index;
|
||||
}
|
||||
} else if (element.name == 'core/message') {
|
||||
$scope.messages.push(element);
|
||||
|
||||
if (element.index > $scope.highestMessageIndex) {
|
||||
$scope.highestMessageIndex = element.index;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$watch(function () {
|
||||
return Projector.lastModified();
|
||||
}, function () {
|
||||
$scope.projectors = Projector.getAll();
|
||||
if (!$scope.active_projector) {
|
||||
$scope.changeProjector($scope.projectors[0]);
|
||||
}
|
||||
// rebuild all variables after projector update
|
||||
$scope.rebuildAllElements();
|
||||
|
||||
// stop ALL interval timer
|
||||
cancelIntervalTimers();
|
||||
|
||||
rebuildAllElements();
|
||||
});
|
||||
$scope.$on('$destroy', function() {
|
||||
// Cancel all intervals if the controller is destroyed
|
||||
for (var i=0; i<$scope.countdowns.length; i++) {
|
||||
if ( $scope.countdowns[i].interval ) {
|
||||
$interval.cancel($scope.countdowns[i].interval);
|
||||
}
|
||||
}
|
||||
|
||||
cancelIntervalTimers();
|
||||
});
|
||||
|
||||
// watch for changes in Config
|
||||
var last_conf;
|
||||
// watch for changes in projector_broadcast
|
||||
var last_broadcast;
|
||||
$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;
|
||||
var broadcast = Config.get('projector_broadcast').value;
|
||||
if (!last_broadcast || last_broadcast != broadcast) {
|
||||
last_broadcast = broadcast;
|
||||
$scope.broadcast = broadcast;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.changeProjector = function (projector) {
|
||||
$scope.active_projector = projector;
|
||||
$scope.scale = 256.0 / projector.width;
|
||||
$scope.iframeHeight = $scope.scale * projector.height;
|
||||
};
|
||||
|
||||
$scope.editCurrentSlide = function (projector) {
|
||||
var state = projector.getStateForCurrentSlide();
|
||||
if (state) {
|
||||
$state.go(state.state, state.param);
|
||||
}
|
||||
};
|
||||
|
||||
// *** countdown functions ***
|
||||
$scope.calculateCountdownTime = function (countdown) {
|
||||
countdown.seconds = Math.floor( countdown.countdown_time - Date.now() / 1000 + $scope.serverOffset );
|
||||
};
|
||||
$scope.rebuildAllElements = function () {
|
||||
$scope.countdowns = [];
|
||||
$scope.messages = [];
|
||||
// iterate via all projector elements and catch all countdowns and messages
|
||||
$.each(Projector.get(1).elements, function(key, value) {
|
||||
if (value.name == 'core/countdown') {
|
||||
$scope.countdowns.push(value);
|
||||
if (value.status == "running") {
|
||||
// calculate remaining seconds directly because interval starts with 1 second delay
|
||||
$scope.calculateCountdownTime(value);
|
||||
// start interval timer (every second)
|
||||
value.interval = $interval( function() { $scope.calculateCountdownTime(value); }, 1000);
|
||||
} else {
|
||||
value.seconds = value.countdown_time;
|
||||
$scope.editCountdown = function (countdown) {
|
||||
countdown.editFlag = false;
|
||||
$scope.projectors.forEach(function (projector) {
|
||||
_.forEach(projector.elements, function (element, uuid) {
|
||||
if (element.name == 'core/countdown' && element.index == countdown.index) {
|
||||
var data = {};
|
||||
data[uuid] = {
|
||||
"description": countdown.description,
|
||||
"default_time": parseInt(countdown.default_time)
|
||||
};
|
||||
if (!countdown.running) {
|
||||
data[uuid].countdown_time = parseInt(countdown.default_time);
|
||||
}
|
||||
$http.post('/rest/core/projector/' + projector.id + '/update_elements/', data);
|
||||
}
|
||||
}
|
||||
if (value.name == 'core/message') {
|
||||
$scope.messages.push(value);
|
||||
}
|
||||
});
|
||||
});
|
||||
$scope.scrollLevel = Projector.get(1).scroll;
|
||||
$scope.scaleLevel = Projector.get(1).scale;
|
||||
};
|
||||
|
||||
// get initial values for $scope.countdowns, $scope.messages, $scope.scrollLevel
|
||||
// and $scope.scaleLevel (after page reload)
|
||||
$scope.rebuildAllElements();
|
||||
|
||||
$scope.addCountdown = function () {
|
||||
var defaultvalue = parseInt(Config.get('projector_default_countdown').value);
|
||||
$http.post('/rest/core/projector/1/activate_elements/', [{
|
||||
var default_time = parseInt($scope.config('projector_default_countdown'));
|
||||
$scope.highestCountdownIndex++;
|
||||
// select all projectors on creation, so write the countdown to all projectors
|
||||
$scope.projectors.forEach(function (projector) {
|
||||
$http.post('/rest/core/projector/' + projector.id + '/activate_elements/', [{
|
||||
name: 'core/countdown',
|
||||
status: 'stop',
|
||||
countdown_time: default_time,
|
||||
default_time: default_time,
|
||||
visible: false,
|
||||
index: $scope.countdowns.length,
|
||||
countdown_time: defaultvalue,
|
||||
default: defaultvalue,
|
||||
stable: true
|
||||
}]);
|
||||
selected: true,
|
||||
index: $scope.highestCountdownIndex,
|
||||
running: false,
|
||||
stable: true,
|
||||
}]);
|
||||
});
|
||||
};
|
||||
$scope.removeCountdown = function (countdown) {
|
||||
var data = {};
|
||||
var delta = 0;
|
||||
// rebuild index for all countdowns after the selected (deleted) countdown
|
||||
for (var i=0; i<$scope.countdowns.length; i++) {
|
||||
if ( $scope.countdowns[i].uuid == countdown.uuid ) {
|
||||
delta = 1;
|
||||
} else if (delta > 0) {
|
||||
data[$scope.countdowns[i].uuid] = { "index": i - delta };
|
||||
}
|
||||
}
|
||||
$http.post('/rest/core/projector/1/deactivate_elements/', [countdown.uuid]);
|
||||
if (Object.keys(data).length > 0) {
|
||||
$http.post('/rest/core/projector/1/update_elements/', data);
|
||||
}
|
||||
};
|
||||
$scope.showCountdown = function (countdown) {
|
||||
var data = {};
|
||||
data[countdown.uuid] = { "visible": !countdown.visible };
|
||||
$http.post('/rest/core/projector/1/update_elements/', data);
|
||||
};
|
||||
$scope.editCountdown = function (countdown) {
|
||||
var data = {};
|
||||
data[countdown.uuid] = {
|
||||
"description": countdown.description,
|
||||
"default": parseInt(countdown.default)
|
||||
};
|
||||
if (countdown.status == "stop") {
|
||||
data[countdown.uuid].countdown_time = parseInt(countdown.default);
|
||||
}
|
||||
$http.post('/rest/core/projector/1/update_elements/', data);
|
||||
$scope.projectors.forEach(function (projector) {
|
||||
var countdowns = [];
|
||||
_.forEach(projector.elements, function (element, uuid) {
|
||||
if (element.name == 'core/countdown' && element.index == countdown.index) {
|
||||
$http.post('/rest/core/projector/' + projector.id + '/deactivate_elements/', [uuid]);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
$scope.startCountdown = function (countdown) {
|
||||
var data = {};
|
||||
// calculate end point of countdown (in seconds!)
|
||||
var endTimestamp = Date.now() / 1000 - $scope.serverOffset + countdown.countdown_time;
|
||||
data[countdown.uuid] = {
|
||||
"status": "running",
|
||||
"countdown_time": endTimestamp
|
||||
};
|
||||
$http.post('/rest/core/projector/1/update_elements/', data);
|
||||
$scope.projectors.forEach(function (projector) {
|
||||
_.forEach(projector.elements, function (element, uuid) {
|
||||
if (element.name == 'core/countdown' && element.index == countdown.index) {
|
||||
var data = {};
|
||||
// calculate end point of countdown (in seconds!)
|
||||
var endTimestamp = Date.now() / 1000 - $scope.serverOffset + countdown.countdown_time;
|
||||
data[uuid] = {
|
||||
'running': true,
|
||||
'countdown_time': endTimestamp
|
||||
};
|
||||
$http.post('/rest/core/projector/' + projector.id + '/update_elements/', data);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
$scope.stopCountdown = function (countdown) {
|
||||
var data = {};
|
||||
// calculate rest duration of countdown (in seconds!)
|
||||
var newDuration = Math.floor( countdown.countdown_time - Date.now() / 1000 + $scope.serverOffset );
|
||||
data[countdown.uuid] = {
|
||||
"status": "stop",
|
||||
"countdown_time": newDuration
|
||||
};
|
||||
$http.post('/rest/core/projector/1/update_elements/', data);
|
||||
$scope.projectors.forEach(function (projector) {
|
||||
_.forEach(projector.elements, function (element, uuid) {
|
||||
if (element.name == 'core/countdown' && element.index == countdown.index) {
|
||||
var data = {};
|
||||
// calculate rest duration of countdown (in seconds!)
|
||||
var newDuration = Math.floor( countdown.countdown_time - Date.now() / 1000 + $scope.serverOffset );
|
||||
data[uuid] = {
|
||||
'running': false,
|
||||
'countdown_time': newDuration
|
||||
};
|
||||
$http.post('/rest/core/projector/' + projector.id + '/update_elements/', data);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
$scope.resetCountdown = function (countdown) {
|
||||
var data = {};
|
||||
data[countdown.uuid] = {
|
||||
"status": "stop",
|
||||
"countdown_time": countdown.default,
|
||||
};
|
||||
$http.post('/rest/core/projector/1/update_elements/', data);
|
||||
$scope.projectors.forEach(function (projector) {
|
||||
_.forEach(projector.elements, function (element, uuid) {
|
||||
if (element.name == 'core/countdown' && element.index == countdown.index) {
|
||||
var data = {};
|
||||
data[uuid] = {
|
||||
'running': false,
|
||||
'countdown_time': countdown.default_time,
|
||||
};
|
||||
$http.post('/rest/core/projector/' + projector.id + '/update_elements/', data);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// *** message functions ***
|
||||
$scope.editMessage = function (message) {
|
||||
message.editFlag = false;
|
||||
$scope.projectors.forEach(function (projector) {
|
||||
_.forEach(projector.elements, function (element, uuid) {
|
||||
if (element.name == 'core/message' && element.index == message.index) {
|
||||
var data = {};
|
||||
data[uuid] = {
|
||||
message: message.message,
|
||||
};
|
||||
$http.post('/rest/core/projector/' + projector.id + '/update_elements/', data);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
$scope.addMessage = function () {
|
||||
$http.post('/rest/core/projector/1/activate_elements/', [{
|
||||
$scope.highestMessageIndex++;
|
||||
// select all projectors on creation, so write the countdown to all projectors
|
||||
$scope.projectors.forEach(function (projector) {
|
||||
$http.post('/rest/core/projector/' + projector.id + '/activate_elements/', [{
|
||||
name: 'core/message',
|
||||
visible: false,
|
||||
index: $scope.messages.length,
|
||||
selected: true,
|
||||
index: $scope.highestMessageIndex,
|
||||
message: '',
|
||||
stable: true
|
||||
}]);
|
||||
stable: true,
|
||||
}]);
|
||||
});
|
||||
};
|
||||
$scope.removeMessage = function (message) {
|
||||
$http.post('/rest/core/projector/1/deactivate_elements/', [message.uuid]);
|
||||
};
|
||||
$scope.showMessage = function (message) {
|
||||
var data = {};
|
||||
// if current message is activated, deactivate all other messages
|
||||
if ( !message.visible ) {
|
||||
for (var i=0; i<$scope.messages.length; i++) {
|
||||
if ( $scope.messages[i].uuid == message.uuid ) {
|
||||
data[$scope.messages[i].uuid] = { "visible": true };
|
||||
} else {
|
||||
data[$scope.messages[i].uuid] = { "visible": false };
|
||||
$scope.projectors.forEach(function (projector) {
|
||||
_.forEach(projector.elements, function (element, uuid) {
|
||||
if (element.name == 'core/message' && element.index == message.index) {
|
||||
$http.post('/rest/core/projector/' + projector.id + '/deactivate_elements/', [uuid]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data[message.uuid] = { "visible": false };
|
||||
}
|
||||
$http.post('/rest/core/projector/1/update_elements/', data);
|
||||
};
|
||||
$scope.editMessage = function (message) {
|
||||
var data = {};
|
||||
data[message.uuid] = {
|
||||
"message": message.message,
|
||||
};
|
||||
$http.post('/rest/core/projector/1/update_elements/', data);
|
||||
message.editMessageFlag = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// *** projector controls ***
|
||||
$scope.scrollLevel = Projector.get(1).scroll;
|
||||
$scope.scaleLevel = Projector.get(1).scale;
|
||||
$scope.controlProjector = function (action, direction) {
|
||||
$http.post('/rest/core/projector/1/control_view/', {"action": action, "direction": direction});
|
||||
/* project functions*/
|
||||
$scope.project = function (element) {
|
||||
$scope.projectors.forEach(function (projector) {
|
||||
_.forEach(projector.elements, function (projectorElement, uuid) {
|
||||
if (element.name == projectorElement.name && element.index == projectorElement.index) {
|
||||
var data = {};
|
||||
data[uuid] = {visible: !projectorElement.visible};
|
||||
$http.post('/rest/core/projector/' + projector.id + '/update_elements/', data);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
$scope.editCurrentSlide = function () {
|
||||
$.each(Projector.get(1).elements, function(key, value) {
|
||||
if (value.name == 'agenda/list-of-speakers') {
|
||||
$state.go('agenda.item.detail', {id: value.id});
|
||||
} else if (
|
||||
value.name != 'agenda/item-list' &&
|
||||
value.name != 'core/clock' &&
|
||||
value.name != 'core/countdown' &&
|
||||
value.name != 'core/message' ) {
|
||||
$state.go(value.name.replace('/', '.')+'.detail.update', {id: value.id});
|
||||
$scope.isProjected = function (element) {
|
||||
var projectorIds = [];
|
||||
$scope.projectors.forEach(function (projector) {
|
||||
_.forEach(projector.elements, function (projectorElement, uuid) {
|
||||
if (element.name == projectorElement.name && element.index == projectorElement.index) {
|
||||
if (projectorElement.visible && projectorElement.selected) {
|
||||
projectorIds.push(projector.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return projectorIds;
|
||||
};
|
||||
$scope.isProjectedOn = function (element, projector) {
|
||||
var projectedIds = $scope.isProjected(element);
|
||||
return _.indexOf(projectedIds, projector.id) > -1;
|
||||
};
|
||||
$scope.hasProjector = function (element, projector) {
|
||||
var hasProjector = false;
|
||||
_.forEach(projector.elements, function (projectorElement, uuid) {
|
||||
if (element.name == projectorElement.name && element.index == projectorElement.index) {
|
||||
if (projectorElement.selected) {
|
||||
hasProjector = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
return hasProjector;
|
||||
};
|
||||
$scope.toggleProjector = function (element, projector) {
|
||||
_.forEach(projector.elements, function (projectorElement, uuid) {
|
||||
if (element.name == projectorElement.name && element.index == projectorElement.index) {
|
||||
var data = {};
|
||||
data[uuid] = {
|
||||
'selected': !projectorElement.selected,
|
||||
};
|
||||
$http.post('/rest/core/projector/' + projector.id + '/update_elements/', data);
|
||||
}
|
||||
});
|
||||
};
|
||||
$scope.selectAll = function (element, value) {
|
||||
$scope.projectors.forEach(function (projector) {
|
||||
_.forEach(projector.elements, function (projectorElement, uuid) {
|
||||
if (element.name == projectorElement.name && element.index == projectorElement.index) {
|
||||
var data = {};
|
||||
data[uuid] = {
|
||||
'selected': value,
|
||||
};
|
||||
$http.post('/rest/core/projector/' + projector.id + '/update_elements/', data);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.preventClose = function (e) {
|
||||
e.stopPropagation();
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.controller('ManageProjectorsCtrl', [
|
||||
'$scope',
|
||||
'$http',
|
||||
'$state',
|
||||
'$timeout',
|
||||
'Projector',
|
||||
'ProjectionDefault',
|
||||
'Config',
|
||||
'gettextCatalog',
|
||||
function ($scope, $http, $state, $timeout, Projector, ProjectionDefault, Config, gettextCatalog) {
|
||||
ProjectionDefault.bindAll({}, $scope, 'projectiondefaults');
|
||||
|
||||
// watch for changes in projector_broadcast
|
||||
// and projector_currentListOfSpeakers_reference
|
||||
var last_broadcast, last_clos;
|
||||
$scope.$watch(function () {
|
||||
return Config.lastModified();
|
||||
}, function () {
|
||||
var broadcast = $scope.config('projector_broadcast'),
|
||||
currentListOfSpeakers = $scope.config('projector_currentListOfSpeakers_reference');
|
||||
if (!last_broadcast || last_broadcast != broadcast) {
|
||||
last_broadcast = broadcast;
|
||||
$scope.broadcast = broadcast;
|
||||
}
|
||||
if (!last_clos || last_clos != currentListOfSpeakers) {
|
||||
last_clos = currentListOfSpeakers;
|
||||
$scope.currentListOfSpeakers = currentListOfSpeakers;
|
||||
}
|
||||
});
|
||||
|
||||
// watch for changes in Projector, and recalc scale and iframeHeight
|
||||
var first_watch = true;
|
||||
$scope.resolutions = [];
|
||||
$scope.edit = [];
|
||||
$scope.$watch(function () {
|
||||
return Projector.lastModified();
|
||||
}, function () {
|
||||
$scope.projectors = Projector.getAll();
|
||||
$scope.projectors.forEach(function (projector) {
|
||||
projector.iframeScale = 256.0 / projector.width;
|
||||
projector.iframeHeight = projector.iframeScale * projector.height;
|
||||
if (first_watch) {
|
||||
$scope.resolutions[projector.id] = {
|
||||
width: projector.width,
|
||||
height: projector.height
|
||||
};
|
||||
$scope.edit[projector.id] = false;
|
||||
}
|
||||
});
|
||||
if ($scope.projectors.length) {
|
||||
first_watch = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Set list of speakers reference
|
||||
$scope.setListOfSpeakers = function (projector) {
|
||||
Config.get('projector_currentListOfSpeakers_reference').value = projector.id;
|
||||
Config.save('projector_currentListOfSpeakers_reference');
|
||||
};
|
||||
|
||||
// Projector functions
|
||||
$scope.setProjectionDefault = function (projector, def) {
|
||||
$http.post('/rest/core/projector/' + projector.id + '/set_projectiondefault/', def.id);
|
||||
};
|
||||
$scope.createProjector = function (name) {
|
||||
var projector = {
|
||||
name: name,
|
||||
config: {},
|
||||
scale: 0,
|
||||
scroll: 0,
|
||||
blank: false,
|
||||
projectiondefaults: [],
|
||||
};
|
||||
Projector.create(projector).then(function (projector) {
|
||||
$http.post('/rest/core/projector/' + projector.id + '/activate_elements/', [{
|
||||
name: 'core/clock',
|
||||
stable: true
|
||||
}]);
|
||||
$scope.resolutions[projector.id] = {
|
||||
width: projector.width,
|
||||
height: projector.height
|
||||
};
|
||||
});
|
||||
};
|
||||
$scope.deleteProjector = function (projector) {
|
||||
if (projector.id != 1) {
|
||||
Projector.destroy(projector.id);
|
||||
}
|
||||
};
|
||||
$scope.editCurrentSlide = function (projector) {
|
||||
var state = projector.getStateForCurrentSlide();
|
||||
if (state) {
|
||||
$state.go(state.state, state.param);
|
||||
}
|
||||
};
|
||||
$scope.editName = function (projector) {
|
||||
projector.config = projector.elements;
|
||||
Projector.save(projector);
|
||||
};
|
||||
$scope.changeResolution = function (projectorId) {
|
||||
$http.post(
|
||||
'/rest/core/projector/' + projectorId + '/set_resolution/',
|
||||
$scope.resolutions[projectorId]
|
||||
).then(function (success) {
|
||||
$scope.resolutions[projectorId].error = null;
|
||||
}, function (error) {
|
||||
$scope.resolutions[projectorId].error = error.data.detail;
|
||||
});
|
||||
};
|
||||
|
||||
// Identify projectors
|
||||
$scope.identifyProjectors = function () {
|
||||
if ($scope.identifyPromise) {
|
||||
$timeout.cancel($scope.identifyPromise);
|
||||
$scope.removeIdentifierMessages();
|
||||
} else {
|
||||
$scope.projectors.forEach(function (projector) {
|
||||
$http.post('/rest/core/projector/' + projector.id + '/activate_elements/', [{
|
||||
name: 'core/message',
|
||||
stable: true,
|
||||
selected: true,
|
||||
visible: true,
|
||||
message: gettextCatalog.getString('Projector') + ' ' + projector.id + ': ' + projector.name,
|
||||
type: 'identify'
|
||||
}]);
|
||||
});
|
||||
$scope.identifyPromise = $timeout($scope.removeIdentifierMessages, 3000);
|
||||
}
|
||||
};
|
||||
$scope.removeIdentifierMessages = function () {
|
||||
Projector.getAll().forEach(function (projector) {
|
||||
$.each(projector.elements, function (uuid, value) {
|
||||
if (value.name == 'core/message' && value.type == 'identify') {
|
||||
$http.post('/rest/core/projector/' + projector.id + '/deactivate_elements/', [uuid]);
|
||||
}
|
||||
});
|
||||
});
|
||||
$scope.identifyPromise = null;
|
||||
};
|
||||
}
|
||||
])
|
||||
|
@ -11,26 +11,6 @@
|
||||
id="{{ key }}"
|
||||
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>
|
||||
|
||||
<!-- comments -->
|
||||
<div class="input-comments" ng-if="type == 'comments'">
|
||||
<div ng-repeat="entry in $parent.value" class="input-group">
|
||||
|
200
openslides/core/static/templates/core/manage-projectors.html
Normal file
200
openslides/core/static/templates/core/manage-projectors.html
Normal file
@ -0,0 +1,200 @@
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
<div class="submenu">
|
||||
<div>
|
||||
<button class="btn btn-primary" ng-bootbox-prompt="{{ 'Please enter a name for the new projector' | translate }}"
|
||||
ng-bootbox-prompt-action="createProjector(result)">
|
||||
<i class="fa fa-plus"></i>
|
||||
<translate>New</translate>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-default dropdown-toggle" id="menuListofSpeakers" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="true">
|
||||
<translate>Current list of speakers reference</translate>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-entries" aria-labelledby="menuListOfSpeakers">
|
||||
<li ng-repeat="projector in projectors"
|
||||
ng-click="setListOfSpeakers(projector)">
|
||||
<i class="fa fa-check" ng-if="projector.id == currentListOfSpeakers"></i>
|
||||
{{ projector.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn" ng-click="identifyProjectors()" ng-class="identifyPromise ? 'btn-primary' : 'btn-default'">
|
||||
<i class="fa fa-binoculars"></i>
|
||||
<translate>Identify</translate>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<h1 translate>Projektoren verwalten</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="details">
|
||||
<div class="projectorContainer">
|
||||
|
||||
<div ng-repeat="projector in projectors">
|
||||
<div>
|
||||
<a ui-sref="projector({id: projector.id})">
|
||||
{{ projector.id }}:
|
||||
<strong>{{ projector.name }}</strong>
|
||||
</a>
|
||||
<a href="" class="pull-right" ng-click="edit[projector.id] = !edit[projector.id]"><i class="fa" ng-class="edit[projector.id] ? 'fa-times' : 'fa-pencil'"></i></a>
|
||||
</div>
|
||||
|
||||
<div ng-show="edit[projector.id]" style="margin-bottom: -20px;">
|
||||
<div>
|
||||
<div class="dropdown" uib-dropdown>
|
||||
<button class="btn btn-default btn-sm" id="menuProjector{{ pr.id }}" uib-dropdown-toggle>
|
||||
<translate>Projection defaults</translate>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-entries" aria-labelledby="menuProjector{{ pr.id }}">
|
||||
<li ng-repeat="projectiondefault in projectiondefaults | orderBy:'id'"
|
||||
ng-click="setProjectionDefault(projector, projectiondefault)">
|
||||
<i class="fa fa-check" ng-if="projectiondefault.projector_id === projector.id"></i>
|
||||
<translate>{{ projectiondefault.display_name }}</translate>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button type="button" class="btn btn-danger btn-sm"
|
||||
ng-hide="projector.id==1"
|
||||
ng-bootbox-confirm="{{ 'Are you sure you want to delete this entry?' | translate }}<br>
|
||||
<b>{{ projector.name }}</b>"
|
||||
ng-bootbox-confirm-action="deleteProjector(projector)">
|
||||
<i class="fa fa-trash"></i>
|
||||
<translate>Delete</translate>
|
||||
</button>
|
||||
<div ng-show="projector.id==1" style="height: 30px"><!-- Placeholder --></div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="name{{ projector.id }}" class="control-label"><translate>Name</translate>:</label>
|
||||
<input type="text" class="form-control" id="name{{ projector.id }}"
|
||||
ng-model="projector.name" ng-change="editName(projector)"
|
||||
ng-model-options="{debounce: 2000}"></input>
|
||||
</div>
|
||||
<div>
|
||||
<label for="resolution{{ projector.id }}" class="control-label"><translate>Resolution</translate>:</label>
|
||||
<div id="resolution{{ projector.id }}">
|
||||
<input ng-model="resolutions[projector.id].width"
|
||||
ng-model-option="{debounce: 2000}"
|
||||
ng-change="changeResolution(projector.id)"
|
||||
class="form-control resolution"
|
||||
id="{{ projector.id }}_width"
|
||||
type="number">
|
||||
x
|
||||
<input ng-model="resolutions[projector.id].height"
|
||||
ng-model-option="{debounce: 2000}"
|
||||
ng-change="changeResolution(projector.id)"
|
||||
class="form-control resolution"
|
||||
id="{{ projector.id }}_height"
|
||||
type="number">
|
||||
</div>
|
||||
<p class="help-block">
|
||||
{{ resolutions[projector.id].error }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#iframe_{{ projector.id }} {
|
||||
width: {{ projector.width }}px;
|
||||
height: {{ projector.height }}px;
|
||||
-moz-transform: scale({{ projector.iframeScale }});
|
||||
-webkit-transform: scale({{ projector.iframeScale }});
|
||||
-o-transform: scale({{ projector.iframeScale }});
|
||||
transform: scale({{ projector.iframeScale }});
|
||||
/* IE8+ - must be on one line, unfortunately */
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11={{ projector.iframeScale }}, M12=0, M21=0, M22={{ projector.iframeScale }}, SizingMethod='auto expand')";
|
||||
}
|
||||
|
||||
#iframewrapper_{{ projector.id }} {
|
||||
height: {{ projector.iframeHeight }}px;
|
||||
}
|
||||
|
||||
#iframeoverlay_{{ projector.id }} {
|
||||
height: {{ projector.iframeHeight }}px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<a ui-sref="projector({id: projector.id})" target="_blank">
|
||||
<div class="iframewrapper" id="iframewrapper_{{ projector.id }}">
|
||||
<iframe class="iframe" id="iframe_{{ projector.id }}" ng-src="{{ '/real-projector/' + projector.id }}" frameborder="0"></iframe>
|
||||
<div class="iframeoverlay" id="iframeoverlay_{{ projector.id }}"></div>
|
||||
</div>
|
||||
</a>
|
||||
<!-- projector control buttons -->
|
||||
<div os-perms="core.can_manage_projector">
|
||||
|
||||
<!-- edit -->
|
||||
<a ng-click="editCurrentSlide(projector)"
|
||||
class="btn btn-default btn-sm"
|
||||
title="{{ 'Edit current slide' | translate}}">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</a>
|
||||
|
||||
<!-- scale -->
|
||||
<div class="btn-group">
|
||||
<a ng-click="projector.controlProjector('scale', 'down')"
|
||||
class="btn btn-default btn-sm"
|
||||
title="{{ 'Smaller' | translate}}">
|
||||
<i class="fa fa-search-minus"></i>
|
||||
</a>
|
||||
<a ng-click="projector.controlProjector('scale', 'up')"
|
||||
class="btn btn-default btn-sm"
|
||||
title="{{ 'Bigger' | translate}}">
|
||||
<i class="fa fa-search-plus"></i>
|
||||
</a>
|
||||
<a ng-click="projector.controlProjector('scale', 'reset')"
|
||||
class="btn btn-default btn-sm"
|
||||
title="{{ 'Reset scaling' | translate}}">
|
||||
<i class="fa fa-undo"></i>
|
||||
</a>
|
||||
</div>
|
||||
<span ng-class="{'notNull': projector.scale != 0}">{{ projector.scale }}</span>
|
||||
|
||||
<!-- scroll -->
|
||||
<div class="btn-group">
|
||||
<a ng-click="projector.controlProjector('scroll', 'down')"
|
||||
class="btn btn-default btn-sm"
|
||||
title="{{ 'Scroll up' | translate}}">
|
||||
<i class="fa fa-arrow-up"></i>
|
||||
</a>
|
||||
<a ng-click="projector.controlProjector('scroll', 'up')"
|
||||
class="btn btn-default btn-sm"
|
||||
title="{{ 'Scroll down' | translate}}">
|
||||
<i class="fa fa-arrow-down"></i>
|
||||
</a>
|
||||
<a ng-click="projector.controlProjector('scroll', 'reset')"
|
||||
class="btn btn-default btn-sm"
|
||||
title="{{ 'Reset scrolling' | translate}}">
|
||||
<i class="fa fa-undo"></i>
|
||||
</a>
|
||||
</div>
|
||||
<span ng-class="{'notNull': projector.scroll != 0}">{{ projector.scroll }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Default, BC, Blank -->
|
||||
<div class="middle">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm" ng-class="broadcast == projector.id ? 'btn-primary' : 'btn-default'"
|
||||
ng-click="projector.toggleBroadcast(projector)"
|
||||
ng-disabled="broadcast > 0 && broadcast != projector.id">
|
||||
<i class="fa" ng-class="broadcast == projector.id ? 'fa-star' : 'fa-star-o'"></i>
|
||||
<translate>Broadcast</translate>
|
||||
</button>
|
||||
<button class="btn btn-sm" ng-class="projector.blank ? 'btn-primary' : 'btn-default'"
|
||||
ng-click="projector.toggleBlank(projector)"
|
||||
ng-disabled="broadcast > 0 && broadcast != projector.id">
|
||||
<i class="fa" ng-class="projector.blank ? 'fa-square' : 'fa-square-o'"></i>
|
||||
<translate>Blank</translate>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -10,10 +10,9 @@
|
||||
</a>
|
||||
<div uib-collapse="isLiveViewClosed" ng-cloak>
|
||||
<style>
|
||||
/* iframe for live view */
|
||||
.col2 #iframe {
|
||||
width: {{ projectorWidth }}px;
|
||||
height: {{ projectorHeight }}px;
|
||||
.col2 #iframe_sidebar {
|
||||
width: {{ active_projector.width }}px;
|
||||
height: {{ active_projector.height }}px;
|
||||
-moz-transform: scale({{ scale }});
|
||||
-webkit-transform: scale({{ scale }});
|
||||
-o-transform: scale({{ scale }});
|
||||
@ -22,25 +21,72 @@
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11={{ scale }}, M12=0, M21=0, M22={{ scale }}, SizingMethod='auto expand')";
|
||||
}
|
||||
|
||||
.col2 #iframewrapper {
|
||||
.col2 #iframewrapper_sidebar {
|
||||
height: {{ iframeHeight }}px;
|
||||
}
|
||||
|
||||
.col2 #iframeoverlay {
|
||||
.col2 #iframeoverlay_sidebar {
|
||||
height: {{ iframeHeight }}px;
|
||||
}
|
||||
</style>
|
||||
<a ui-sref="projector" target="_blank">
|
||||
<div id="iframewrapper">
|
||||
<iframe id="iframe" src="/real-projector" frameborder="0"></iframe>
|
||||
<div id="iframeoverlay"></div>
|
||||
<div class="projectorSelector">
|
||||
<div>
|
||||
<div class="dropdown" ng-show="projectors.length > 1">
|
||||
<button class="btn btn-default btn-sm dropdown-toggle" id="menuProjector" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
{{ active_projector.name }}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-entries" aria-labelledby="menuProjector">
|
||||
<li ng-repeat="projector in projectors"
|
||||
ng-class="{'projected': projector === active_projector}"
|
||||
ng-click="changeProjector(projector)">
|
||||
<i ng-show="projector === active_projector" class="fa fa-video-camera"></i>
|
||||
{{ projector.name }}
|
||||
<i ng-show="projector.id == broadcast" class="fa fa-star-o spacer-left"></i>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-sm" ng-click="active_projector.toggleBlank()" ng-hide="projectors.length > 1"
|
||||
ng-class="active_projector.blank ? 'btn-primary' : 'btn-default'">
|
||||
<i class="fa" ng-class="active_projector.blank ? 'fa-square' : 'fa-square-o'"></i>
|
||||
<translate>Blank</translate>
|
||||
</button>
|
||||
</div>
|
||||
<a class="btn btn-primary btn-sm manageBtn" ui-sref="manage-projectors">
|
||||
<i class="fa fa-cog fa-lg"></i>
|
||||
<translate>Manage</translate>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="btn-group" ng-show="projectors.length > 1">
|
||||
<button class="btn btn-sm" ng-class="broadcast == active_projector.id ? 'btn-primary' : 'btn-default'"
|
||||
ng-click="active_projector.toggleBroadcast()" ng-disabled="broadcast > 0 && broadcast != active_projector.id">
|
||||
<i class="fa" ng-class="broadcast == active_projector.id ? 'fa-star' : 'fa-star-o'"></i>
|
||||
<translate>Broadcast</translate>
|
||||
</button>
|
||||
<button class="btn btn-sm" ng-click="active_projector.toggleBlank()"
|
||||
ng-class="active_projector.blank ? 'btn-primary' : 'btn-default'"
|
||||
ng-disabled="broadcast > 0 && broadcast != active_projector.id">
|
||||
<i class="fa" ng-class="active_projector.blank ? 'fa-square' : 'fa-square-o'"></i>
|
||||
<translate>Blank</translate>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a ui-sref="projector({id: active_projector.id })" target="_blank">
|
||||
<div class="iframewrapper" id="iframewrapper_sidebar">
|
||||
<iframe class="iframe" id="iframe_sidebar" ng-src="{{ '/real-projector/' + active_projector.id }}" frameborder="0"></iframe>
|
||||
<div class="iframeoverlay" id="iframeoverlay_sidebar"></div>
|
||||
</div>
|
||||
</a>
|
||||
<!-- projector control buttons -->
|
||||
<div os-perms="core.can_manage_projector">
|
||||
|
||||
<!-- edit -->
|
||||
<a ng-click="editCurrentSlide()"
|
||||
<a ng-click="editCurrentSlide(active_projector)"
|
||||
class="btn btn-default btn-sm"
|
||||
title="{{ 'Edit current slide' | translate}}">
|
||||
<i class="fa fa-pencil"></i>
|
||||
@ -48,43 +94,43 @@
|
||||
|
||||
<!-- scale -->
|
||||
<div class="btn-group">
|
||||
<a ng-click="controlProjector('scale', 'down')"
|
||||
<a ng-click="active_projector.controlProjector('scale', 'down')"
|
||||
class="btn btn-default btn-sm"
|
||||
title="{{ 'Smaller' | translate}}">
|
||||
<i class="fa fa-search-minus"></i>
|
||||
</a>
|
||||
<a ng-click="controlProjector('scale', 'up')"
|
||||
<a ng-click="active_projector.controlProjector('scale', 'up')"
|
||||
class="btn btn-default btn-sm"
|
||||
title="{{ 'Bigger' | translate}}">
|
||||
<i class="fa fa-search-plus"></i>
|
||||
</a>
|
||||
<a ng-click="controlProjector('scale', 'reset')"
|
||||
<a ng-click="active_projector.controlProjector('scale', 'reset')"
|
||||
class="btn btn-default btn-sm"
|
||||
title="{{ 'Reset scaling' | translate}}">
|
||||
<i class="fa fa-undo"></i>
|
||||
</a>
|
||||
</div>
|
||||
<span ng-class="{ 'notNull': scaleLevel != 0 }">{{ scaleLevel }}</span>
|
||||
<span ng-class="{ 'notNull': active_projector.scale != 0 }">{{ active_projector.scale }}</span>
|
||||
|
||||
<!-- scroll -->
|
||||
<div class="btn-group">
|
||||
<a ng-click="controlProjector('scroll', 'down')"
|
||||
<a ng-click="active_projector.controlProjector('scroll', 'down')"
|
||||
class="btn btn-default btn-sm"
|
||||
title="{{ 'Scroll up' | translate}}">
|
||||
<i class="fa fa-arrow-up"></i>
|
||||
</a>
|
||||
<a ng-click="controlProjector('scroll', 'up')"
|
||||
<a ng-click="active_projector.controlProjector('scroll', 'up')"
|
||||
class="btn btn-default btn-sm"
|
||||
title="{{ 'Scroll down' | translate}}">
|
||||
<i class="fa fa-arrow-down"></i>
|
||||
</a>
|
||||
<a ng-click="controlProjector('scroll', 'reset')"
|
||||
<a ng-click="active_projector.controlProjector('scroll', 'reset')"
|
||||
class="btn btn-default btn-sm"
|
||||
title="{{ 'Reset scrolling' | translate}}">
|
||||
<i class="fa fa-undo"></i>
|
||||
</a>
|
||||
</div>
|
||||
<span ng-class="{ 'notNull': scrollLevel != 0 }">{{ scrollLevel }}</span>
|
||||
<span ng-class="{ 'notNull': active_projector.scroll != 0 }">{{ active_projector.scroll }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -96,11 +142,10 @@
|
||||
<h4 translate>Countdowns</h4>
|
||||
</a>
|
||||
<div uib-collapse="!isCountdowns" ng-cloak>
|
||||
<div ng-repeat="countdown in countdowns | orderBy: 'index'" id="{{countdown.uuid}}"
|
||||
class="countdown panel panel-default">
|
||||
<div ng-repeat="countdown in countdowns | orderBy: 'index'" id="countdown{{countdown.uuid}}" class="countdown panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<span ng-if="countdown.description">{{ countdown.description }}</span>
|
||||
<span ng-if="!countdown.description">Countdown {{ countdown.index +1 }}</span>
|
||||
<span ng-if="!countdown.description">Countdown {{ $index +1 }}</span>
|
||||
<!-- remove countdown button -->
|
||||
<button type="button" class="close"
|
||||
ng-click="removeCountdown(countdown)"
|
||||
@ -109,36 +154,58 @@
|
||||
</button>
|
||||
<!-- edit countdown button -->
|
||||
<button type="button" class="close editicon"
|
||||
ng-click="editCountdownFlag=true;"
|
||||
ng-click="countdown.editFlag=true;"
|
||||
title="{{ 'Edit countdown' | translate}}">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="panel-body"
|
||||
ng-class="{ 'projected': countdown.visible }">
|
||||
ng-class="{ 'projected': isProjected(countdown).length }">
|
||||
<!-- project countdown button -->
|
||||
<a class="btn btn-default btn-sm"
|
||||
ng-model="countdown.visible"
|
||||
ng-click="showCountdown(countdown)"
|
||||
ng-class="{ 'btn-primary': countdown.visible }"
|
||||
title="{{ 'Project countdown' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</a>
|
||||
<div class="btn-group" style="width:54px;" uib-dropdown>
|
||||
<button type="button" class="btn btn-default btn-sm"
|
||||
ng-click="project(countdown)"
|
||||
ng-class="{ 'btn-primary': isProjected(countdown).length }">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</button>
|
||||
<button ng-if="projectors.length > 1" type="button" class="btn btn-default btn-sm slimDropDown" uib-dropdown-toggle>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li role="menuitem" ng-if="projectors.length > 1" style="text-align: center;">
|
||||
<span class="pointer" ng-click="selectAll(countdown, true); preventClose($event)" translate>
|
||||
All
|
||||
</span>
|
||||
| <span class="pointer" ng-click="selectAll(countdown, false); preventClose($event)" translate>
|
||||
None
|
||||
</span>
|
||||
</li>
|
||||
<li class="divider" ng-if="projectors.length > 1"></li>
|
||||
<li role="menuitem" ng-repeat="projector in projectors">
|
||||
<a href="" ng-click="toggleProjector(countdown, projector); preventClose($event)"
|
||||
ng-class="{ 'projected': isProjectedOn(countdown, projector) }">
|
||||
<i class="fa fa-square-o" ng-hide="hasProjector(countdown, projector)"></i>
|
||||
<i class="fa fa-check-square-o" ng-show="hasProjector(countdown, projector)"></i>
|
||||
{{ projector.name }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- countdown controls -->
|
||||
<a class="btn btn-default vcenter"
|
||||
ng-click="resetCountdown(countdown)"
|
||||
ng-class="{ 'disabled': countdown.status == 'stop' && countdown.default == countdown.countdown_time }"
|
||||
ng-class="{ 'disabled': !countdown.running && countdown.default_time == countdown.countdown_time }"
|
||||
title="{{ 'Reset countdown' | translate}}">
|
||||
<i class="fa fa-stop"></i>
|
||||
</a>
|
||||
<a ng-if="countdown.status=='stop'" class="btn btn-default vcenter"
|
||||
<a ng-if="!countdown.running" class="btn btn-default vcenter"
|
||||
ng-click="startCountdown(countdown)"
|
||||
title="{{ 'Start' | translate}}">
|
||||
<i class="fa fa-play"></i>
|
||||
<i ng-if="countdown.status=='running'" class="fa fa-pause"></i>
|
||||
<i ng-if="countdown.running" class="fa fa-pause"></i>
|
||||
</a>
|
||||
<a ng-if="countdown.status=='running'" class="btn btn-default vcenter"
|
||||
<a ng-if="countdown.running" class="btn btn-default vcenter"
|
||||
ng-click="stopCountdown(countdown)"
|
||||
title="{{ 'Pause' | translate}}">
|
||||
<i class="fa fa-pause"></i>
|
||||
@ -150,22 +217,29 @@
|
||||
{{ countdown.seconds | osSecondsToTime }}
|
||||
</span>
|
||||
<!-- edit countdown form -->
|
||||
<form ng-show="editCountdownFlag" ng-submit="editCountdown(countdown)">
|
||||
<form ng-show="countdown.editFlag"
|
||||
ng-submit="editCountdown(countdown)">
|
||||
<div class="form-group">
|
||||
<label translate>Description</label>
|
||||
<input ng-model="countdown.description" type="text" class="form-control input-sm">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label translate>Start time</label>
|
||||
<input data-ng-model="countdown.default" min-sec-format
|
||||
<div class="input-group">
|
||||
<input data-ng-model="countdown.default_time" min-sec-format
|
||||
type="text" placeholder="mm:ss" class="form-control input-sm">
|
||||
<div class="input-group-addon pointer" uib-tooltip="{{ 'Reset countdown' | translate }}"
|
||||
ng-click="countdown.reset()">
|
||||
<i class="fa fa-undo"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit"
|
||||
title="{{ 'Save' | translate}}"
|
||||
class="btn btn-sm btn-primary">
|
||||
<i class="fa fa-check"></i>
|
||||
</button>
|
||||
<button ng-click="editCountdownFlag=false;"
|
||||
<button ng-click="countdown.editFlag=false;"
|
||||
title="{{ 'Cancel' | translate}}"
|
||||
class="btn btn-default btn-sm">
|
||||
<i class="fa fa-times"></i>
|
||||
@ -182,7 +256,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- messages -->
|
||||
<div class="section" os-perms="core.can_manage_projector">
|
||||
<a href="#" ng-click="isMessages = !isMessages">
|
||||
@ -190,10 +263,10 @@
|
||||
<h4 translate>Messages</h4>
|
||||
</a>
|
||||
<div uib-collapse="!isMessages" ng-cloak>
|
||||
<div ng-repeat="message in messages | orderBy: 'index'" id="{{message.uuid}}" class="message panel panel-default">
|
||||
<div ng-repeat="message in messages" id="message{{message.id}}" class="message panel panel-default">
|
||||
|
||||
<div class="panel-heading">
|
||||
<span>{{ 'Message' | translate }} {{ message.index + 1 }}</span>
|
||||
<span>{{ 'Message' | translate }} {{ $index + 1 }}</span>
|
||||
<!-- remove message button -->
|
||||
<button type="button" class="close"
|
||||
ng-click="removeMessage(message)"
|
||||
@ -201,29 +274,52 @@
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
<button type="button" class="close editicon"
|
||||
ng-click="editMessageFlag=true;"
|
||||
ng-click="message.editFlag=true"
|
||||
title="{{ 'Edit message' | translate}}">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="panel-body"
|
||||
ng-class="{ 'projected': message.visible }">
|
||||
ng-class="{ 'projected': isProjected(message).length }">
|
||||
<div class="projectorbtn">
|
||||
<!-- project message button -->
|
||||
<a class="btn btn-default btn-sm"
|
||||
ng-model="message.visible"
|
||||
ng-click="showMessage(message)"
|
||||
ng-class="{ 'btn-primary': message.visible }"
|
||||
title="{{ 'Project message' | translate }}" float="left">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</a>
|
||||
<div class="btn-group" style="width:54px;" uib-dropdown>
|
||||
<button type="button" class="btn btn-default btn-sm"
|
||||
ng-click="project(message)"
|
||||
ng-class="{ 'btn-primary': isProjected(message).length }">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</button>
|
||||
<button type="button" ng-if="projectors.length > 1" class="btn btn-default btn-sm slimDropDown" uib-dropdown-toggle>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li role="menuitem" ng-if="projectors.length > 1" style="text-align: center;">
|
||||
<span class="pointer" ng-click="selectAll(message, true); preventClose($event)" translate>
|
||||
All
|
||||
</span>
|
||||
| <span class="pointer" ng-click="selectAll(message, false); preventClose($event)" translate>
|
||||
None
|
||||
</span>
|
||||
</li>
|
||||
<li class="divider" ng-if="projectors.length > 1"></li>
|
||||
<li role="menuitem" ng-repeat="projector in projectors">
|
||||
<a href="" ng-click="toggleProjector(message, projector); preventClose($event)"
|
||||
ng-class="{ 'projected': isProjectedOn(message, projector) }">
|
||||
<i class="fa fa-square-o" ng-hide="hasProjector(message, projector)"></i>
|
||||
<i class="fa fa-check-square-o" ng-show="hasProjector(message, projector)"></i>
|
||||
{{ projector.name }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="innermessage" ng-bind-html="message.message"> </div>
|
||||
|
||||
<div class="panel-input">
|
||||
<div ng-if="editMessageFlag" class="input-group">
|
||||
<div ng-if="message.editFlag" class="input-group">
|
||||
<input ng-model="message.message" type="text" class="form-control input-sm">
|
||||
<a ng-click="editMessage(message)"
|
||||
title="{{ 'Save' | translate}}"
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div ng-controller="SlideCountdownCtrl">
|
||||
<div ng-if="visible">
|
||||
<div ng-if="visible && selected">
|
||||
<div class="countdown well pull-right"
|
||||
ng-class="{
|
||||
'negative': seconds <= 0,
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div ng-controller="SlideMessageCtrl">
|
||||
<div ng-if="visible" class="message_background"></div>
|
||||
<div ng-if="visible" class="message well" ng-bind-html="message"></div>
|
||||
<div ng-if="visible && selected" class="message_background"></div>
|
||||
<div ng-if="visible && selected" class="message well" ng-class="{'identify': type=='identify'}" ng-bind-html="message"></div>
|
||||
</div>
|
||||
|
27
openslides/core/static/templates/projector-button.html
Normal file
27
openslides/core/static/templates/projector-button.html
Normal file
@ -0,0 +1,27 @@
|
||||
<div class="btn-group" style="min-width:{{ projectors.length > 1 ? '54' : '34' }}px;" uib-dropdown
|
||||
uib-tooltip="{{ 'Projector' | translate }} {{ model.isProjected(additionalId) }}"
|
||||
tooltip-enable="model.isProjected(additionalId) > 0"
|
||||
os-perms="core.can_manage_projector">
|
||||
<button type="button" class="btn btn-default btn-sm"
|
||||
ng-click="model.project(defaultProjectorId, additionalId)"
|
||||
ng-class="{ 'btn-primary': model.isProjected(additionalId) == defaultProjectorId }">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
{{ content }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-sm slimDropDown"
|
||||
ng-class="{ 'btn-primary': (model.isProjected(additionalId) > 0 && model.isProjected(additionalId) != defaultProjectorId) }"
|
||||
ng-if="projectors.length > 1"
|
||||
uib-dropdown-toggle>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li role="menuitem" ng-repeat="projector in projectors">
|
||||
<a href="" ng-click="model.project(projector.id, additionalId)"
|
||||
ng-class="{ 'projected': (model.isProjected(additionalId) == projector.id) }">
|
||||
<i class="fa fa-video-camera" ng-show="model.isProjected(additionalId) == projector.id"></i>
|
||||
{{ projector.name }}
|
||||
<span ng-if="defaultProjectorId == projector.id">(<translate>Standard</translate>)</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
@ -37,10 +37,14 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="iframewrapper">
|
||||
<iframe id="iframe" src="/real-projector" frameborder="0"></iframe>
|
||||
<div id="iframewrapper" ng-hide="error">
|
||||
<iframe id="iframe" ng-src="{{ '/real-projector/' + projector_id }}" frameborder="0"></iframe>
|
||||
<div id="iframeoverlay"></div>
|
||||
</div>
|
||||
|
||||
<div class="error" ng-show="error">
|
||||
<p>{{ error | translate }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -11,34 +11,41 @@
|
||||
<script src="static/js/openslides.js"></script>
|
||||
<script src="static/js/openslides-templates.js"></script>
|
||||
|
||||
<style type="text/css">
|
||||
#header, #footer {
|
||||
background-color: {{ config('projector_header_backgroundcolor') }};
|
||||
}
|
||||
#header, #footer, #currentTime {
|
||||
color: {{ config('projector_header_fontcolor') }};
|
||||
}
|
||||
h1 {
|
||||
color: {{ config('projector_h1_fontcolor') }};
|
||||
}
|
||||
</style>
|
||||
<div id="projectorContainer" ng-controller="ProjectorCtrl">
|
||||
<style type="text/css">
|
||||
#header, #footer {
|
||||
background-color: {{ config('projector_header_backgroundcolor') }};
|
||||
}
|
||||
#header, #footer, #currentTime {
|
||||
color: {{ config('projector_header_fontcolor') }};
|
||||
}
|
||||
#header, #footer, .contentContainer {
|
||||
visibility: {{ blank ? 'hidden' : 'visible' }};
|
||||
}
|
||||
#projectorContainer {
|
||||
background-color: {{ blank ? config('projector_blank_color') : '#fff' }};
|
||||
height: {{ blank ? '100%' : 'auto' }};
|
||||
}
|
||||
h1 {
|
||||
color: {{ config('projector_h1_fontcolor') }};
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<div id="header">
|
||||
<img ng-if="config('projector_enable_logo')" id="logo" src="/static/img/logo-projector.png" alt="OpenSlides" />
|
||||
<div ng-if="config('projector_enable_title')" id="eventdata">
|
||||
<div class="title" ng-class="{ 'titleonly': !config('general_event_description') }"
|
||||
ng-bind-html="config('general_event_name')"></div>
|
||||
<div ng-if="config('general_event_description')" class="description"
|
||||
ng-bind-html="config('general_event_description')"></div>
|
||||
<div id="header">
|
||||
<img ng-if="config('projector_enable_logo')" id="logo" src="/static/img/logo-projector.png" alt="OpenSlides" />
|
||||
<div ng-if="config('projector_enable_title')" id="eventdata">
|
||||
<div class="title" ng-class="{ 'titleonly': !config('general_event_description') }"
|
||||
ng-bind-html="config('general_event_name')"></div>
|
||||
<div ng-if="config('general_event_description')" class="description"
|
||||
ng-bind-html="config('general_event_description')"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-controller="ProjectorCtrl">
|
||||
<style type="text/css">
|
||||
.scrollcontent {
|
||||
margin-top: {{scroll}}px !important;
|
||||
font-size: {{scale}}%;
|
||||
margin-top: {{ -80 * projector.scroll }}px !important;
|
||||
font-size: {{ 100 + 20 * projector.scale }}%;
|
||||
}
|
||||
.mediascrollcontent {
|
||||
margin-top: {{scroll/2}}em !important;
|
||||
@ -47,21 +54,21 @@
|
||||
transform: scale({{scale/100}});
|
||||
}
|
||||
</style>
|
||||
<div ng-repeat="element in elements | orderBy:'index'">
|
||||
<div class="contentContainer" ng-repeat="element in elements | orderBy:'index'">
|
||||
<div ng-include="element.template"></div>
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<span ng-if="config('general_event_date')">
|
||||
{{ config('general_event_date') }}
|
||||
</span>
|
||||
<span ng-if="config('general_event_date') && config('general_event_location')">
|
||||
|
|
||||
</span>
|
||||
<span ng-if="config('general_event_location')">
|
||||
{{ config('general_event_location') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<span ng-if="config('general_event_date')">
|
||||
{{ config('general_event_date') }}
|
||||
</span>
|
||||
<span ng-if="config('general_event_date') && config('general_event_location')">
|
||||
|
|
||||
</span>
|
||||
<span ng-if="config('general_event_location')">
|
||||
{{ config('general_event_location') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<script src="/webclient/projector/"></script>
|
||||
|
@ -28,10 +28,10 @@ urlpatterns = [
|
||||
name='core_webclient_javascript'),
|
||||
|
||||
# View for the projectors are handled by angular.
|
||||
url(r'^projector.*$', views.ProjectorView.as_view()),
|
||||
url(r'^projector/(\d+)/$', views.ProjectorView.as_view()),
|
||||
|
||||
# Original view without resolutioncontrol for the projectors are handled by angular.
|
||||
url(r'^real-projector.*$', views.RealProjectorView.as_view()),
|
||||
url(r'^real-projector/(\d+)/$', views.RealProjectorView.as_view()),
|
||||
|
||||
# Main entry point for all angular pages.
|
||||
# Has to be the last entry in the urls.py
|
||||
|
@ -18,6 +18,7 @@ from django.utils.timezone import now
|
||||
|
||||
from openslides import __version__ as version
|
||||
from openslides.utils import views as utils_views
|
||||
from openslides.utils.autoupdate import inform_changed_data
|
||||
from openslides.utils.plugins import (
|
||||
get_plugin_description,
|
||||
get_plugin_verbose_name,
|
||||
@ -25,7 +26,6 @@ from openslides.utils.plugins import (
|
||||
)
|
||||
from openslides.utils.rest_api import (
|
||||
ModelViewSet,
|
||||
ReadOnlyModelViewSet,
|
||||
Response,
|
||||
SimpleMetadata,
|
||||
ValidationError,
|
||||
@ -42,7 +42,7 @@ from .access_permissions import (
|
||||
)
|
||||
from .config import config
|
||||
from .exceptions import ConfigError, ConfigNotFound
|
||||
from .models import ChatMessage, Projector, Tag
|
||||
from .models import ChatMessage, ProjectionDefault, Projector, Tag
|
||||
|
||||
|
||||
# Special Django views
|
||||
@ -171,7 +171,7 @@ class WebclientJavaScriptView(utils_views.View):
|
||||
|
||||
# Viewsets for the REST API
|
||||
|
||||
class ProjectorViewSet(ReadOnlyModelViewSet):
|
||||
class ProjectorViewSet(ModelViewSet):
|
||||
"""
|
||||
API endpoint for the projector slide info.
|
||||
|
||||
@ -192,13 +192,23 @@ class ProjectorViewSet(ReadOnlyModelViewSet):
|
||||
result = self.request.user.has_perm('core.can_see_projector')
|
||||
elif self.action in ('activate_elements', 'prune_elements', 'update_elements',
|
||||
'deactivate_elements', 'clear_elements', 'control_view',
|
||||
'set_resolution', 'set_scroll'):
|
||||
'set_resolution', 'set_scroll', 'control_blank', 'destroy',
|
||||
'create', 'update', 'broadcast', 'set_projectiondefault'):
|
||||
result = (self.request.user.has_perm('core.can_see_projector') and
|
||||
self.request.user.has_perm('core.can_manage_projector'))
|
||||
else:
|
||||
result = False
|
||||
return result
|
||||
|
||||
# Assign all projectionDefaults from this projector to the default projector (pk=1)
|
||||
def destroy(self, *args, **kwargs):
|
||||
projector_instance = self.get_object()
|
||||
for a in ProjectionDefault.objects.all():
|
||||
if a.projector.id == projector_instance.id:
|
||||
a.projector = Projector.objects.get(pk=1)
|
||||
a.save()
|
||||
return super(ProjectorViewSet, self).destroy(*args, **kwargs)
|
||||
|
||||
@detail_route(methods=['post'])
|
||||
def activate_elements(self, request, pk):
|
||||
"""
|
||||
@ -447,6 +457,70 @@ class ProjectorViewSet(ReadOnlyModelViewSet):
|
||||
scroll=request.data)
|
||||
return Response({'detail': message})
|
||||
|
||||
@detail_route(methods=['post'])
|
||||
def control_blank(self, request, pk):
|
||||
"""
|
||||
REST API operation to blank the projector.
|
||||
|
||||
It expects a POST request to
|
||||
/rest/core/projector/<pk>/control_blank/ with a value for blank.
|
||||
"""
|
||||
if not isinstance(request.data, bool):
|
||||
raise ValidationError({'detail': 'Data must be a bool.'})
|
||||
|
||||
projector_instance = self.get_object()
|
||||
projector_instance.blank = request.data
|
||||
projector_instance.save()
|
||||
message = "Setting 'blank' to {blank} was successful.".format(
|
||||
blank=request.data)
|
||||
return Response({'detail': message})
|
||||
|
||||
@detail_route(methods=['post'])
|
||||
def broadcast(self, request, pk):
|
||||
"""
|
||||
REST API operation to (un-)broadcast the given projector.
|
||||
This method takes care, that all other projectors get the new requirements.
|
||||
|
||||
It expects a POST request to
|
||||
/rest/core/projector/<pk>/broadcast/ without an argument
|
||||
"""
|
||||
if config['projector_broadcast'] == 0:
|
||||
config['projector_broadcast'] = pk
|
||||
projector_instance = self.get_object()
|
||||
inform_changed_data(projector_instance)
|
||||
message = "Setting projector {id} as broadcast projector was successful.".format(
|
||||
id=pk)
|
||||
else:
|
||||
config['projector_broadcast'] = 0
|
||||
message = "Disabling broadcast was successful."
|
||||
return Response({'detail': message})
|
||||
|
||||
@detail_route(methods=['post'])
|
||||
def set_projectiondefault(self, request, pk):
|
||||
"""
|
||||
REST API operation to set a projectiondefault to the requested projector. The argument
|
||||
has to be an int representing the pk from the projectiondefault to be set.
|
||||
|
||||
It expects a POST request to
|
||||
/rest/core/projector/<pk>/set_projectiondefault/ with the projectiondefault id as the argument
|
||||
"""
|
||||
if not isinstance(request.data, int):
|
||||
raise ValidationError({'detail': 'Data must be an int.'})
|
||||
|
||||
try:
|
||||
projectiondefault = ProjectionDefault.objects.get(pk=request.data)
|
||||
except ProjectionDefault.DoesNotExist:
|
||||
raise ValidationError({'detail': 'The projectiondefault with pk={pk} was not found.'.format(
|
||||
pk=request.data)})
|
||||
else:
|
||||
projector_instance = self.get_object()
|
||||
projectiondefault.projector = projector_instance
|
||||
projectiondefault.save()
|
||||
|
||||
return Response('Setting projectiondefault "{name}" to projector {projector_id} was successful.'.format(
|
||||
name=projectiondefault.display_name,
|
||||
projector_id=projector_instance.pk))
|
||||
|
||||
|
||||
class TagViewSet(ModelViewSet):
|
||||
"""
|
||||
|
@ -56,19 +56,35 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
|
||||
'MediafileForm',
|
||||
'User',
|
||||
'Projector',
|
||||
function($scope, $http, ngDialog, Mediafile, MediafileForm, User, Projector) {
|
||||
'ProjectionDefault',
|
||||
function($scope, $http, ngDialog, Mediafile, MediafileForm, User, Projector, ProjectionDefault) {
|
||||
Mediafile.bindAll({}, $scope, 'mediafiles');
|
||||
User.bindAll({}, $scope, 'users');
|
||||
$scope.$watch(function() {
|
||||
return Projector.lastModified();
|
||||
}, function() {
|
||||
$scope.projectors = Projector.getAll();
|
||||
updatePresentedMediafiles();
|
||||
});
|
||||
$scope.$watch(function () {
|
||||
return Projector.lastModified();
|
||||
}, function () {
|
||||
var projectiondefault = ProjectionDefault.filter({name: 'mediafiles'})[0];
|
||||
if (projectiondefault) {
|
||||
$scope.defaultProjectorId = projectiondefault.projector_id;
|
||||
}
|
||||
});
|
||||
|
||||
// setup table sorting
|
||||
$scope.sortColumn = 'title';
|
||||
$scope.filterPresent = '';
|
||||
$scope.reverse = false;
|
||||
|
||||
function updatePresentedMediafiles() {
|
||||
var projectorElements = _.map(Projector.get(1).elements, function(element) { return element; });
|
||||
$scope.presentedMediafiles = _.filter(projectorElements, function (element) {
|
||||
return element.name === 'mediafiles/mediafile';
|
||||
function updatePresentedMediafiles () {
|
||||
$scope.presentedMediafiles = [];
|
||||
Projector.getAll().forEach(function (projector) {
|
||||
var projectorElements = _.map(projector.elements, function(element) { return element; });
|
||||
var mediaElements = _.filter(projectorElements, function (element) {
|
||||
return element.name === 'mediafiles/mediafile';
|
||||
});
|
||||
mediaElements.forEach(function (element) {
|
||||
$scope.presentedMediafiles.push(element);
|
||||
});
|
||||
});
|
||||
if ($scope.presentedMediafiles.length) {
|
||||
$scope.isMeta = false;
|
||||
@ -77,12 +93,13 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$watch(function() {
|
||||
return Projector.get(1).elements;
|
||||
}, updatePresentedMediafiles);
|
||||
|
||||
updatePresentedMediafiles();
|
||||
|
||||
// setup table sorting
|
||||
$scope.sortColumn = 'title';
|
||||
$scope.filterPresent = '';
|
||||
$scope.reverse = false;
|
||||
|
||||
// function to sort by clicked column
|
||||
$scope.toggleSort = function ( column ) {
|
||||
if ( $scope.sortColumn === column ) {
|
||||
@ -138,37 +155,47 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
|
||||
|
||||
// ** PDF presentation functions **/
|
||||
// show document on projector
|
||||
$scope.showMediafile = function (mediafile) {
|
||||
var postUrl = '/rest/core/projector/1/prune_elements/';
|
||||
var data = [{
|
||||
name: 'mediafiles/mediafile',
|
||||
id: mediafile.id,
|
||||
numPages: mediafile.mediafile.pages,
|
||||
page: 1,
|
||||
scale: 'page-fit',
|
||||
rotate: 0,
|
||||
visible: true,
|
||||
playing: false,
|
||||
fullscreen: mediafile.is_pdf
|
||||
}];
|
||||
$http.post(postUrl, data);
|
||||
$scope.showMediafile = function (projectorId, mediafile) {
|
||||
var isProjectedId = mediafile.isProjected();
|
||||
if (isProjectedId > 0) {
|
||||
$http.post('/rest/core/projector/' + isProjectedId + '/prune_elements/', []);
|
||||
}
|
||||
if (isProjectedId != projectorId) {
|
||||
var postUrl = '/rest/core/projector/' + projectorId + '/prune_elements/';
|
||||
var data = [{
|
||||
name: 'mediafiles/mediafile',
|
||||
id: mediafile.id,
|
||||
numPages: mediafile.mediafile.pages,
|
||||
page: 1,
|
||||
scale: 'page-fit',
|
||||
rotate: 0,
|
||||
visible: true,
|
||||
playing: false,
|
||||
fullscreen: mediafile.is_pdf
|
||||
}];
|
||||
$http.post(postUrl, data);
|
||||
}
|
||||
};
|
||||
|
||||
function sendMediafileCommand(data) {
|
||||
var mediafileElement = getCurrentlyPresentedMediafile();
|
||||
var updateData = _.extend({}, mediafileElement);
|
||||
// To avoid some kind of 60,000000000001% in template
|
||||
$scope.round = function (val) {return Math.round(val);};
|
||||
|
||||
var sendMediafileCommand = function (mediafile, data) {
|
||||
var updateData = _.extend({}, mediafile);
|
||||
_.extend(updateData, data);
|
||||
var postData = {};
|
||||
postData[mediafileElement.uuid] = updateData;
|
||||
$http.post('/rest/core/projector/1/update_elements/', postData);
|
||||
}
|
||||
postData[mediafile.uuid] = updateData;
|
||||
|
||||
// Find Projector where the mediafile is projected
|
||||
$scope.projectors.forEach(function (projector) {
|
||||
if (_.find(projector.elements, function (e) {return e.uuid == mediafile.uuid;})) {
|
||||
$http.post('/rest/core/projector/' + projector.id + '/update_elements/', postData);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function getCurrentlyPresentedMediafile() {
|
||||
return $scope.presentedMediafiles[0];
|
||||
}
|
||||
|
||||
$scope.getTitle = function (presentedMediafile) {
|
||||
return Mediafile.get(presentedMediafile.id).title;
|
||||
$scope.getTitle = function (mediafile) {
|
||||
return Mediafile.get(mediafile.id).title;
|
||||
};
|
||||
|
||||
$scope.getType = function(presentedMediafile) {
|
||||
@ -176,55 +203,57 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
|
||||
return mediafile.is_pdf ? 'pdf' : mediafile.is_image ? 'image' : 'video';
|
||||
};
|
||||
|
||||
$scope.mediafileGoToPage = function (page) {
|
||||
var mediafileElement = getCurrentlyPresentedMediafile();
|
||||
$scope.mediafileGoToPage = function (mediafile, page) {
|
||||
if (parseInt(page) > 0) {
|
||||
sendMediafileCommand({
|
||||
page: parseInt(page)
|
||||
});
|
||||
sendMediafileCommand(
|
||||
mediafile,
|
||||
{page: parseInt(page)}
|
||||
);
|
||||
}
|
||||
};
|
||||
$scope.mediafileZoomIn = function () {
|
||||
var mediafileElement = getCurrentlyPresentedMediafile();
|
||||
$scope.mediafileZoomIn = function (mediafile) {
|
||||
var scale = 1;
|
||||
if (parseFloat(mediafileElement.scale)) {
|
||||
scale = mediafileElement.scale;
|
||||
if (parseFloat(mediafile.scale)) {
|
||||
scale = mediafile.scale;
|
||||
}
|
||||
sendMediafileCommand({
|
||||
scale: scale + 0.2
|
||||
});
|
||||
sendMediafileCommand(
|
||||
mediafile,
|
||||
{scale: scale + 0.2}
|
||||
);
|
||||
};
|
||||
$scope.mediafileFit = function () {
|
||||
sendMediafileCommand({
|
||||
scale: 'page-fit'
|
||||
});
|
||||
$scope.mediafileFit = function (mediafile) {
|
||||
sendMediafileCommand(
|
||||
mediafile,
|
||||
{scale: 'page-fit'}
|
||||
);
|
||||
};
|
||||
$scope.mediafileZoomOut = function () {
|
||||
var mediafileElement = getCurrentlyPresentedMediafile();
|
||||
$scope.mediafileZoomOut = function (mediafile) {
|
||||
var scale = 1;
|
||||
if (parseFloat(mediafileElement.scale)) {
|
||||
scale = mediafileElement.scale;
|
||||
if (parseFloat(mediafile.scale)) {
|
||||
scale = mediafile.scale;
|
||||
}
|
||||
sendMediafileCommand({
|
||||
scale: scale - 0.2
|
||||
});
|
||||
sendMediafileCommand(
|
||||
mediafile,
|
||||
{scale: scale - 0.2}
|
||||
);
|
||||
};
|
||||
$scope.mediafileChangePage = function(pageNum) {
|
||||
sendMediafileCommand({
|
||||
pageToDisplay: pageNum
|
||||
});
|
||||
$scope.mediafileChangePage = function(mediafile, pageNum) {
|
||||
sendMediafileCommand(
|
||||
mediafile,
|
||||
{pageToDisplay: pageNum}
|
||||
);
|
||||
};
|
||||
$scope.mediafileRotate = function () {
|
||||
var mediafileElement = getCurrentlyPresentedMediafile();
|
||||
var rotation = mediafileElement.rotate;
|
||||
$scope.mediafileRotate = function (mediafile) {
|
||||
var rotation = mediafile.rotate;
|
||||
if (rotation === 270) {
|
||||
rotation = 0;
|
||||
} else {
|
||||
rotation = rotation + 90;
|
||||
}
|
||||
sendMediafileCommand({
|
||||
rotate: rotation
|
||||
});
|
||||
sendMediafileCommand(
|
||||
mediafile,
|
||||
{rotate: rotation}
|
||||
);
|
||||
};
|
||||
$scope.mediafileScroll = function(scroll) {
|
||||
var mediafileElement = getCurrentlyPresentedMediafile();
|
||||
|
@ -28,12 +28,12 @@
|
||||
<h3>{{ getTitle(presentedMediafile) }}</h3>
|
||||
<nav ng-show="getType(presentedMediafile) === 'pdf'" ng-class="getNavStyle(scroll)" class="form-inline">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-default" ng-click="mediafileGoToPage(presentedMediafile.page - 1)"
|
||||
<button class="btn btn-default" ng-click="mediafileGoToPage(presentedMediafile, presentedMediafile.page - 1)"
|
||||
ng-class="{ 'disabled': (presentedMediafile.page - 1) < 1 }"
|
||||
title="{{ 'Previous page' | translate }}">
|
||||
<i class="fa fa-backward"></i>
|
||||
</button>
|
||||
<button class="btn btn-default" ng-click="mediafileGoToPage(presentedMediafile.page + 1)"
|
||||
<button class="btn btn-default" ng-click="mediafileGoToPage(presentedMediafile, presentedMediafile.page + 1)"
|
||||
ng-class="{ 'disabled': (presentedMediafile.page + 1) > presentedMediafile.numPages }"
|
||||
title="{{ 'Next page' | translate }}">
|
||||
<i class="fa fa-forward"></i>
|
||||
@ -42,23 +42,23 @@
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon" translate>Page</span>
|
||||
<input type="number" min=1 ng-model="presentedMediafile.page" class="form-control" style="width: 80px"
|
||||
ng-change="mediafileGoToPage(presentedMediafile.page)">
|
||||
ng-change="mediafileGoToPage(presentedMediafile, presentedMediafile.page)">
|
||||
<span class="input-group-addon"><translate>of</translate> {{presentedMediafile.numPages}}</span>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-default" ng-click="mediafileRotate()" title="{{ 'Rotate clockwise' | translate }}">
|
||||
<button class="btn btn-default" ng-click="mediafileRotate(presentedMediafile)" title="{{ 'Rotate clockwise' | translate }}">
|
||||
<i class="fa fa-repeat"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-default" ng-click="mediafileZoomOut()" title="{{ 'Zoom out' | translate }}">
|
||||
<button class="btn btn-default" ng-click="mediafileZoomOut(presentedMediafile)" title="{{ 'Zoom out' | translate }}">
|
||||
<i class="fa fa-search-minus"></i>
|
||||
</button>
|
||||
<button class="btn" ng-click="mediafileFit()" title="{{ 'Reset zoom' | translate }}"
|
||||
<button class="btn" ng-click="mediafileFit(presentedMediafile)" title="{{ 'Reset zoom' | translate }}"
|
||||
ng-class="presentedMediafile.scale=='page-fit' ? 'btn-primary' : 'btn-default'">
|
||||
<i class="fa fa-arrows-alt"></i>
|
||||
</button>
|
||||
<button class="btn btn-default" ng-click="mediafileZoomIn()" title="{{ 'Zoom in' | translate }}">
|
||||
<button class="btn btn-default" ng-click="mediafileZoomIn(presentedMediafile)" title="{{ 'Zoom in' | translate }}">
|
||||
<i class="fa fa-search-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
@ -195,13 +195,32 @@
|
||||
<!-- projector column -->
|
||||
<td ng-show="!isDeleteMode"
|
||||
os-perms="core.can_manage_projector">
|
||||
<a class="btn btn-default btn-sm"
|
||||
ng-if="mediafile.is_presentable"
|
||||
ng-class="{ 'btn-primary': mediafile.isProjected() }"
|
||||
ng-click="showMediafile(mediafile)"
|
||||
title="{{ 'Project mediafile' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</a>
|
||||
<div class="btn-group" style="min-width:54px;" uib-dropdown
|
||||
ng-if="mediafile.mediafile.is_presentable"
|
||||
uib-tooltip="{{ 'Projektor' | translate }} {{ mediafile.isProjected() }}"
|
||||
tooltip-enable="mediafile.isProjected() > 0">
|
||||
<button type="button" class="btn btn-default btn-sm"
|
||||
ng-click="showMediafile(defaultProjectorId, mediafile)"
|
||||
ng-class="{ 'btn-primary': mediafile.isProjected() == defaultProjectorId }">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-sm slimDropDown"
|
||||
ng-class="{ 'btn-primary': (mediafile.isProjected() > 0 && mediafile.isProjected() != defaultProjectorId) }"
|
||||
ng-if="projectors.length > 1"
|
||||
uib-dropdown-toggle>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li role="menuitem" ng-repeat="projector in projectors">
|
||||
<a href="" ng-click="showMediafile(projector.id, mediafile)"
|
||||
ng-class="{ 'projected': (mediafile.isProjected() == projector.id) }">
|
||||
<i class="fa fa-video-camera" ng-show="mediafile.isProjected() == projector.id"></i>
|
||||
{{ projector.name }}
|
||||
<span ng-if="defaultProjectorId == projector.id">(<translate>Standard</translate>)</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- delete selection column -->
|
||||
<td ng-show="isDeleteMode" os-perms="mediafiles.can_manage" class="deleteColumn">
|
||||
<input type="checkbox" ng-model="mediafile.selected">
|
||||
|
@ -21,7 +21,8 @@ angular.module('OpenSlidesApp.motions.projector', ['OpenSlidesApp.motions'])
|
||||
'User',
|
||||
'Config',
|
||||
'Projector',
|
||||
function($scope, $rootScope, $http, Motion, User, Config, Projector) {
|
||||
'$timeout',
|
||||
function($scope, $rootScope, $http, Motion, User, Config, Projector, $timeout) {
|
||||
// Attention! Each object that is used here has to be dealt on server side.
|
||||
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||
// class.
|
||||
@ -78,8 +79,11 @@ angular.module('OpenSlidesApp.motions.projector', ['OpenSlidesApp.motions'])
|
||||
}
|
||||
};
|
||||
|
||||
Motion.bindOne(id, $scope, 'motion');
|
||||
Motion.find(id).then(function (motion) {
|
||||
$scope.motion = motion;
|
||||
});
|
||||
User.bindAll({}, $scope, 'users');
|
||||
|
||||
}
|
||||
]);
|
||||
|
||||
|
@ -917,14 +917,25 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid
|
||||
'PdfMakeDocumentProvider',
|
||||
'gettextCatalog',
|
||||
'HTMLValidizer',
|
||||
'Projector',
|
||||
'ProjectionDefault',
|
||||
function($scope, $state, $http, ngDialog, MotionForm, Motion, Category, Tag, Workflow, User, Agenda, MotionDocxExport,
|
||||
MotionContentProvider, MotionCatalogContentProvider, PdfMakeConverter,
|
||||
PdfMakeDocumentProvider, gettextCatalog, HTMLValidizer) {
|
||||
MotionContentProvider, MotionCatalogContentProvider, PdfMakeConverter, PdfMakeDocumentProvider,
|
||||
gettextCatalog, HTMLValidizer, Projector, ProjectionDefault) {
|
||||
Motion.bindAll({}, $scope, 'motions');
|
||||
Category.bindAll({}, $scope, 'categories');
|
||||
Tag.bindAll({}, $scope, 'tags');
|
||||
Workflow.bindAll({}, $scope, 'workflows');
|
||||
User.bindAll({}, $scope, 'users');
|
||||
Projector.bindAll({}, $scope, 'projectors');
|
||||
$scope.$watch(function () {
|
||||
return Projector.lastModified();
|
||||
}, function () {
|
||||
var projectiondefault = ProjectionDefault.filter({name: 'motions'})[0];
|
||||
if (projectiondefault) {
|
||||
$scope.defaultProjectorId = projectiondefault.projector_id;
|
||||
}
|
||||
});
|
||||
$scope.alert = {};
|
||||
|
||||
// setup table sorting
|
||||
@ -1192,9 +1203,10 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid
|
||||
'gettextCatalog',
|
||||
'Projector',
|
||||
'HTMLValidizer',
|
||||
'ProjectionDefault',
|
||||
function($scope, $http, ngDialog, MotionForm, Motion, Category, Mediafile, Tag, User, Workflow, Config,
|
||||
motion, MotionContentProvider, PollContentProvider,
|
||||
PdfMakeConverter, PdfMakeDocumentProvider, MotionInlineEditing, gettextCatalog, Projector, HTMLValidizer) {
|
||||
motion, MotionContentProvider, PollContentProvider, PdfMakeConverter, PdfMakeDocumentProvider,
|
||||
MotionInlineEditing, gettextCatalog, Projector, HTMLValidizer, ProjectionDefault) {
|
||||
Motion.bindOne(motion.id, $scope, 'motion');
|
||||
Category.bindAll({}, $scope, 'categories');
|
||||
Mediafile.bindAll({}, $scope, 'mediafiles');
|
||||
@ -1202,6 +1214,12 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid
|
||||
User.bindAll({}, $scope, 'users');
|
||||
Workflow.bindAll({}, $scope, 'workflows');
|
||||
Motion.loadRelations(motion, 'agenda_item');
|
||||
$scope.$watch(function () {
|
||||
return Projector.lastModified();
|
||||
}, function () {
|
||||
console.log(ProjectionDefault.getAll());
|
||||
$scope.defaultProjectorId = ProjectionDefault.filter({name: 'motions'})[0].projector_id;
|
||||
});
|
||||
$scope.version = motion.active_version;
|
||||
$scope.isCollapsed = true;
|
||||
$scope.commentsFields = Config.get('motions_comments').value;
|
||||
|
@ -16,12 +16,8 @@
|
||||
<translate>List of speakers</translate>
|
||||
</a>
|
||||
<!-- project -->
|
||||
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
||||
ng-class="{ 'btn-primary': motion.isProjected() }"
|
||||
ng-click="motion.project()"
|
||||
title="{{ 'Project motion' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</a>
|
||||
<projector-button model="motion" default-projector-id="defaultProjectorId">
|
||||
</projector-button>
|
||||
<!-- edit -->
|
||||
<a ng-if="motion.isAllowed('update')" ng-click="openDialog(motion)"
|
||||
class="btn btn-default btn-sm"
|
||||
|
@ -98,7 +98,6 @@
|
||||
ng-click="checkAll()"></i>
|
||||
</div>
|
||||
<div class="col-xs-11 main-header">
|
||||
|
||||
<span class="form-inline text-right pull-right">
|
||||
<span class="sort-spacer pointer" ng-click="reset_filters()"
|
||||
ng-if="are_filters_set()" ng-disabled="isDeleteMode"
|
||||
@ -304,12 +303,8 @@
|
||||
</div>
|
||||
<!-- projector column -->
|
||||
<div class="col-xs-1 centered" os-perms="core.can_manage_projector">
|
||||
<a class="btn btn-default btn-sm"
|
||||
ng-class="{ 'btn-primary': motion.isProjected() }"
|
||||
ng-click="motion.project()"
|
||||
title="{{ 'Project motion' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</a>
|
||||
<projector-button model="motion", default-projector-id="defaultProjectorId">
|
||||
</projector-button>
|
||||
</div>
|
||||
<!-- main content column -->
|
||||
<div class="col-xs-6 content">
|
||||
|
@ -3,6 +3,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
import openslides.utils.models
|
||||
|
||||
|
||||
|
@ -156,8 +156,18 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics'])
|
||||
'TopicForm',
|
||||
'Topic',
|
||||
'topic',
|
||||
function($scope, ngDialog, TopicForm, Topic, topic) {
|
||||
'Projector',
|
||||
'ProjectionDefault',
|
||||
function($scope, ngDialog, TopicForm, Topic, topic, Projector, ProjectionDefault) {
|
||||
Topic.bindOne(topic.id, $scope, 'topic');
|
||||
$scope.$watch(function () {
|
||||
return Projector.lastModified();
|
||||
}, function () {
|
||||
var projectiondefault = ProjectionDefault.filter({name: 'topics'})[0];
|
||||
if (projectiondefault) {
|
||||
$scope.defaultProjectorId = projectiondefault.projector_id;
|
||||
}
|
||||
});
|
||||
Topic.loadRelations(topic, 'agenda_item');
|
||||
$scope.openDialog = function (topic) {
|
||||
ngDialog.open(TopicForm.getDialog(topic));
|
||||
|
@ -11,12 +11,8 @@
|
||||
<translate>List of speakers</translate>
|
||||
</a>
|
||||
<!-- project -->
|
||||
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
||||
ng-class="{ 'btn-primary': topic.isProjected() }"
|
||||
ng-click="topic.project()"
|
||||
title="{{ 'Project topic' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</a>
|
||||
<projector-button model="topic" default-projector-id="defaultProjectorId">
|
||||
</projector-button>
|
||||
<!-- edit -->
|
||||
<a os-perms="agenda.can_manage" ng-click="openDialog(topic)"
|
||||
class="btn btn-default btn-sm"
|
||||
|
@ -421,9 +421,19 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users'])
|
||||
'User',
|
||||
'Group',
|
||||
'PasswordGenerator',
|
||||
function($scope, $state, $http, ngDialog, UserForm, User, Group, PasswordGenerator) {
|
||||
'Projector',
|
||||
'ProjectionDefault',
|
||||
function($scope, $state, $http, ngDialog, UserForm, User, Group, PasswordGenerator, Projector, ProjectionDefault) {
|
||||
User.bindAll({}, $scope, 'users');
|
||||
Group.bindAll({where: {id: {'>': 1}}}, $scope, 'groups');
|
||||
$scope.$watch(function () {
|
||||
return Projector.lastModified();
|
||||
}, function () {
|
||||
var projectiondefault = ProjectionDefault.filter({name: 'users'})[0];
|
||||
if (projectiondefault) {
|
||||
$scope.defaultProjectorId = projectiondefault.projector_id;
|
||||
}
|
||||
});
|
||||
$scope.alert = {};
|
||||
$scope.groupFilter = undefined;
|
||||
|
||||
|
@ -184,12 +184,8 @@
|
||||
ng-class="{ 'activeline': user.isProjected(), 'selected': user.selected }">
|
||||
<!-- projector column -->
|
||||
<td ng-show="!isSelectMode" os-perms="core.can_manage_projector">
|
||||
<a class="btn btn-default btn-sm"
|
||||
ng-class="{ 'btn-primary': user.isProjected() }"
|
||||
ng-click="user.project()"
|
||||
title="{{ 'Project user' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</a>
|
||||
<projector-button model="user" default-projector-id="defaultProjectorId">
|
||||
</projector-button>
|
||||
<!-- selection column -->
|
||||
<td ng-show="isSelectMode" os-perms="users.can_manage" class="deleteColumn">
|
||||
<input type="checkbox" ng-model="user.selected">
|
||||
|
@ -138,11 +138,25 @@ def send_data(message):
|
||||
projectors = Projector.get_projectors_that_show_this(collection_element)
|
||||
send_all = None # The decission is done later
|
||||
|
||||
broadcast_id = config['projector_broadcast']
|
||||
if broadcast_id > 0:
|
||||
projectors = Projector.objects.all() # also the broadcasted projector should get data
|
||||
broadcast_projector = Projector.objects.get(pk=broadcast_id)
|
||||
|
||||
send_all = True
|
||||
# The data from the broadcasted projector
|
||||
broadcast_projector_data = get_projector_element_data(broadcast_projector)
|
||||
else:
|
||||
broadcast_projector_data = None
|
||||
|
||||
for projector in projectors:
|
||||
if send_all is None:
|
||||
send_all = projector.need_full_update_for_this(collection_element)
|
||||
if send_all:
|
||||
output = get_projector_element_data(projector)
|
||||
if broadcast_projector_data is None:
|
||||
output = get_projector_element_data(projector)
|
||||
else:
|
||||
output = broadcast_projector_data
|
||||
else:
|
||||
output = []
|
||||
output.append(collection_element.as_autoupdate_for_projector())
|
||||
|
@ -14,6 +14,7 @@ from rest_framework.routers import DefaultRouter
|
||||
from rest_framework.serializers import ModelSerializer as _ModelSerializer
|
||||
from rest_framework.serializers import ( # noqa
|
||||
MANY_RELATION_KWARGS,
|
||||
BooleanField,
|
||||
CharField,
|
||||
DictField,
|
||||
Field,
|
||||
@ -29,8 +30,6 @@ from rest_framework.serializers import ( # noqa
|
||||
)
|
||||
from rest_framework.viewsets import GenericViewSet as _GenericViewSet # noqa
|
||||
from rest_framework.viewsets import ModelViewSet as _ModelViewSet # noqa
|
||||
from rest_framework.viewsets import \
|
||||
ReadOnlyModelViewSet as _ReadOnlyModelViewSet # noqa
|
||||
from rest_framework.viewsets import ViewSet as _ViewSet # noqa
|
||||
|
||||
router = DefaultRouter()
|
||||
@ -174,9 +173,5 @@ class ModelViewSet(PermissionMixin, _ModelViewSet):
|
||||
pass
|
||||
|
||||
|
||||
class ReadOnlyModelViewSet(PermissionMixin, _ReadOnlyModelViewSet):
|
||||
pass
|
||||
|
||||
|
||||
class ViewSet(PermissionMixin, _ViewSet):
|
||||
pass
|
||||
|
@ -216,17 +216,6 @@ class Speak(TestCase):
|
||||
|
||||
def test_begin_speech_with_countdown(self):
|
||||
config['agenda_couple_countdown_and_speakers'] = True
|
||||
projector = Projector.objects.get(pk=1)
|
||||
projector.config['03e87dea9c3f43c88b756c06a4c044fb'] = {
|
||||
'name': 'core/countdown',
|
||||
'status': 'stop',
|
||||
'visible': True,
|
||||
'default': 60,
|
||||
'countdown_time': 60,
|
||||
'stable': True,
|
||||
'index': 0
|
||||
}
|
||||
projector.save()
|
||||
Speaker.objects.add(self.user, self.item)
|
||||
speaker = Speaker.objects.add(get_user_model().objects.get(username='admin'), self.item)
|
||||
self.client.put(
|
||||
@ -234,32 +223,22 @@ class Speak(TestCase):
|
||||
{'speaker': speaker.pk})
|
||||
for key, value in Projector.objects.get().config.items():
|
||||
if value['name'] == 'core/countdown':
|
||||
self.assertEqual(value['status'], 'running')
|
||||
success = True
|
||||
self.assertTrue(value['running'])
|
||||
# If created, the countdown should have index 1
|
||||
created = value['index'] == 1
|
||||
break
|
||||
else:
|
||||
success = False
|
||||
self.assertTrue(success)
|
||||
created = False
|
||||
self.assertTrue(created)
|
||||
|
||||
def test_end_speech_with_countdown(self):
|
||||
config['agenda_couple_countdown_and_speakers'] = True
|
||||
projector = Projector.objects.get(pk=1)
|
||||
projector.config['03e87dea9c3f43c88b756c06a4c044fb'] = {
|
||||
'name': 'core/countdown',
|
||||
'status': 'stop',
|
||||
'visible': True,
|
||||
'default': 60,
|
||||
'countdown_time': 60,
|
||||
'stable': True,
|
||||
'index': 0
|
||||
}
|
||||
projector.save()
|
||||
speaker = Speaker.objects.add(get_user_model().objects.get(username='admin'), self.item)
|
||||
speaker.begin_speech()
|
||||
self.client.delete(reverse('item-speak', args=[self.item.pk]))
|
||||
for key, value in Projector.objects.get().config.items():
|
||||
if value['name'] == 'core/countdown':
|
||||
self.assertEqual(value['status'], 'stop')
|
||||
self.assertFalse(value['running'])
|
||||
success = True
|
||||
break
|
||||
else:
|
||||
|
@ -25,9 +25,11 @@ class ProjectorAPI(TestCase):
|
||||
default_projector.save()
|
||||
|
||||
response = self.client.get(reverse('projector-detail', args=['1']))
|
||||
content = json.loads(response.content.decode())
|
||||
del content['projectiondefaults']
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(json.loads(response.content.decode()), {
|
||||
self.assertEqual(content, {
|
||||
'id': 1,
|
||||
'elements': {
|
||||
'aae4a07b26534cfb9af4232f361dce73':
|
||||
@ -36,6 +38,8 @@ class ProjectorAPI(TestCase):
|
||||
'name': 'topics/topic'}},
|
||||
'scale': 0,
|
||||
'scroll': 0,
|
||||
'name': 'Defaultprojector',
|
||||
'blank': False,
|
||||
'width': 1024,
|
||||
'height': 768})
|
||||
|
||||
@ -47,9 +51,11 @@ class ProjectorAPI(TestCase):
|
||||
default_projector.save()
|
||||
|
||||
response = self.client.get(reverse('projector-detail', args=['1']))
|
||||
content = json.loads(response.content.decode())
|
||||
del content['projectiondefaults']
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(json.loads(response.content.decode()), {
|
||||
self.assertEqual(content, {
|
||||
'id': 1,
|
||||
'elements': {
|
||||
'fc6ef43b624043068c8e6e7a86c5a1b0':
|
||||
@ -58,6 +64,8 @@ class ProjectorAPI(TestCase):
|
||||
'error': 'Projector element does not exist.'}},
|
||||
'scale': 0,
|
||||
'scroll': 0,
|
||||
'name': 'Defaultprojector',
|
||||
'blank': False,
|
||||
'width': 1024,
|
||||
'height': 768})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user