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 support for big assemblies with lots of users.
|
||||||
- Added HTML support for messages on the projector.
|
- Added HTML support for messages on the projector.
|
||||||
- Moved custom slides to own app "topics". Renamed it to "Topic".
|
- Moved custom slides to own app "topics". Renamed it to "Topic".
|
||||||
|
- Added support for multiple projectors
|
||||||
|
|
||||||
Motions:
|
Motions:
|
||||||
- Added origin field.
|
- Added origin field.
|
||||||
|
@ -64,3 +64,32 @@ class ListOfSpeakersSlide(ProjectorElement):
|
|||||||
# Full update if item changes because then we may have new speakers
|
# Full update if item changes because then we may have new speakers
|
||||||
# and therefor need new users.
|
# and therefor need new users.
|
||||||
return collection_element.collection_string == Item.get_collection_string()
|
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
|
// override project function of jsDataModel factory
|
||||||
project: function() {
|
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(
|
return $http.post(
|
||||||
'/rest/core/projector/1/prune_elements/',
|
'/rest/core/projector/' + projectorId + '/prune_elements/',
|
||||||
[{name: this.content_object.collection, id: this.content_object.id}]
|
[{name: name, tree: tree, id: id}]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// override isProjected function of jsDataModel factory
|
// override isProjected function of jsDataModel factory
|
||||||
isProjected: function (list) {
|
isProjected: function (tree) {
|
||||||
// Returns true if there is a projector element with the same
|
// Returns the id of the last projector with an agenda-item element. Else return 0.
|
||||||
// name and the same id.
|
if (typeof tree === 'undefined') {
|
||||||
var projector = Projector.get(1);
|
tree = false;
|
||||||
var isProjected;
|
}
|
||||||
if (typeof projector !== 'undefined') {
|
|
||||||
var self = this;
|
var self = this;
|
||||||
var predicate = function (element) {
|
var predicate = function (element) {
|
||||||
var value;
|
var value;
|
||||||
if (typeof list === 'undefined') {
|
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
|
// Releated item detail slide
|
||||||
value = element.name == self.content_object.collection &&
|
value = element.name == self.content_object.collection &&
|
||||||
typeof element.id !== 'undefined' &&
|
typeof element.id !== 'undefined' &&
|
||||||
element.id == self.content_object.id;
|
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;
|
return value;
|
||||||
};
|
};
|
||||||
isProjected = typeof _.findKey(projector.elements, predicate) === 'string';
|
var isProjected = 0;
|
||||||
} else {
|
Projector.getAll().forEach(function (projector) {
|
||||||
isProjected = false;
|
if (typeof _.findKey(projector.elements, predicate) === 'string') {
|
||||||
|
isProjected = projector.id;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
return isProjected;
|
return isProjected;
|
||||||
},
|
},
|
||||||
// project list of speakers
|
// project list of speakers
|
||||||
projectListOfSpeakers: function() {
|
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(
|
return $http.post(
|
||||||
'/rest/core/projector/1/prune_elements/',
|
'/rest/core/projector/' + projectorId + '/prune_elements/',
|
||||||
[{name: 'agenda/list-of-speakers', id: this.id}]
|
[{name: 'agenda/list-of-speakers', id: this.id}]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// check if list of speakers is projected
|
// check if list of speakers is projected
|
||||||
isListOfSpeakersProjected: function () {
|
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.
|
// name 'agenda/list-of-speakers' and the same id.
|
||||||
var projector = Projector.get(1);
|
|
||||||
if (typeof projector === 'undefined') return false;
|
|
||||||
var self = this;
|
var self = this;
|
||||||
var predicate = function (element) {
|
var predicate = function (element) {
|
||||||
return element.name == 'agenda/list-of-speakers' &&
|
return element.name == 'agenda/list-of-speakers' &&
|
||||||
typeof element.id !== 'undefined' &&
|
typeof element.id !== 'undefined' &&
|
||||||
element.id == self.id;
|
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) {
|
hasSubitems: function(items) {
|
||||||
var self = this;
|
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.
|
// Make sure that the Agenda resource is loaded.
|
||||||
.run(['Agenda', function(Agenda) {}]);
|
.run(['Agenda', function(Agenda) {}]);
|
||||||
|
|
||||||
|
@ -13,6 +13,39 @@ angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda'])
|
|||||||
slidesProvider.registerSlide('agenda/item-list', {
|
slidesProvider.registerSlide('agenda/item-list', {
|
||||||
template: 'static/templates/agenda/slide-item-list.html',
|
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',
|
'$scope',
|
||||||
'Agenda',
|
'Agenda',
|
||||||
'User',
|
'User',
|
||||||
function($scope, Agenda, User) {
|
function ($scope, Agenda, User) {
|
||||||
// Attention! Each object that is used here has to be dealt on server side.
|
// 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
|
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||||
// class.
|
// class.
|
||||||
@ -35,7 +68,7 @@ angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda'])
|
|||||||
'$filter',
|
'$filter',
|
||||||
'Agenda',
|
'Agenda',
|
||||||
'AgendaTree',
|
'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.
|
// 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
|
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||||
// class.
|
// 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.
|
'TopicForm', // TODO: Remove this dependency. Use template hook for "New" and "Import" buttons.
|
||||||
'AgendaTree',
|
'AgendaTree',
|
||||||
'Projector',
|
'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
|
// Bind agenda tree to the scope
|
||||||
$scope.$watch(function () {
|
$scope.$watch(function () {
|
||||||
return Agenda.lastModified();
|
return Agenda.lastModified();
|
||||||
@ -110,6 +111,17 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
|||||||
$scope.agendaHasSubitems = true;
|
$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.alert = {};
|
||||||
|
|
||||||
$scope.sumDurations = function () {
|
$scope.sumDurations = function () {
|
||||||
@ -218,34 +230,78 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
|||||||
$scope.uncheckAll();
|
$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
|
// project agenda
|
||||||
$scope.projectAgenda = function (tree, id) {
|
$scope.projectAgenda = function (projectorId, tree, id) {
|
||||||
$http.post('/rest/core/projector/1/prune_elements/',
|
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}]);
|
[{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
|
// check if agenda is projected
|
||||||
$scope.isAgendaProjected = function (tree) {
|
$scope.isAgendaProjected = function (tree) {
|
||||||
// Returns true if there is a projector element with the name
|
// Returns true if there is a projector element with the name
|
||||||
// 'agenda/item-list'.
|
// 'agenda/item-list'.
|
||||||
var projector = Projector.get(1);
|
|
||||||
if (typeof projector === 'undefined') return false;
|
|
||||||
var self = this;
|
|
||||||
var predicate = function (element) {
|
var predicate = function (element) {
|
||||||
var value;
|
var value;
|
||||||
if (typeof tree === 'undefined') {
|
if (tree) {
|
||||||
// only main agenda items
|
|
||||||
value = element.name == 'agenda/item-list' &&
|
|
||||||
typeof element.id === 'undefined' &&
|
|
||||||
!element.tree;
|
|
||||||
} else {
|
|
||||||
// tree with all agenda items
|
// tree with all agenda items
|
||||||
value = element.name == 'agenda/item-list' &&
|
value = element.name == 'agenda/item-list' &&
|
||||||
typeof element.id === 'undefined' &&
|
typeof element.id === 'undefined' &&
|
||||||
element.tree;
|
element.tree;
|
||||||
|
} else {
|
||||||
|
// only main agenda items
|
||||||
|
value = element.name == 'agenda/item-list' &&
|
||||||
|
typeof element.id === 'undefined' &&
|
||||||
|
!element.tree;
|
||||||
}
|
}
|
||||||
return value;
|
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
|
// auto numbering of agenda items
|
||||||
$scope.autoNumbering = function() {
|
$scope.autoNumbering = function() {
|
||||||
@ -263,9 +319,24 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
|||||||
'Agenda',
|
'Agenda',
|
||||||
'User',
|
'User',
|
||||||
'item',
|
'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');
|
Agenda.bindOne(item.id, $scope, 'item');
|
||||||
User.bindAll({}, $scope, 'users');
|
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.speakerSelectBox = {};
|
||||||
$scope.alert = {};
|
$scope.alert = {};
|
||||||
$scope.speakers = $filter('orderBy')(item.speakers, 'weight');
|
$scope.speakers = $filter('orderBy')(item.speakers, 'weight');
|
||||||
@ -428,50 +499,69 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
|||||||
'$state',
|
'$state',
|
||||||
'$http',
|
'$http',
|
||||||
'Projector',
|
'Projector',
|
||||||
'Assignment', // TODO: Remove this after refactoring of data loading on start.
|
'ProjectionDefault',
|
||||||
'Topic', // TODO: Remove this after refactoring of data loading on start.
|
'Config',
|
||||||
'Motion', // TODO: Remove this after refactoring of data loading on start.
|
'CurrentListOfSpeakersItem',
|
||||||
'Agenda',
|
function($scope, $state, $http, Projector, ProjectionDefault, Config, CurrentListOfSpeakersItem) {
|
||||||
function($scope, $state, $http, Projector, Assignment, Topic, Motion, Agenda) {
|
// Watch for changes in the current list of speakers reference
|
||||||
$scope.$watch(
|
$scope.$watch(function () {
|
||||||
function() {
|
return Config.lastModified('projector_currentListOfSpeakers_reference');
|
||||||
return Projector.lastModified(1);
|
}, function () {
|
||||||
},
|
$scope.currentListOfSpeakersReference = $scope.config('projector_currentListOfSpeakers_reference');
|
||||||
function() {
|
$scope.updateCurrentListOfSpeakers();
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
|
$scope.$watch(function() {
|
||||||
|
return Projector.lastModified();
|
||||||
|
}, function() {
|
||||||
|
$scope.projectors = Projector.getAll();
|
||||||
|
$scope.updateCurrentListOfSpeakers();
|
||||||
});
|
});
|
||||||
break;
|
$scope.$watch(function () {
|
||||||
case 'topics/topic':
|
return Projector.lastModified();
|
||||||
Topic.find(element.id).then(function(topic) {
|
}, function () {
|
||||||
Topic.loadRelations(topic, 'agenda_item').then(function() {
|
var projectiondefault = ProjectionDefault.filter({name: 'current_list_of_speakers'})[0];
|
||||||
$scope.AgendaItem = topic.agenda_item;
|
if (projectiondefault) {
|
||||||
|
$scope.defaultProjectorId = projectiondefault.projector_id;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
break;
|
$scope.updateCurrentListOfSpeakers = function () {
|
||||||
case 'assignments/assignment':
|
var itemPromise = CurrentListOfSpeakersItem.getItem($scope.currentListOfSpeakersReference);
|
||||||
Assignment.find(element.id).then(function(assignment) {
|
if (itemPromise) {
|
||||||
Assignment.loadRelations(assignment, 'agenda_item').then(function() {
|
itemPromise.then(function(item) {
|
||||||
$scope.AgendaItem = assignment.agenda_item;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'agenda/list-of-speakers':
|
|
||||||
Agenda.find(element.id).then(function(item) {
|
|
||||||
$scope.AgendaItem = 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
|
// go to the list of speakers (management) of the currently
|
||||||
// displayed projector slide
|
// displayed projector slide
|
||||||
$scope.goToListOfSpeakers = function() {
|
$scope.goToListOfSpeakers = function() {
|
||||||
|
@ -1,11 +1,32 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="submenu">
|
<div class="submenu">
|
||||||
<button ng-click="isFullScreen = !isFullScreen"
|
<div class="form-inline">
|
||||||
class="btn btn-sm btn-default">
|
<div os-perms="core.can_manage_projector" class="btn-group" uib-dropdown
|
||||||
<i class="fa fa-expand fa-lg"></i>
|
uib-tooltip="{{ 'Projector' | translate }} {{ isCurrentLoSProjected() }}"
|
||||||
<translate>Fullscreen</translate>
|
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>
|
||||||
|
<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"
|
<button os-perms="agenda.can_manage"
|
||||||
ng-click="goToListOfSpeakers()"
|
ng-click="goToListOfSpeakers()"
|
||||||
class="btn btn-sm btn-default">
|
class="btn btn-sm btn-default">
|
||||||
@ -13,24 +34,17 @@
|
|||||||
<translate>Manage list</translate>
|
<translate>Manage list</translate>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<h1 translate>List of speakers</h1>
|
</div>
|
||||||
|
<h1 translate>Current list of speakers</h1>
|
||||||
<h2> {{ AgendaItem.getTitle() }}
|
<h2> {{ AgendaItem.getTitle() }}
|
||||||
<span class="slimlabel label label-danger ng-scope" style="" ng-if="AgendaItem.speaker_list_closed" translate>
|
<span class="slimlabel label label-danger ng-scope" style="" ng-if="AgendaItem.speaker_list_closed" translate>
|
||||||
Closed
|
Closed
|
||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content" ng-class="isFullScreen ? 'fullscreendiv' : 'details'"
|
<div class="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 -->
|
<!-- Last speakers -->
|
||||||
<p ng-repeat="speaker in lastSpeakers = (AgendaItem.speakers | filter: {end_time: '!!', begin_time: '!!'}) |
|
<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">
|
limitTo: config('agenda_show_last_speakers') : (lastSpeakers.length - config('agenda_show_last_speakers'))" class="lastSpeakers">
|
||||||
@ -44,5 +58,4 @@
|
|||||||
<li ng-repeat="speaker in AgendaItem.speakers | filter: {begin_time: null} | orderBy:'weight'">
|
<li ng-repeat="speaker in AgendaItem.speakers | filter: {begin_time: null} | orderBy:'weight'">
|
||||||
{{ speaker.user.get_full_name() }}
|
{{ speaker.user.get_full_name() }}
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,20 +10,37 @@
|
|||||||
{{ item.getContentResource().verboseName | translate }}
|
{{ item.getContentResource().verboseName | translate }}
|
||||||
</a>
|
</a>
|
||||||
<!-- project list of speakers -->
|
<!-- project list of speakers -->
|
||||||
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
<span class="btn-group" style="min-width:54px;" uib-dropdown
|
||||||
ng-class="{ 'btn-primary': item.isListOfSpeakersProjected() }"
|
uib-tooltip="{{ 'Projektor' | translate }} {{ item.isListOfSpeakersProjected() }}"
|
||||||
ng-click="item.projectListOfSpeakers()">
|
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>
|
<i class="fa fa-video-camera"></i>
|
||||||
<translate>List of speakers</translate>
|
<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>
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
<!-- project -->
|
<!-- project -->
|
||||||
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
<projector-button model="item" default-projector-id="defaultProjectorItemId"
|
||||||
ng-class="{ 'btn-primary': item.isProjected() }"
|
content="{{ item.getContentResource().verboseName | translate }}">
|
||||||
ng-click="item.project()"
|
</projector-button>
|
||||||
title="{{ 'Project item' | translate }}">
|
|
||||||
<i class="fa fa-video-camera"></i>
|
|
||||||
{{ item.getContentResource().verboseName | translate }}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<h1>{{ item.getTitle() }}</h1>
|
<h1>{{ item.getTitle() }}</h1>
|
||||||
<h2>
|
<h2>
|
||||||
|
@ -38,26 +38,37 @@
|
|||||||
<translate>Select ...</translate>
|
<translate>Select ...</translate>
|
||||||
</button>
|
</button>
|
||||||
<!-- project agenda button -->
|
<!-- project agenda button -->
|
||||||
<div os-perms="core.can_manage_projector" class="btn-group" uib-dropdown>
|
<div class="btn-group" uib-dropdown
|
||||||
<button
|
uib-tooltip="{{ 'Projector' | translate }} {{ isAgendaProjected(mainListTree) }}"
|
||||||
id="project-agenda-button"
|
tooltip-enable="isAgendaProjected(mainListTree) > 0"
|
||||||
type="button"
|
os-perms="core.can_manage_projector">
|
||||||
class="btn btn-default"
|
<button type="button" class="btn btn-default"
|
||||||
title="{{ 'Project agenda' | translate }}"
|
title="{{ 'Project agenda' | translate }}"
|
||||||
ng-click="projectAgenda(tree=true)"
|
ng-click="projectAgenda(defaultProjectorId_all_items, mainListTree)"
|
||||||
ng-class="{ 'btn-primary': isAgendaProjected(tree=true) }">
|
ng-class="{ 'btn-primary': isAgendaProjected(mainListTree) > 0 && isAgendaProjected(mainListTree) == defaultProjectorId_all_items}">
|
||||||
<i class="fa fa-video-camera"></i>
|
<i class="fa fa-video-camera"></i>
|
||||||
<translate>Agenda</translate>
|
<translate>Agenda</translate>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-default"
|
<button type="button" class="btn btn-default" uib-dropdown-toggle
|
||||||
ng-if="agendaHasSubitems"
|
ng-class="{ 'btn-primary': isAgendaProjected(mainListTree) > 0 && isAgendaProjected(mainListTree) != defaultProjectorId_all_items}">
|
||||||
ng-class="{ 'btn-primary': isAgendaProjected() }"
|
|
||||||
uib-dropdown-toggle>
|
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="project-agenda-button">
|
<ul class="dropdown-menu" role="menu" aria-labelledby="split-button">
|
||||||
<li role="menuitem"><a href="" ng-click="projectAgenda(tree=true)" translate>All agenda items (Default)</a>
|
<li role="menuitem" ng-show="agendaHasSubitems">
|
||||||
<li role="menuitem"><a href="" ng-click="projectAgenda(tree=false)" translate>Only main agenda items</a>
|
<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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<!-- auto numbering button -->
|
<!-- auto numbering button -->
|
||||||
@ -71,7 +82,7 @@
|
|||||||
<a os-perms="users.can_see_name" class="btn btn-default"
|
<a os-perms="users.can_see_name" class="btn btn-default"
|
||||||
ui-sref="agenda.current-list-of-speakers">
|
ui-sref="agenda.current-list-of-speakers">
|
||||||
<i class="fa fa-microphone"></i>
|
<i class="fa fa-microphone"></i>
|
||||||
<translate>List of speakers</translate>
|
<translate>Current list of speakers</translate>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -150,25 +161,37 @@
|
|||||||
ng-class="{ 'activeline': item.isProjected(), 'selected': item.selected, 'hiddenrow': item.is_hidden}">
|
ng-class="{ 'activeline': item.isProjected(), 'selected': item.selected, 'hiddenrow': item.is_hidden}">
|
||||||
<!-- projector column -->
|
<!-- projector column -->
|
||||||
<td ng-show="!isDeleteMode" os-perms="core.can_manage_projector">
|
<td ng-show="!isDeleteMode" os-perms="core.can_manage_projector">
|
||||||
<div class="btn-group" style="width:54px;" uib-dropdown>
|
<div class="btn-group" style="min-width:54px;" uib-dropdown
|
||||||
<button os-perms="core.can_manage_projector"
|
uib-tooltip="{{ 'Projector' | translate }} {{ item.isProjected(item.tree) }}"
|
||||||
id="project-item"
|
tooltip-enable="item.isProjected(item.tree) > 0">
|
||||||
type="button"
|
<button class="btn btn-default btn-sm"
|
||||||
class="btn btn-default btn-sm"
|
|
||||||
title="{{ 'Project item' | translate }}"
|
title="{{ 'Project item' | translate }}"
|
||||||
ng-click="item.project()"
|
ng-click="item.project(getProjectionDefault(item), item.tree)"
|
||||||
ng-class="{ 'btn-primary': item.isProjected() }">
|
ng-class="{ 'btn-primary': item.isProjected(item.tree) > 0 && item.isProjected(item.tree) == getProjectionDefault(item)}">
|
||||||
<i class="fa fa-video-camera"></i>
|
<i class="fa fa-video-camera"></i>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-default btn-sm slimDropDown"
|
<button type="button" class="btn btn-default btn-sm slimDropDown"
|
||||||
ng-if="item.hasSubitems(items)"
|
ng-class="{ 'btn-primary': item.isProjected(item.tree) > 0 && item.isProjected(item.tree) != getProjectionDefault(item)}"
|
||||||
ng-class="{ 'btn-primary': item.isProjected(list=true) }"
|
ng-show="item.hasSubitems(items) || projectors.length > 1"
|
||||||
uib-dropdown-toggle>
|
uib-dropdown-toggle>
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="project-item">
|
<ul class="dropdown-menu" role="menu" aria-labelledby="split-button">
|
||||||
<li role="menuitem"><a href="" ng-click="item.project()" translate>Project item (Default)</a>
|
<li role="menuitem" ng-show="item.hasSubitems(items)">
|
||||||
<li role="menuitem"><a href="" ng-click="projectAgenda(tree=true, id=item.id)" translate>Project all sub items</a>
|
<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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<!-- delete selection column -->
|
<!-- 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,19 +267,22 @@ angular.module('OpenSlidesApp.assignments', [])
|
|||||||
return "Election";
|
return "Election";
|
||||||
},
|
},
|
||||||
// override project function of jsDataModel factory
|
// override project function of jsDataModel factory
|
||||||
project: function (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(
|
return $http.post(
|
||||||
'/rest/core/projector/1/prune_elements/',
|
'/rest/core/projector/' + projectorId + '/prune_elements/',
|
||||||
[{name: 'assignments/assignment', id: this.id, poll: poll_id}]
|
[{name: 'assignments/assignment', id: this.id, poll: pollId}]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// override isProjected function of jsDataModel factory
|
// override isProjected function of jsDataModel factory
|
||||||
isProjected: function (poll_id) {
|
isProjected: function (poll_id) {
|
||||||
// Returns true if there is a projector element with the name
|
// Returns the id of the last projector found with an element
|
||||||
// 'assignments/assignment'.
|
// with the name 'assignments/assignment'.
|
||||||
var projector = Projector.get(1);
|
|
||||||
var isProjected;
|
|
||||||
if (typeof projector !== 'undefined') {
|
|
||||||
var self = this;
|
var self = this;
|
||||||
var predicate = function (element) {
|
var predicate = function (element) {
|
||||||
var value;
|
var value;
|
||||||
@ -299,10 +302,12 @@ angular.module('OpenSlidesApp.assignments', [])
|
|||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
isProjected = typeof _.findKey(projector.elements, predicate) === 'string';
|
var isProjected = 0;
|
||||||
} else {
|
Projector.getAll().forEach(function (projector) {
|
||||||
isProjected = false;
|
if (typeof _.findKey(projector.elements, predicate) === 'string') {
|
||||||
|
isProjected = projector.id;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
return isProjected;
|
return isProjected;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -233,9 +233,19 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
|||||||
'Tag',
|
'Tag',
|
||||||
'Agenda',
|
'Agenda',
|
||||||
'phases',
|
'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');
|
Assignment.bindAll({}, $scope, 'assignments');
|
||||||
Tag.bindAll({}, $scope, 'tags');
|
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.phases = phases;
|
||||||
$scope.alert = {};
|
$scope.alert = {};
|
||||||
|
|
||||||
@ -339,10 +349,21 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
|||||||
'User',
|
'User',
|
||||||
'assignment',
|
'assignment',
|
||||||
'phases',
|
'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');
|
User.bindAll({}, $scope, 'users');
|
||||||
Assignment.bindOne(assignment.id, $scope, 'assignment');
|
Assignment.bindOne(assignment.id, $scope, 'assignment');
|
||||||
Assignment.loadRelations(assignment, 'agenda_item');
|
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.candidateSelectBox = {};
|
||||||
$scope.phases = phases;
|
$scope.phases = phases;
|
||||||
$scope.alert = {};
|
$scope.alert = {};
|
||||||
|
@ -16,12 +16,8 @@
|
|||||||
<translate>List of speakers</translate>
|
<translate>List of speakers</translate>
|
||||||
</a>
|
</a>
|
||||||
<!-- project -->
|
<!-- project -->
|
||||||
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
<projector-button model="assignment", default-projector-id="defaultProjectorId">
|
||||||
ng-class="{ 'btn-primary': assignment.isProjected() }"
|
</projector-button>
|
||||||
ng-click="assignment.project()"
|
|
||||||
title="{{ 'Project election' | translate }}">
|
|
||||||
<i class="fa fa-video-camera"></i>
|
|
||||||
</a>
|
|
||||||
<!-- edit -->
|
<!-- edit -->
|
||||||
<a os-perms="assignments.can_manage" ng-click="openDialog(assignment)"
|
<a os-perms="assignments.can_manage" ng-click="openDialog(assignment)"
|
||||||
class="btn btn-default btn-sm"
|
class="btn btn-default btn-sm"
|
||||||
@ -155,13 +151,9 @@
|
|||||||
3. <translate>Published</translate>
|
3. <translate>Published</translate>
|
||||||
</button>
|
</button>
|
||||||
<i class="fa fa-arrow-right"></i>
|
<i class="fa fa-arrow-right"></i>
|
||||||
<button os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
<projector-button model="assignment" default-projector-id="defaultProjectorId"
|
||||||
ng-class="{ 'btn-primary': assignment.isProjected(poll.id) }"
|
additional-id="poll.id" content="4. {{ 'Project' | translate }}">
|
||||||
ng-click="assignment.project(poll.id)"
|
</projector-button>
|
||||||
title="{{ 'Project ballot' | translate }}">
|
|
||||||
<i class="fa fa-video-camera"></i>
|
|
||||||
4. <translate>Project</translate>
|
|
||||||
</button>
|
|
||||||
<a class="btn btn-danger btn-sm"
|
<a class="btn btn-danger btn-sm"
|
||||||
ng-bootbox-confirm="{{ 'Are you sure you want to delete this ballot?' | translate }}"
|
ng-bootbox-confirm="{{ 'Are you sure you want to delete this ballot?' | translate }}"
|
||||||
ng-bootbox-confirm-action="deleteBallot(poll)">
|
ng-bootbox-confirm-action="deleteBallot(poll)">
|
||||||
|
@ -121,12 +121,8 @@
|
|||||||
|
|
||||||
<!-- projector -->
|
<!-- projector -->
|
||||||
<td ng-show="!isDeleteMode" os-perms="core.can_manage_projector">
|
<td ng-show="!isDeleteMode" os-perms="core.can_manage_projector">
|
||||||
<a class="btn btn-default btn-sm"
|
<projector-button model="assignment" default-projector-id="defaultProjectorId">
|
||||||
ng-class="{ 'btn-primary': assignment.isProjected() }"
|
</projector-button>
|
||||||
ng-click="assignment.project()"
|
|
||||||
title="{{ 'Project election' | translate }}">
|
|
||||||
<i class="fa fa-video-camera"></i>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<!-- delete selection -->
|
<!-- delete selection -->
|
||||||
<td ng-show="isDeleteMode" os-perms="assignments.can_manage" class="deleteColumn">
|
<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.rest_api import router
|
||||||
from openslides.utils.search import index_add_instance, index_del_instance
|
from openslides.utils.search import index_add_instance, index_del_instance
|
||||||
from .config_variables import get_config_variables
|
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 (
|
from .views import (
|
||||||
ChatMessageViewSet,
|
ChatMessageViewSet,
|
||||||
ConfigViewSet,
|
ConfigViewSet,
|
||||||
@ -35,6 +35,9 @@ class CoreAppConfig(AppConfig):
|
|||||||
post_permission_creation.connect(
|
post_permission_creation.connect(
|
||||||
delete_django_app_permissions,
|
delete_django_app_permissions,
|
||||||
dispatch_uid='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.
|
# Register viewsets.
|
||||||
router.register(self.get_model('Projector').get_collection_string(), ProjectorViewSet)
|
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 .exceptions import ConfigError, ConfigNotFound
|
||||||
from .models import ConfigStore
|
from .models import ConfigStore
|
||||||
|
|
||||||
# remove resolution when changing to multiprojector
|
|
||||||
INPUT_TYPE_MAPPING = {
|
INPUT_TYPE_MAPPING = {
|
||||||
'string': str,
|
'string': str,
|
||||||
'text': str,
|
'text': str,
|
||||||
'integer': int,
|
'integer': int,
|
||||||
'boolean': bool,
|
'boolean': bool,
|
||||||
'choice': str,
|
'choice': str,
|
||||||
'colorpicker': str,
|
|
||||||
'comments': list,
|
'comments': list,
|
||||||
'resolution': dict}
|
'colorpicker': str}
|
||||||
|
|
||||||
|
|
||||||
class ConfigHandler:
|
class ConfigHandler:
|
||||||
@ -89,16 +87,6 @@ class ConfigHandler:
|
|||||||
except DjangoValidationError as e:
|
except DjangoValidationError as e:
|
||||||
raise ConfigError(e.messages[0])
|
raise ConfigError(e.messages[0])
|
||||||
|
|
||||||
# remove this block when changing to multiprojector
|
|
||||||
if config_variable.input_type == 'resolution':
|
|
||||||
if value.get('width') is None or value.get('height') is None:
|
|
||||||
raise ConfigError(_('A width and a height have to be given.'))
|
|
||||||
if not isinstance(value['width'], int) or not isinstance(value['height'], int):
|
|
||||||
raise ConfigError(_('Data has to be integers.'))
|
|
||||||
if (value['width'] < 800 or value['width'] > 3840 or
|
|
||||||
value['height'] < 600 or value['height'] > 2160):
|
|
||||||
raise ConfigError(_('The Resolution have to be between 800x600 and 3840x2160.'))
|
|
||||||
|
|
||||||
if config_variable.input_type == 'comments':
|
if config_variable.input_type == 'comments':
|
||||||
if not isinstance(value, list):
|
if not isinstance(value, list):
|
||||||
raise ConfigError(_('motions_comments has to be a list.'))
|
raise ConfigError(_('motions_comments has to be a list.'))
|
||||||
|
@ -157,11 +157,28 @@ def get_config_variables():
|
|||||||
weight=185,
|
weight=185,
|
||||||
group='Projector')
|
group='Projector')
|
||||||
|
|
||||||
# set the resolution for one projector. It can be removed with the multiprojector feature.
|
|
||||||
yield ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='projector_resolution',
|
name='projector_blank_color',
|
||||||
default_value={'width': 1024, 'height': 768},
|
default_value='#FFFFFF',
|
||||||
input_type='resolution',
|
input_type='colorpicker',
|
||||||
label='Projector Resolution',
|
label='Color for blanked projector',
|
||||||
weight=200,
|
weight=190,
|
||||||
group='Projector')
|
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)
|
scroll = models.IntegerField(default=0)
|
||||||
|
|
||||||
# currently unused, but important for the multiprojector.
|
|
||||||
width = models.PositiveIntegerField(default=1024)
|
width = models.PositiveIntegerField(default=1024)
|
||||||
|
|
||||||
height = models.PositiveIntegerField(default=768)
|
height = models.PositiveIntegerField(default=768)
|
||||||
|
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
unique=True,
|
||||||
|
blank=True)
|
||||||
|
|
||||||
|
blank = models.BooleanField(
|
||||||
|
blank=False,
|
||||||
|
default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""
|
"""
|
||||||
Contains general permissions that can not be placed in a specific app.
|
Contains general permissions that can not be placed in a specific app.
|
||||||
@ -169,6 +177,33 @@ class Projector(RESTModelMixin, models.Model):
|
|||||||
return result
|
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):
|
class Tag(RESTModelMixin, models.Model):
|
||||||
"""
|
"""
|
||||||
Model for tags. This tags can be used for other models like agenda items,
|
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 django.utils.timezone import now
|
||||||
|
|
||||||
from ..utils.projector import ProjectorElement
|
from ..utils.projector import ProjectorElement
|
||||||
@ -20,7 +22,7 @@ class Countdown(ProjectorElement):
|
|||||||
To start the countdown write into the config field:
|
To start the countdown write into the config field:
|
||||||
|
|
||||||
{
|
{
|
||||||
"status": "running",
|
"running": True,
|
||||||
"countdown_time": <timestamp>,
|
"countdown_time": <timestamp>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,10 +32,10 @@ class Countdown(ProjectorElement):
|
|||||||
|
|
||||||
To stop the countdown set the countdown time to the current value of the
|
To stop the countdown set the countdown time to the current value of the
|
||||||
countdown (countdown_time = countdown_time - now + serverTimeOffset)
|
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
|
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
|
Do not forget to send values for additional keywords like "stable" if
|
||||||
you do not want to use the default.
|
you do not want to use the default.
|
||||||
@ -63,28 +65,56 @@ class Countdown(ProjectorElement):
|
|||||||
"""
|
"""
|
||||||
if not isinstance(config_data.get('countdown_time'), (int, float)):
|
if not isinstance(config_data.get('countdown_time'), (int, float)):
|
||||||
raise ProjectorException('Invalid countdown time. Use integer or float.')
|
raise ProjectorException('Invalid countdown time. Use integer or float.')
|
||||||
if config_data.get('status') not in ('running', 'stop'):
|
if not isinstance(config_data.get('running'), bool):
|
||||||
raise ProjectorException("Invalid status. Use 'running' or 'stop'.")
|
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):
|
if config_data.get('default') is not None and not isinstance(config_data.get('default'), int):
|
||||||
raise ProjectorException('Invalid default value. Use integer.')
|
raise ProjectorException('Invalid default value. Use integer.')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def control(cls, action, projector_id=1, index=0):
|
def control(cls, action):
|
||||||
"""
|
|
||||||
Starts, stops or resets the countdown with the given index on the
|
|
||||||
given projector.
|
|
||||||
|
|
||||||
Action must be 'start', 'stop' or 'reset'.
|
|
||||||
"""
|
|
||||||
if action not in ('start', 'stop', 'reset'):
|
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)
|
# 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 = {}
|
projector_config = {}
|
||||||
found = False
|
found = False
|
||||||
for key, value in projector_instance.config.items():
|
for key, value in projector.config.items():
|
||||||
if value['name'] == cls.name:
|
if value['name'] == cls.name and value['index'] == lowest_index:
|
||||||
if index == 0:
|
|
||||||
try:
|
try:
|
||||||
cls.validate_config(value)
|
cls.validate_config(value)
|
||||||
except ProjectorException:
|
except ProjectorException:
|
||||||
@ -92,21 +122,19 @@ class Countdown(ProjectorElement):
|
|||||||
# The variable found remains False.
|
# The variable found remains False.
|
||||||
break
|
break
|
||||||
found = True
|
found = True
|
||||||
if action == 'start' and value['status'] == 'stop':
|
if action == 'start':
|
||||||
value['status'] = 'running'
|
value['running'] = True
|
||||||
value['countdown_time'] = now().timestamp() + value['countdown_time']
|
value['countdown_time'] = now().timestamp() + value['default_time']
|
||||||
elif action == 'stop' and value['status'] == 'running':
|
elif action == 'stop' and value['running']:
|
||||||
value['status'] = 'stop'
|
value['running'] = False
|
||||||
value['countdown_time'] = value['countdown_time'] - now().timestamp()
|
value['countdown_time'] = value['countdown_time'] - now().timestamp()
|
||||||
elif action == 'reset':
|
elif action == 'reset':
|
||||||
value['status'] = 'stop'
|
value['running'] = False
|
||||||
value['countdown_time'] = value.get('default', config['projector_default_countdown'])
|
value['countdown_time'] = value['default_time']
|
||||||
else:
|
|
||||||
index += -1
|
|
||||||
projector_config[key] = value
|
projector_config[key] = value
|
||||||
if found:
|
if found:
|
||||||
projector_instance.config = projector_config
|
projector.config = projector_config
|
||||||
projector_instance.save()
|
projector.save()
|
||||||
|
|
||||||
|
|
||||||
class Message(ProjectorElement):
|
class Message(ProjectorElement):
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from openslides.utils.rest_api import Field, ModelSerializer, ValidationError
|
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):
|
class JSONSerializerField(Field):
|
||||||
@ -22,15 +22,26 @@ class JSONSerializerField(Field):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectionDefaultSerializer(ModelSerializer):
|
||||||
|
"""
|
||||||
|
Serializer for core.models.ProjectionDefault objects.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = ProjectionDefault
|
||||||
|
fields = ('id', 'name', 'display_name', 'projector', )
|
||||||
|
|
||||||
|
|
||||||
class ProjectorSerializer(ModelSerializer):
|
class ProjectorSerializer(ModelSerializer):
|
||||||
"""
|
"""
|
||||||
Serializer for core.models.Projector objects.
|
Serializer for core.models.Projector objects.
|
||||||
"""
|
"""
|
||||||
config = JSONSerializerField(write_only=True)
|
config = JSONSerializerField(write_only=True)
|
||||||
|
projectiondefaults = ProjectionDefaultSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Projector
|
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):
|
class TagSerializer(ModelSerializer):
|
||||||
|
@ -3,6 +3,8 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.dispatch import Signal
|
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
|
# 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
|
# after post_migrate sending and creating all Permission objects. Don't use it
|
||||||
# for other things than dealing with Permission objects.
|
# for other things than dealing with Permission objects.
|
||||||
@ -20,3 +22,56 @@ def delete_django_app_permissions(sender, **kwargs):
|
|||||||
Q(app_label='sessions'))
|
Q(app_label='sessions'))
|
||||||
for permission in Permission.objects.filter(content_type__in=contenttypes):
|
for permission in Permission.objects.filter(content_type__in=contenttypes):
|
||||||
permission.delete()
|
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;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.col1 .header .submenu > div {
|
||||||
|
display: inline-block;
|
||||||
|
float: left;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.col1 .meta .title {
|
.col1 .meta .title {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -331,15 +337,6 @@ img {
|
|||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .resolution can be removed with the multiprojector, but maybe
|
|
||||||
* it could be reused for the settings in the projectormanage-view */
|
|
||||||
.col1 .input-group .resolution {
|
|
||||||
float: none;
|
|
||||||
display: inline-block;
|
|
||||||
width: 80px;
|
|
||||||
border-radius: 4px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Toolbar to save motion in inline editing mode */
|
/* Toolbar to save motion in inline editing mode */
|
||||||
.motion-save-toolbar {
|
.motion-save-toolbar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -586,7 +583,6 @@ img {
|
|||||||
|
|
||||||
#content .col2 .section a:hover {
|
#content .col2 .section a:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
opacity: 0.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#content .toggle-icon {
|
#content .toggle-icon {
|
||||||
@ -652,20 +648,20 @@ img {
|
|||||||
color: #CC0000;
|
color: #CC0000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col2 .notNull {
|
.notNull {
|
||||||
color: red;
|
color: red;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* iframe for live view */
|
/* iframe for live view */
|
||||||
.col2 #iframe {
|
.iframe {
|
||||||
-moz-transform-origin: 0 0;
|
-moz-transform-origin: 0 0;
|
||||||
-webkit-transform-origin: 0 0;
|
-webkit-transform-origin: 0 0;
|
||||||
-o-transform-origin: 0 0;
|
-o-transform-origin: 0 0;
|
||||||
transform-origin: 0 0 0;
|
transform-origin: 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col2 #iframewrapper {
|
.iframewrapper {
|
||||||
width: 256px;
|
width: 256px;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -673,7 +669,7 @@ img {
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col2 #iframeoverlay {
|
.iframeoverlay {
|
||||||
width: 256px;
|
width: 256px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
@ -782,6 +778,77 @@ img {
|
|||||||
margin-bottom: 5px
|
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 **/
|
||||||
#footer {
|
#footer {
|
||||||
float: left;
|
float: left;
|
||||||
@ -988,21 +1055,6 @@ img {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* List of speakers view */
|
/* 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 {
|
.fullscreendiv-title {
|
||||||
border-bottom: 5px solid #d3d3d3;
|
border-bottom: 5px solid #d3d3d3;
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
|
@ -3,7 +3,11 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
body{
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
font-size: 20px !important;
|
font-size: 20px !important;
|
||||||
line-height: 24px !important;
|
line-height: 24px !important;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -34,7 +38,7 @@ body{
|
|||||||
transform-origin: 0 0 0;
|
transform-origin: 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pContainer #iframewrapper {
|
.pContainer #iframewrapper, .pContainer .error {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
@ -49,6 +53,12 @@ body{
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pContainer .error > p {
|
||||||
|
color: #f00;
|
||||||
|
font-size: 150%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
/*** HEADER ***/
|
/*** HEADER ***/
|
||||||
#header {
|
#header {
|
||||||
box-shadow: 0 0 7px rgba(0,0,0,0.6);
|
box-shadow: 0 0 7px rgba(0,0,0,0.6);
|
||||||
@ -278,6 +288,10 @@ hr {
|
|||||||
line-height: normal !important;
|
line-height: normal !important;
|
||||||
z-index: 301;
|
z-index: 301;
|
||||||
}
|
}
|
||||||
|
.identify {
|
||||||
|
background-color: #D9F8C4;
|
||||||
|
z-index: 400;
|
||||||
|
}
|
||||||
|
|
||||||
/*** PDF presentation ***/
|
/*** PDF presentation ***/
|
||||||
.rotate0 {
|
.rotate0 {
|
||||||
@ -379,7 +393,6 @@ tr.elected td {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*** Line numbers ***/
|
/*** Line numbers ***/
|
||||||
.motion-text .highlight {
|
.motion-text .highlight {
|
||||||
background-color: #ff0;
|
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', [
|
.factory('autoupdate', [
|
||||||
'DS',
|
'DS',
|
||||||
'$rootScope',
|
'$rootScope',
|
||||||
'REALM',
|
'REALM',
|
||||||
function (DS, $rootScope, REALM) {
|
'ProjectorID',
|
||||||
|
function (DS, $rootScope, REALM, ProjectorID) {
|
||||||
var socket = null;
|
var socket = null;
|
||||||
var recInterval = null;
|
var recInterval = null;
|
||||||
$rootScope.connected = false;
|
$rootScope.connected = false;
|
||||||
@ -46,8 +55,7 @@ angular.module('OpenSlidesApp.core', [
|
|||||||
if (REALM == 'site') {
|
if (REALM == 'site') {
|
||||||
websocketPath = '/ws/site/';
|
websocketPath = '/ws/site/';
|
||||||
} else if (REALM == 'projector') {
|
} else if (REALM == 'projector') {
|
||||||
// TODO: At the moment there is only one projector. Find out which one is requested
|
websocketPath = '/ws/projector/' + ProjectorID() + '/';
|
||||||
websocketPath = '/ws/projector/1/';
|
|
||||||
} else {
|
} else {
|
||||||
console.error('The constant REALM is not set properly.');
|
console.error('The constant REALM is not set properly.');
|
||||||
}
|
}
|
||||||
@ -246,7 +254,7 @@ angular.module('OpenSlidesApp.core', [
|
|||||||
return function () {
|
return function () {
|
||||||
Config.findAll();
|
Config.findAll();
|
||||||
|
|
||||||
// Loads all projector data
|
// Loads all projector data and the projectiondefaults
|
||||||
Projector.findAll();
|
Projector.findAll();
|
||||||
|
|
||||||
// Loads all chat messages data and their user_ids
|
// 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', [
|
.factory('jsDataModel', [
|
||||||
'$http',
|
'$http',
|
||||||
'Projector',
|
'Projector',
|
||||||
function($http, Projector) {
|
function($http, Projector) {
|
||||||
var BaseModel = function() {};
|
var BaseModel = function() {};
|
||||||
BaseModel.prototype.project = function() {
|
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(
|
return $http.post(
|
||||||
'/rest/core/projector/1/prune_elements/',
|
'/rest/core/projector/' + projectorId + '/prune_elements/',
|
||||||
[{name: this.getResourceName(), id: this.id}]
|
[{name: this.getResourceName(), id: this.id}]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
BaseModel.prototype.isProjected = function() {
|
BaseModel.prototype.isProjected = function() {
|
||||||
// Returns true if there is a projector element with the same
|
// Returns the projector id if there is a projector element
|
||||||
// name and the same id.
|
// with the same name and the same id. Else returns 0.
|
||||||
var projector = Projector.get(1);
|
// Attention: if this element is projected multiple times, only the
|
||||||
var isProjected;
|
// id of the last projector is returned.
|
||||||
if (typeof projector !== 'undefined') {
|
|
||||||
var self = this;
|
var self = this;
|
||||||
var predicate = function (element) {
|
var predicate = function (element) {
|
||||||
return element.name == self.getResourceName() &&
|
return element.name == self.getResourceName() &&
|
||||||
typeof element.id !== 'undefined' &&
|
typeof element.id !== 'undefined' &&
|
||||||
element.id == self.id;
|
element.id == self.id;
|
||||||
};
|
};
|
||||||
isProjected = typeof _.findKey(projector.elements, predicate) === 'string';
|
var isProjectedId = 0;
|
||||||
} else {
|
Projector.getAll().forEach(function (projector) {
|
||||||
isProjected = false;
|
if (typeof _.findKey(projector.elements, predicate) === 'string') {
|
||||||
|
isProjectedId = projector.id;
|
||||||
}
|
}
|
||||||
return isProjected;
|
});
|
||||||
|
return isProjectedId;
|
||||||
};
|
};
|
||||||
return BaseModel;
|
return BaseModel;
|
||||||
}
|
}
|
||||||
@ -396,10 +461,74 @@ angular.module('OpenSlidesApp.core', [
|
|||||||
*/
|
*/
|
||||||
.factory('Projector', [
|
.factory('Projector', [
|
||||||
'DS',
|
'DS',
|
||||||
function(DS) {
|
'$http',
|
||||||
|
'Config',
|
||||||
|
function(DS, $http, Config) {
|
||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: 'core/projector',
|
name: 'core/projector',
|
||||||
onConflict: 'replace',
|
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',
|
'ChatMessage',
|
||||||
'Config',
|
'Config',
|
||||||
'Projector',
|
'Projector',
|
||||||
|
'ProjectionDefault',
|
||||||
'Tag',
|
'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
|
// Projector Container Controller
|
||||||
.controller('ProjectorContainerCtrl', [
|
.controller('ProjectorContainerCtrl', [
|
||||||
'$scope',
|
'$scope',
|
||||||
'Config',
|
'$location',
|
||||||
'loadGlobalData',
|
'loadGlobalData',
|
||||||
function($scope, Config, loadGlobalData) {
|
'Projector',
|
||||||
|
'ProjectorID',
|
||||||
|
function($scope, $location, loadGlobalData, Projector, ProjectorID) {
|
||||||
loadGlobalData();
|
loadGlobalData();
|
||||||
// watch for changes in Config
|
|
||||||
var last_conf;
|
$scope.projector_id = ProjectorID();
|
||||||
|
$scope.error = '';
|
||||||
|
|
||||||
|
// watch for changes in Projector
|
||||||
$scope.$watch(function () {
|
$scope.$watch(function () {
|
||||||
return Config.lastModified();
|
return Projector.lastModified($scope.projector_id);
|
||||||
}, function () {
|
}, function () {
|
||||||
// With multiprojector, get the resolution from Prjector.get(pk).{width; height}
|
Projector.find($scope.projector_id).then(function (projector) {
|
||||||
if (typeof $scope.config === 'function') {
|
$scope.projectorWidth = projector.width;
|
||||||
var conf = $scope.config('projector_resolution');
|
$scope.projectorHeight = projector.height;
|
||||||
if(!last_conf || last_conf.width != conf.width || last_conf.height != conf.height) {
|
|
||||||
last_conf = conf;
|
|
||||||
$scope.projectorWidth = conf.width;
|
|
||||||
$scope.projectorHeight = conf.height;
|
|
||||||
$scope.recalculateIframe();
|
$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
|
// recalculate the actual Iframesize and scale
|
||||||
@ -114,16 +120,17 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
|||||||
|
|
||||||
.controller('ProjectorCtrl', [
|
.controller('ProjectorCtrl', [
|
||||||
'$scope',
|
'$scope',
|
||||||
|
'$location',
|
||||||
'Projector',
|
'Projector',
|
||||||
'slides',
|
'slides',
|
||||||
function($scope, Projector, slides) {
|
'Config',
|
||||||
$scope.$watch(function () {
|
'ProjectorID',
|
||||||
// TODO: Use the current projector. At the moment there is only one.
|
function($scope, $location, Projector, slides, Config, ProjectorID) {
|
||||||
return Projector.lastModified(1);
|
var projector_id = ProjectorID();
|
||||||
}, function () {
|
|
||||||
// TODO: Use the current projector. At the moment there is only one
|
$scope.broadcast = 0;
|
||||||
var projector = Projector.get(1);
|
|
||||||
if (projector) {
|
var setElements = function (projector) {
|
||||||
$scope.elements = [];
|
$scope.elements = [];
|
||||||
_.forEach(slides.getElements(projector), function(element) {
|
_.forEach(slides.getElements(projector), function(element) {
|
||||||
if (!element.error) {
|
if (!element.error) {
|
||||||
@ -132,9 +139,65 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
|||||||
console.error("Error for slide " + element.name + ": " + element.error);
|
console.error("Error for slide " + element.name + ": " + element.error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 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;
|
$scope.$watch(function () {
|
||||||
|
return Projector.lastModified(projector_id);
|
||||||
|
}, function () {
|
||||||
|
$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 = [];
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||||
// class.
|
// class.
|
||||||
$scope.seconds = Math.floor( $scope.element.countdown_time - Date.now() / 1000 + $scope.serverOffset );
|
$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.visible = $scope.element.visible;
|
||||||
|
$scope.selected = $scope.element.selected;
|
||||||
$scope.index = $scope.element.index;
|
$scope.index = $scope.element.index;
|
||||||
$scope.description = $scope.element.description;
|
$scope.description = $scope.element.description;
|
||||||
// start interval timer if countdown status is running
|
// start interval timer if countdown status is running
|
||||||
var interval;
|
var interval;
|
||||||
if ($scope.status == "running") {
|
if ($scope.running) {
|
||||||
interval = $interval( function() {
|
interval = $interval( function() {
|
||||||
$scope.seconds = Math.floor( $scope.element.countdown_time - Date.now() / 1000 + $scope.serverOffset );
|
$scope.seconds = Math.floor( $scope.element.countdown_time - Date.now() / 1000 + $scope.serverOffset );
|
||||||
}, 1000);
|
}, 1000);
|
||||||
@ -186,6 +250,8 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
|||||||
// class.
|
// class.
|
||||||
$scope.message = $scope.element.message;
|
$scope.message = $scope.element.message;
|
||||||
$scope.visible = $scope.element.visible;
|
$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'
|
templateUrl: 'static/templates/home.html'
|
||||||
})
|
})
|
||||||
.state('projector', {
|
.state('projector', {
|
||||||
url: '/projector',
|
url: '/projector/{id:int}',
|
||||||
templateUrl: 'static/templates/projector-container.html',
|
templateUrl: 'static/templates/projector-container.html',
|
||||||
data: {extern: true},
|
data: {extern: true},
|
||||||
onEnter: function($window) {
|
onEnter: function($window) {
|
||||||
@ -725,13 +725,18 @@ angular.module('OpenSlidesApp.core.site', [
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('real-projector', {
|
.state('real-projector', {
|
||||||
url: '/real-projector',
|
url: '/real-projector/{id:int}',
|
||||||
templateUrl: 'static/templates/projector.html',
|
templateUrl: 'static/templates/projector.html',
|
||||||
data: {extern: true},
|
data: {extern: true},
|
||||||
onEnter: function($window) {
|
onEnter: function($window) {
|
||||||
$window.location.href = this.url;
|
$window.location.href = this.url;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.state('manage-projectors', {
|
||||||
|
url: '/manage-projectors',
|
||||||
|
templateUrl: 'static/templates/core/manage-projectors.html',
|
||||||
|
controller: 'ManageProjectorsCtrl'
|
||||||
|
})
|
||||||
.state('core', {
|
.state('core', {
|
||||||
url: '/core',
|
url: '/core',
|
||||||
abstract: true,
|
abstract: true,
|
||||||
@ -901,7 +906,6 @@ angular.module('OpenSlidesApp.core.site', [
|
|||||||
'Config',
|
'Config',
|
||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
function($parse, Config, gettextCatalog) {
|
function($parse, Config, gettextCatalog) {
|
||||||
// remove resolution when changing to multiprojector
|
|
||||||
function getHtmlType(type) {
|
function getHtmlType(type) {
|
||||||
return {
|
return {
|
||||||
string: 'text',
|
string: 'text',
|
||||||
@ -911,7 +915,6 @@ angular.module('OpenSlidesApp.core.site', [
|
|||||||
choice: 'choice',
|
choice: 'choice',
|
||||||
colorpicker: 'colorpicker',
|
colorpicker: 'colorpicker',
|
||||||
comments: 'comments',
|
comments: 'comments',
|
||||||
resolution: 'resolution',
|
|
||||||
}[type];
|
}[type];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1187,209 +1190,439 @@ angular.module('OpenSlidesApp.core.site', [
|
|||||||
'$http',
|
'$http',
|
||||||
'$interval',
|
'$interval',
|
||||||
'$state',
|
'$state',
|
||||||
|
'$q',
|
||||||
'Config',
|
'Config',
|
||||||
'Projector',
|
'Projector',
|
||||||
function($scope, $http, $interval, $state, Config, Projector) {
|
function($scope, $http, $interval, $state, $q, Config, Projector) {
|
||||||
// bind projector elements to the scope, update after projector changed
|
$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 () {
|
$scope.$watch(function () {
|
||||||
return Projector.lastModified(1);
|
return Projector.lastModified();
|
||||||
}, function () {
|
}, function () {
|
||||||
|
$scope.projectors = Projector.getAll();
|
||||||
|
if (!$scope.active_projector) {
|
||||||
|
$scope.changeProjector($scope.projectors[0]);
|
||||||
|
}
|
||||||
|
|
||||||
// stop ALL interval timer
|
// stop ALL interval timer
|
||||||
for (var i=0; i<$scope.countdowns.length; i++) {
|
cancelIntervalTimers();
|
||||||
if ( $scope.countdowns[i].interval ) {
|
|
||||||
$interval.cancel($scope.countdowns[i].interval);
|
rebuildAllElements();
|
||||||
}
|
|
||||||
}
|
|
||||||
// rebuild all variables after projector update
|
|
||||||
$scope.rebuildAllElements();
|
|
||||||
});
|
});
|
||||||
$scope.$on('$destroy', function() {
|
$scope.$on('$destroy', function() {
|
||||||
// Cancel all intervals if the controller is destroyed
|
// Cancel all intervals if the controller is destroyed
|
||||||
for (var i=0; i<$scope.countdowns.length; i++) {
|
cancelIntervalTimers();
|
||||||
if ( $scope.countdowns[i].interval ) {
|
|
||||||
$interval.cancel($scope.countdowns[i].interval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// watch for changes in Config
|
// watch for changes in projector_broadcast
|
||||||
var last_conf;
|
var last_broadcast;
|
||||||
$scope.$watch(function () {
|
$scope.$watch(function () {
|
||||||
return Config.lastModified();
|
return Config.lastModified();
|
||||||
}, function () {
|
}, function () {
|
||||||
var conf = Config.get('projector_resolution').value;
|
var broadcast = Config.get('projector_broadcast').value;
|
||||||
// With multiprojector, get the resolution from Prjector.get(pk).{width; height}
|
if (!last_broadcast || last_broadcast != broadcast) {
|
||||||
if(!last_conf || last_conf.width != conf.width || last_conf.height != conf.height) {
|
last_broadcast = broadcast;
|
||||||
last_conf = conf;
|
$scope.broadcast = broadcast;
|
||||||
$scope.projectorWidth = conf.width;
|
|
||||||
$scope.projectorHeight = conf.height;
|
|
||||||
$scope.scale = 256.0 / $scope.projectorWidth;
|
|
||||||
$scope.iframeHeight = $scope.scale * $scope.projectorHeight;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$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 ***
|
// *** countdown functions ***
|
||||||
$scope.calculateCountdownTime = function (countdown) {
|
$scope.calculateCountdownTime = function (countdown) {
|
||||||
countdown.seconds = Math.floor( countdown.countdown_time - Date.now() / 1000 + $scope.serverOffset );
|
countdown.seconds = Math.floor( countdown.countdown_time - Date.now() / 1000 + $scope.serverOffset );
|
||||||
};
|
};
|
||||||
$scope.rebuildAllElements = function () {
|
$scope.editCountdown = function (countdown) {
|
||||||
$scope.countdowns = [];
|
countdown.editFlag = false;
|
||||||
$scope.messages = [];
|
$scope.projectors.forEach(function (projector) {
|
||||||
// iterate via all projector elements and catch all countdowns and messages
|
_.forEach(projector.elements, function (element, uuid) {
|
||||||
$.each(Projector.get(1).elements, function(key, value) {
|
if (element.name == 'core/countdown' && element.index == countdown.index) {
|
||||||
if (value.name == 'core/countdown') {
|
var data = {};
|
||||||
$scope.countdowns.push(value);
|
data[uuid] = {
|
||||||
if (value.status == "running") {
|
"description": countdown.description,
|
||||||
// calculate remaining seconds directly because interval starts with 1 second delay
|
"default_time": parseInt(countdown.default_time)
|
||||||
$scope.calculateCountdownTime(value);
|
};
|
||||||
// start interval timer (every second)
|
if (!countdown.running) {
|
||||||
value.interval = $interval( function() { $scope.calculateCountdownTime(value); }, 1000);
|
data[uuid].countdown_time = parseInt(countdown.default_time);
|
||||||
} else {
|
|
||||||
value.seconds = value.countdown_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 () {
|
$scope.addCountdown = function () {
|
||||||
var defaultvalue = parseInt(Config.get('projector_default_countdown').value);
|
var default_time = parseInt($scope.config('projector_default_countdown'));
|
||||||
$http.post('/rest/core/projector/1/activate_elements/', [{
|
$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',
|
name: 'core/countdown',
|
||||||
status: 'stop',
|
countdown_time: default_time,
|
||||||
|
default_time: default_time,
|
||||||
visible: false,
|
visible: false,
|
||||||
index: $scope.countdowns.length,
|
selected: true,
|
||||||
countdown_time: defaultvalue,
|
index: $scope.highestCountdownIndex,
|
||||||
default: defaultvalue,
|
running: false,
|
||||||
stable: true
|
stable: true,
|
||||||
}]);
|
}]);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
$scope.removeCountdown = function (countdown) {
|
$scope.removeCountdown = function (countdown) {
|
||||||
var data = {};
|
$scope.projectors.forEach(function (projector) {
|
||||||
var delta = 0;
|
var countdowns = [];
|
||||||
// rebuild index for all countdowns after the selected (deleted) countdown
|
_.forEach(projector.elements, function (element, uuid) {
|
||||||
for (var i=0; i<$scope.countdowns.length; i++) {
|
if (element.name == 'core/countdown' && element.index == countdown.index) {
|
||||||
if ( $scope.countdowns[i].uuid == countdown.uuid ) {
|
$http.post('/rest/core/projector/' + projector.id + '/deactivate_elements/', [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.startCountdown = function (countdown) {
|
$scope.startCountdown = function (countdown) {
|
||||||
|
$scope.projectors.forEach(function (projector) {
|
||||||
|
_.forEach(projector.elements, function (element, uuid) {
|
||||||
|
if (element.name == 'core/countdown' && element.index == countdown.index) {
|
||||||
var data = {};
|
var data = {};
|
||||||
// calculate end point of countdown (in seconds!)
|
// calculate end point of countdown (in seconds!)
|
||||||
var endTimestamp = Date.now() / 1000 - $scope.serverOffset + countdown.countdown_time;
|
var endTimestamp = Date.now() / 1000 - $scope.serverOffset + countdown.countdown_time;
|
||||||
data[countdown.uuid] = {
|
data[uuid] = {
|
||||||
"status": "running",
|
'running': true,
|
||||||
"countdown_time": endTimestamp
|
'countdown_time': endTimestamp
|
||||||
};
|
};
|
||||||
$http.post('/rest/core/projector/1/update_elements/', data);
|
$http.post('/rest/core/projector/' + projector.id + '/update_elements/', data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
$scope.stopCountdown = function (countdown) {
|
$scope.stopCountdown = function (countdown) {
|
||||||
|
$scope.projectors.forEach(function (projector) {
|
||||||
|
_.forEach(projector.elements, function (element, uuid) {
|
||||||
|
if (element.name == 'core/countdown' && element.index == countdown.index) {
|
||||||
var data = {};
|
var data = {};
|
||||||
// calculate rest duration of countdown (in seconds!)
|
// calculate rest duration of countdown (in seconds!)
|
||||||
var newDuration = Math.floor( countdown.countdown_time - Date.now() / 1000 + $scope.serverOffset );
|
var newDuration = Math.floor( countdown.countdown_time - Date.now() / 1000 + $scope.serverOffset );
|
||||||
data[countdown.uuid] = {
|
data[uuid] = {
|
||||||
"status": "stop",
|
'running': false,
|
||||||
"countdown_time": newDuration
|
'countdown_time': newDuration
|
||||||
};
|
};
|
||||||
$http.post('/rest/core/projector/1/update_elements/', data);
|
$http.post('/rest/core/projector/' + projector.id + '/update_elements/', data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
$scope.resetCountdown = function (countdown) {
|
$scope.resetCountdown = function (countdown) {
|
||||||
|
$scope.projectors.forEach(function (projector) {
|
||||||
|
_.forEach(projector.elements, function (element, uuid) {
|
||||||
|
if (element.name == 'core/countdown' && element.index == countdown.index) {
|
||||||
var data = {};
|
var data = {};
|
||||||
data[countdown.uuid] = {
|
data[uuid] = {
|
||||||
"status": "stop",
|
'running': false,
|
||||||
"countdown_time": countdown.default,
|
'countdown_time': countdown.default_time,
|
||||||
};
|
};
|
||||||
$http.post('/rest/core/projector/1/update_elements/', data);
|
$http.post('/rest/core/projector/' + projector.id + '/update_elements/', data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// *** message functions ***
|
// *** message functions ***
|
||||||
$scope.addMessage = function () {
|
|
||||||
$http.post('/rest/core/projector/1/activate_elements/', [{
|
|
||||||
name: 'core/message',
|
|
||||||
visible: false,
|
|
||||||
index: $scope.messages.length,
|
|
||||||
message: '',
|
|
||||||
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 };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data[message.uuid] = { "visible": false };
|
|
||||||
}
|
|
||||||
$http.post('/rest/core/projector/1/update_elements/', data);
|
|
||||||
};
|
|
||||||
$scope.editMessage = function (message) {
|
$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 = {};
|
var data = {};
|
||||||
data[message.uuid] = {
|
data[uuid] = {
|
||||||
"message": message.message,
|
message: message.message,
|
||||||
};
|
};
|
||||||
$http.post('/rest/core/projector/1/update_elements/', data);
|
$http.post('/rest/core/projector/' + projector.id + '/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});
|
|
||||||
};
|
|
||||||
$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.addMessage = function () {
|
||||||
|
$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,
|
||||||
|
selected: true,
|
||||||
|
index: $scope.highestMessageIndex,
|
||||||
|
message: '',
|
||||||
|
stable: true,
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
$scope.removeMessage = function (message) {
|
||||||
|
$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]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/* 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.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 }}"
|
id="{{ key }}"
|
||||||
type="{{ type }}">
|
type="{{ type }}">
|
||||||
|
|
||||||
<!-- resolution -->
|
|
||||||
<!-- Can be removed with multiprojector, but maybe it could be reused in the projectormanage-view -->
|
|
||||||
<!-- if removed, remember to delete the class resolution -->
|
|
||||||
<span ng-if="type == 'resolution'">
|
|
||||||
<translate>Width</translate>:
|
|
||||||
<input ng-model="$parent.value.width"
|
|
||||||
ng-model-option="{debounce: 1000}"
|
|
||||||
ng-change="save(configOption.key, $parent.value)"
|
|
||||||
class="form-control resolution"
|
|
||||||
id="{{ key }}_width"
|
|
||||||
type="number">
|
|
||||||
<translate>Height</translate>:
|
|
||||||
<input ng-model="$parent.value.height"
|
|
||||||
ng-model-option="{debounce: 1000}"
|
|
||||||
ng-change="save(configOption.key, $parent.value)"
|
|
||||||
class="form-control resolution"
|
|
||||||
id="{{ key }}_height"
|
|
||||||
type="number">
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<!-- comments -->
|
<!-- comments -->
|
||||||
<div class="input-comments" ng-if="type == 'comments'">
|
<div class="input-comments" ng-if="type == 'comments'">
|
||||||
<div ng-repeat="entry in $parent.value" class="input-group">
|
<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>
|
</a>
|
||||||
<div uib-collapse="isLiveViewClosed" ng-cloak>
|
<div uib-collapse="isLiveViewClosed" ng-cloak>
|
||||||
<style>
|
<style>
|
||||||
/* iframe for live view */
|
.col2 #iframe_sidebar {
|
||||||
.col2 #iframe {
|
width: {{ active_projector.width }}px;
|
||||||
width: {{ projectorWidth }}px;
|
height: {{ active_projector.height }}px;
|
||||||
height: {{ projectorHeight }}px;
|
|
||||||
-moz-transform: scale({{ scale }});
|
-moz-transform: scale({{ scale }});
|
||||||
-webkit-transform: scale({{ scale }});
|
-webkit-transform: scale({{ scale }});
|
||||||
-o-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')";
|
-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;
|
height: {{ iframeHeight }}px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col2 #iframeoverlay {
|
.col2 #iframeoverlay_sidebar {
|
||||||
height: {{ iframeHeight }}px;
|
height: {{ iframeHeight }}px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<a ui-sref="projector" target="_blank">
|
<div class="projectorSelector">
|
||||||
<div id="iframewrapper">
|
<div>
|
||||||
<iframe id="iframe" src="/real-projector" frameborder="0"></iframe>
|
<div class="dropdown" ng-show="projectors.length > 1">
|
||||||
<div id="iframeoverlay"></div>
|
<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>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<!-- projector control buttons -->
|
<!-- projector control buttons -->
|
||||||
<div os-perms="core.can_manage_projector">
|
<div os-perms="core.can_manage_projector">
|
||||||
|
|
||||||
<!-- edit -->
|
<!-- edit -->
|
||||||
<a ng-click="editCurrentSlide()"
|
<a ng-click="editCurrentSlide(active_projector)"
|
||||||
class="btn btn-default btn-sm"
|
class="btn btn-default btn-sm"
|
||||||
title="{{ 'Edit current slide' | translate}}">
|
title="{{ 'Edit current slide' | translate}}">
|
||||||
<i class="fa fa-pencil"></i>
|
<i class="fa fa-pencil"></i>
|
||||||
@ -48,43 +94,43 @@
|
|||||||
|
|
||||||
<!-- scale -->
|
<!-- scale -->
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a ng-click="controlProjector('scale', 'down')"
|
<a ng-click="active_projector.controlProjector('scale', 'down')"
|
||||||
class="btn btn-default btn-sm"
|
class="btn btn-default btn-sm"
|
||||||
title="{{ 'Smaller' | translate}}">
|
title="{{ 'Smaller' | translate}}">
|
||||||
<i class="fa fa-search-minus"></i>
|
<i class="fa fa-search-minus"></i>
|
||||||
</a>
|
</a>
|
||||||
<a ng-click="controlProjector('scale', 'up')"
|
<a ng-click="active_projector.controlProjector('scale', 'up')"
|
||||||
class="btn btn-default btn-sm"
|
class="btn btn-default btn-sm"
|
||||||
title="{{ 'Bigger' | translate}}">
|
title="{{ 'Bigger' | translate}}">
|
||||||
<i class="fa fa-search-plus"></i>
|
<i class="fa fa-search-plus"></i>
|
||||||
</a>
|
</a>
|
||||||
<a ng-click="controlProjector('scale', 'reset')"
|
<a ng-click="active_projector.controlProjector('scale', 'reset')"
|
||||||
class="btn btn-default btn-sm"
|
class="btn btn-default btn-sm"
|
||||||
title="{{ 'Reset scaling' | translate}}">
|
title="{{ 'Reset scaling' | translate}}">
|
||||||
<i class="fa fa-undo"></i>
|
<i class="fa fa-undo"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<span ng-class="{ 'notNull': scaleLevel != 0 }">{{ scaleLevel }}</span>
|
<span ng-class="{ 'notNull': active_projector.scale != 0 }">{{ active_projector.scale }}</span>
|
||||||
|
|
||||||
<!-- scroll -->
|
<!-- scroll -->
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a ng-click="controlProjector('scroll', 'down')"
|
<a ng-click="active_projector.controlProjector('scroll', 'down')"
|
||||||
class="btn btn-default btn-sm"
|
class="btn btn-default btn-sm"
|
||||||
title="{{ 'Scroll up' | translate}}">
|
title="{{ 'Scroll up' | translate}}">
|
||||||
<i class="fa fa-arrow-up"></i>
|
<i class="fa fa-arrow-up"></i>
|
||||||
</a>
|
</a>
|
||||||
<a ng-click="controlProjector('scroll', 'up')"
|
<a ng-click="active_projector.controlProjector('scroll', 'up')"
|
||||||
class="btn btn-default btn-sm"
|
class="btn btn-default btn-sm"
|
||||||
title="{{ 'Scroll down' | translate}}">
|
title="{{ 'Scroll down' | translate}}">
|
||||||
<i class="fa fa-arrow-down"></i>
|
<i class="fa fa-arrow-down"></i>
|
||||||
</a>
|
</a>
|
||||||
<a ng-click="controlProjector('scroll', 'reset')"
|
<a ng-click="active_projector.controlProjector('scroll', 'reset')"
|
||||||
class="btn btn-default btn-sm"
|
class="btn btn-default btn-sm"
|
||||||
title="{{ 'Reset scrolling' | translate}}">
|
title="{{ 'Reset scrolling' | translate}}">
|
||||||
<i class="fa fa-undo"></i>
|
<i class="fa fa-undo"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<span ng-class="{ 'notNull': scrollLevel != 0 }">{{ scrollLevel }}</span>
|
<span ng-class="{ 'notNull': active_projector.scroll != 0 }">{{ active_projector.scroll }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -96,11 +142,10 @@
|
|||||||
<h4 translate>Countdowns</h4>
|
<h4 translate>Countdowns</h4>
|
||||||
</a>
|
</a>
|
||||||
<div uib-collapse="!isCountdowns" ng-cloak>
|
<div uib-collapse="!isCountdowns" ng-cloak>
|
||||||
<div ng-repeat="countdown in countdowns | orderBy: 'index'" id="{{countdown.uuid}}"
|
<div ng-repeat="countdown in countdowns | orderBy: 'index'" id="countdown{{countdown.uuid}}" class="countdown panel panel-default">
|
||||||
class="countdown panel panel-default">
|
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<span ng-if="countdown.description">{{ countdown.description }}</span>
|
<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 -->
|
<!-- remove countdown button -->
|
||||||
<button type="button" class="close"
|
<button type="button" class="close"
|
||||||
ng-click="removeCountdown(countdown)"
|
ng-click="removeCountdown(countdown)"
|
||||||
@ -109,36 +154,58 @@
|
|||||||
</button>
|
</button>
|
||||||
<!-- edit countdown button -->
|
<!-- edit countdown button -->
|
||||||
<button type="button" class="close editicon"
|
<button type="button" class="close editicon"
|
||||||
ng-click="editCountdownFlag=true;"
|
ng-click="countdown.editFlag=true;"
|
||||||
title="{{ 'Edit countdown' | translate}}">
|
title="{{ 'Edit countdown' | translate}}">
|
||||||
<i class="fa fa-pencil"></i>
|
<i class="fa fa-pencil"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body"
|
<div class="panel-body"
|
||||||
ng-class="{ 'projected': countdown.visible }">
|
ng-class="{ 'projected': isProjected(countdown).length }">
|
||||||
<!-- project countdown button -->
|
<!-- project countdown button -->
|
||||||
<a class="btn btn-default btn-sm"
|
<div class="btn-group" style="width:54px;" uib-dropdown>
|
||||||
ng-model="countdown.visible"
|
<button type="button" class="btn btn-default btn-sm"
|
||||||
ng-click="showCountdown(countdown)"
|
ng-click="project(countdown)"
|
||||||
ng-class="{ 'btn-primary': countdown.visible }"
|
ng-class="{ 'btn-primary': isProjected(countdown).length }">
|
||||||
title="{{ 'Project countdown' | translate }}">
|
|
||||||
<i class="fa fa-video-camera"></i>
|
<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>
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- countdown controls -->
|
<!-- countdown controls -->
|
||||||
<a class="btn btn-default vcenter"
|
<a class="btn btn-default vcenter"
|
||||||
ng-click="resetCountdown(countdown)"
|
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}}">
|
title="{{ 'Reset countdown' | translate}}">
|
||||||
<i class="fa fa-stop"></i>
|
<i class="fa fa-stop"></i>
|
||||||
</a>
|
</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)"
|
ng-click="startCountdown(countdown)"
|
||||||
title="{{ 'Start' | translate}}">
|
title="{{ 'Start' | translate}}">
|
||||||
<i class="fa fa-play"></i>
|
<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>
|
||||||
<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)"
|
ng-click="stopCountdown(countdown)"
|
||||||
title="{{ 'Pause' | translate}}">
|
title="{{ 'Pause' | translate}}">
|
||||||
<i class="fa fa-pause"></i>
|
<i class="fa fa-pause"></i>
|
||||||
@ -150,22 +217,29 @@
|
|||||||
{{ countdown.seconds | osSecondsToTime }}
|
{{ countdown.seconds | osSecondsToTime }}
|
||||||
</span>
|
</span>
|
||||||
<!-- edit countdown form -->
|
<!-- edit countdown form -->
|
||||||
<form ng-show="editCountdownFlag" ng-submit="editCountdown(countdown)">
|
<form ng-show="countdown.editFlag"
|
||||||
|
ng-submit="editCountdown(countdown)">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label translate>Description</label>
|
<label translate>Description</label>
|
||||||
<input ng-model="countdown.description" type="text" class="form-control input-sm">
|
<input ng-model="countdown.description" type="text" class="form-control input-sm">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label translate>Start time</label>
|
<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">
|
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>
|
</div>
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
title="{{ 'Save' | translate}}"
|
title="{{ 'Save' | translate}}"
|
||||||
class="btn btn-sm btn-primary">
|
class="btn btn-sm btn-primary">
|
||||||
<i class="fa fa-check"></i>
|
<i class="fa fa-check"></i>
|
||||||
</button>
|
</button>
|
||||||
<button ng-click="editCountdownFlag=false;"
|
<button ng-click="countdown.editFlag=false;"
|
||||||
title="{{ 'Cancel' | translate}}"
|
title="{{ 'Cancel' | translate}}"
|
||||||
class="btn btn-default btn-sm">
|
class="btn btn-default btn-sm">
|
||||||
<i class="fa fa-times"></i>
|
<i class="fa fa-times"></i>
|
||||||
@ -182,7 +256,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- messages -->
|
<!-- messages -->
|
||||||
<div class="section" os-perms="core.can_manage_projector">
|
<div class="section" os-perms="core.can_manage_projector">
|
||||||
<a href="#" ng-click="isMessages = !isMessages">
|
<a href="#" ng-click="isMessages = !isMessages">
|
||||||
@ -190,10 +263,10 @@
|
|||||||
<h4 translate>Messages</h4>
|
<h4 translate>Messages</h4>
|
||||||
</a>
|
</a>
|
||||||
<div uib-collapse="!isMessages" ng-cloak>
|
<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">
|
<div class="panel-heading">
|
||||||
<span>{{ 'Message' | translate }} {{ message.index + 1 }}</span>
|
<span>{{ 'Message' | translate }} {{ $index + 1 }}</span>
|
||||||
<!-- remove message button -->
|
<!-- remove message button -->
|
||||||
<button type="button" class="close"
|
<button type="button" class="close"
|
||||||
ng-click="removeMessage(message)"
|
ng-click="removeMessage(message)"
|
||||||
@ -201,29 +274,52 @@
|
|||||||
<i class="fa fa-times"></i>
|
<i class="fa fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="close editicon"
|
<button type="button" class="close editicon"
|
||||||
ng-click="editMessageFlag=true;"
|
ng-click="message.editFlag=true"
|
||||||
title="{{ 'Edit message' | translate}}">
|
title="{{ 'Edit message' | translate}}">
|
||||||
<i class="fa fa-pencil"></i>
|
<i class="fa fa-pencil"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel-body"
|
<div class="panel-body"
|
||||||
ng-class="{ 'projected': message.visible }">
|
ng-class="{ 'projected': isProjected(message).length }">
|
||||||
<div class="projectorbtn">
|
<div class="projectorbtn">
|
||||||
<!-- project message button -->
|
<!-- project message button -->
|
||||||
<a class="btn btn-default btn-sm"
|
<div class="btn-group" style="width:54px;" uib-dropdown>
|
||||||
ng-model="message.visible"
|
<button type="button" class="btn btn-default btn-sm"
|
||||||
ng-click="showMessage(message)"
|
ng-click="project(message)"
|
||||||
ng-class="{ 'btn-primary': message.visible }"
|
ng-class="{ 'btn-primary': isProjected(message).length }">
|
||||||
title="{{ 'Project message' | translate }}" float="left">
|
|
||||||
<i class="fa fa-video-camera"></i>
|
<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>
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="innermessage" ng-bind-html="message.message"> </div>
|
<div class="innermessage" ng-bind-html="message.message"> </div>
|
||||||
|
|
||||||
<div class="panel-input">
|
<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">
|
<input ng-model="message.message" type="text" class="form-control input-sm">
|
||||||
<a ng-click="editMessage(message)"
|
<a ng-click="editMessage(message)"
|
||||||
title="{{ 'Save' | translate}}"
|
title="{{ 'Save' | translate}}"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<div ng-controller="SlideCountdownCtrl">
|
<div ng-controller="SlideCountdownCtrl">
|
||||||
<div ng-if="visible">
|
<div ng-if="visible && selected">
|
||||||
<div class="countdown well pull-right"
|
<div class="countdown well pull-right"
|
||||||
ng-class="{
|
ng-class="{
|
||||||
'negative': seconds <= 0,
|
'negative': seconds <= 0,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<div ng-controller="SlideMessageCtrl">
|
<div ng-controller="SlideMessageCtrl">
|
||||||
<div ng-if="visible" class="message_background"></div>
|
<div ng-if="visible && selected" class="message_background"></div>
|
||||||
<div ng-if="visible" class="message well" ng-bind-html="message"></div>
|
<div ng-if="visible && selected" class="message well" ng-class="{'identify': type=='identify'}" ng-bind-html="message"></div>
|
||||||
</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>
|
</style>
|
||||||
|
|
||||||
<div id="iframewrapper">
|
<div id="iframewrapper" ng-hide="error">
|
||||||
<iframe id="iframe" src="/real-projector" frameborder="0"></iframe>
|
<iframe id="iframe" ng-src="{{ '/real-projector/' + projector_id }}" frameborder="0"></iframe>
|
||||||
<div id="iframeoverlay"></div>
|
<div id="iframeoverlay"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="error" ng-show="error">
|
||||||
|
<p>{{ error | translate }}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -11,20 +11,28 @@
|
|||||||
<script src="static/js/openslides.js"></script>
|
<script src="static/js/openslides.js"></script>
|
||||||
<script src="static/js/openslides-templates.js"></script>
|
<script src="static/js/openslides-templates.js"></script>
|
||||||
|
|
||||||
<style type="text/css">
|
<div id="projectorContainer" ng-controller="ProjectorCtrl">
|
||||||
|
<style type="text/css">
|
||||||
#header, #footer {
|
#header, #footer {
|
||||||
background-color: {{ config('projector_header_backgroundcolor') }};
|
background-color: {{ config('projector_header_backgroundcolor') }};
|
||||||
}
|
}
|
||||||
#header, #footer, #currentTime {
|
#header, #footer, #currentTime {
|
||||||
color: {{ config('projector_header_fontcolor') }};
|
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 {
|
h1 {
|
||||||
color: {{ config('projector_h1_fontcolor') }};
|
color: {{ config('projector_h1_fontcolor') }};
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<img ng-if="config('projector_enable_logo')" id="logo" src="/static/img/logo-projector.png" alt="OpenSlides" />
|
<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 ng-if="config('projector_enable_title')" id="eventdata">
|
||||||
<div class="title" ng-class="{ 'titleonly': !config('general_event_description') }"
|
<div class="title" ng-class="{ 'titleonly': !config('general_event_description') }"
|
||||||
@ -32,13 +40,12 @@
|
|||||||
<div ng-if="config('general_event_description')" class="description"
|
<div ng-if="config('general_event_description')" class="description"
|
||||||
ng-bind-html="config('general_event_description')"></div>
|
ng-bind-html="config('general_event_description')"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-controller="ProjectorCtrl">
|
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.scrollcontent {
|
.scrollcontent {
|
||||||
margin-top: {{scroll}}px !important;
|
margin-top: {{ -80 * projector.scroll }}px !important;
|
||||||
font-size: {{scale}}%;
|
font-size: {{ 100 + 20 * projector.scale }}%;
|
||||||
}
|
}
|
||||||
.mediascrollcontent {
|
.mediascrollcontent {
|
||||||
margin-top: {{scroll/2}}em !important;
|
margin-top: {{scroll/2}}em !important;
|
||||||
@ -47,12 +54,11 @@
|
|||||||
transform: scale({{scale/100}});
|
transform: scale({{scale/100}});
|
||||||
}
|
}
|
||||||
</style>
|
</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 ng-include="element.template"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
<span ng-if="config('general_event_date')">
|
<span ng-if="config('general_event_date')">
|
||||||
{{ config('general_event_date') }}
|
{{ config('general_event_date') }}
|
||||||
</span>
|
</span>
|
||||||
@ -62,6 +68,7 @@
|
|||||||
<span ng-if="config('general_event_location')">
|
<span ng-if="config('general_event_location')">
|
||||||
{{ config('general_event_location') }}
|
{{ config('general_event_location') }}
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/webclient/projector/"></script>
|
<script src="/webclient/projector/"></script>
|
||||||
|
@ -28,10 +28,10 @@ urlpatterns = [
|
|||||||
name='core_webclient_javascript'),
|
name='core_webclient_javascript'),
|
||||||
|
|
||||||
# View for the projectors are handled by angular.
|
# 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.
|
# 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.
|
# Main entry point for all angular pages.
|
||||||
# Has to be the last entry in the urls.py
|
# Has to be the last entry in the urls.py
|
||||||
|
@ -18,6 +18,7 @@ from django.utils.timezone import now
|
|||||||
|
|
||||||
from openslides import __version__ as version
|
from openslides import __version__ as version
|
||||||
from openslides.utils import views as utils_views
|
from openslides.utils import views as utils_views
|
||||||
|
from openslides.utils.autoupdate import inform_changed_data
|
||||||
from openslides.utils.plugins import (
|
from openslides.utils.plugins import (
|
||||||
get_plugin_description,
|
get_plugin_description,
|
||||||
get_plugin_verbose_name,
|
get_plugin_verbose_name,
|
||||||
@ -25,7 +26,6 @@ from openslides.utils.plugins import (
|
|||||||
)
|
)
|
||||||
from openslides.utils.rest_api import (
|
from openslides.utils.rest_api import (
|
||||||
ModelViewSet,
|
ModelViewSet,
|
||||||
ReadOnlyModelViewSet,
|
|
||||||
Response,
|
Response,
|
||||||
SimpleMetadata,
|
SimpleMetadata,
|
||||||
ValidationError,
|
ValidationError,
|
||||||
@ -42,7 +42,7 @@ from .access_permissions import (
|
|||||||
)
|
)
|
||||||
from .config import config
|
from .config import config
|
||||||
from .exceptions import ConfigError, ConfigNotFound
|
from .exceptions import ConfigError, ConfigNotFound
|
||||||
from .models import ChatMessage, Projector, Tag
|
from .models import ChatMessage, ProjectionDefault, Projector, Tag
|
||||||
|
|
||||||
|
|
||||||
# Special Django views
|
# Special Django views
|
||||||
@ -171,7 +171,7 @@ class WebclientJavaScriptView(utils_views.View):
|
|||||||
|
|
||||||
# Viewsets for the REST API
|
# Viewsets for the REST API
|
||||||
|
|
||||||
class ProjectorViewSet(ReadOnlyModelViewSet):
|
class ProjectorViewSet(ModelViewSet):
|
||||||
"""
|
"""
|
||||||
API endpoint for the projector slide info.
|
API endpoint for the projector slide info.
|
||||||
|
|
||||||
@ -192,13 +192,23 @@ class ProjectorViewSet(ReadOnlyModelViewSet):
|
|||||||
result = self.request.user.has_perm('core.can_see_projector')
|
result = self.request.user.has_perm('core.can_see_projector')
|
||||||
elif self.action in ('activate_elements', 'prune_elements', 'update_elements',
|
elif self.action in ('activate_elements', 'prune_elements', 'update_elements',
|
||||||
'deactivate_elements', 'clear_elements', 'control_view',
|
'deactivate_elements', 'clear_elements', 'control_view',
|
||||||
'set_resolution', '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
|
result = (self.request.user.has_perm('core.can_see_projector') and
|
||||||
self.request.user.has_perm('core.can_manage_projector'))
|
self.request.user.has_perm('core.can_manage_projector'))
|
||||||
else:
|
else:
|
||||||
result = False
|
result = False
|
||||||
return result
|
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'])
|
@detail_route(methods=['post'])
|
||||||
def activate_elements(self, request, pk):
|
def activate_elements(self, request, pk):
|
||||||
"""
|
"""
|
||||||
@ -447,6 +457,70 @@ class ProjectorViewSet(ReadOnlyModelViewSet):
|
|||||||
scroll=request.data)
|
scroll=request.data)
|
||||||
return Response({'detail': message})
|
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):
|
class TagViewSet(ModelViewSet):
|
||||||
"""
|
"""
|
||||||
|
@ -56,20 +56,36 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
|
|||||||
'MediafileForm',
|
'MediafileForm',
|
||||||
'User',
|
'User',
|
||||||
'Projector',
|
'Projector',
|
||||||
function($scope, $http, ngDialog, Mediafile, MediafileForm, User, Projector) {
|
'ProjectionDefault',
|
||||||
|
function($scope, $http, ngDialog, Mediafile, MediafileForm, User, Projector, ProjectionDefault) {
|
||||||
Mediafile.bindAll({}, $scope, 'mediafiles');
|
Mediafile.bindAll({}, $scope, 'mediafiles');
|
||||||
User.bindAll({}, $scope, 'users');
|
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
|
function updatePresentedMediafiles () {
|
||||||
$scope.sortColumn = 'title';
|
$scope.presentedMediafiles = [];
|
||||||
$scope.filterPresent = '';
|
Projector.getAll().forEach(function (projector) {
|
||||||
$scope.reverse = false;
|
var projectorElements = _.map(projector.elements, function(element) { return element; });
|
||||||
|
var mediaElements = _.filter(projectorElements, function (element) {
|
||||||
function updatePresentedMediafiles() {
|
|
||||||
var projectorElements = _.map(Projector.get(1).elements, function(element) { return element; });
|
|
||||||
$scope.presentedMediafiles = _.filter(projectorElements, function (element) {
|
|
||||||
return element.name === 'mediafiles/mediafile';
|
return element.name === 'mediafiles/mediafile';
|
||||||
});
|
});
|
||||||
|
mediaElements.forEach(function (element) {
|
||||||
|
$scope.presentedMediafiles.push(element);
|
||||||
|
});
|
||||||
|
});
|
||||||
if ($scope.presentedMediafiles.length) {
|
if ($scope.presentedMediafiles.length) {
|
||||||
$scope.isMeta = false;
|
$scope.isMeta = false;
|
||||||
} else {
|
} else {
|
||||||
@ -77,12 +93,13 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.$watch(function() {
|
|
||||||
return Projector.get(1).elements;
|
|
||||||
}, updatePresentedMediafiles);
|
|
||||||
|
|
||||||
updatePresentedMediafiles();
|
updatePresentedMediafiles();
|
||||||
|
|
||||||
|
// setup table sorting
|
||||||
|
$scope.sortColumn = 'title';
|
||||||
|
$scope.filterPresent = '';
|
||||||
|
$scope.reverse = false;
|
||||||
|
|
||||||
// function to sort by clicked column
|
// function to sort by clicked column
|
||||||
$scope.toggleSort = function ( column ) {
|
$scope.toggleSort = function ( column ) {
|
||||||
if ( $scope.sortColumn === column ) {
|
if ( $scope.sortColumn === column ) {
|
||||||
@ -138,8 +155,13 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
|
|||||||
|
|
||||||
// ** PDF presentation functions **/
|
// ** PDF presentation functions **/
|
||||||
// show document on projector
|
// show document on projector
|
||||||
$scope.showMediafile = function (mediafile) {
|
$scope.showMediafile = function (projectorId, mediafile) {
|
||||||
var postUrl = '/rest/core/projector/1/prune_elements/';
|
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 = [{
|
var data = [{
|
||||||
name: 'mediafiles/mediafile',
|
name: 'mediafiles/mediafile',
|
||||||
id: mediafile.id,
|
id: mediafile.id,
|
||||||
@ -152,23 +174,28 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
|
|||||||
fullscreen: mediafile.is_pdf
|
fullscreen: mediafile.is_pdf
|
||||||
}];
|
}];
|
||||||
$http.post(postUrl, data);
|
$http.post(postUrl, data);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function sendMediafileCommand(data) {
|
// To avoid some kind of 60,000000000001% in template
|
||||||
var mediafileElement = getCurrentlyPresentedMediafile();
|
$scope.round = function (val) {return Math.round(val);};
|
||||||
var updateData = _.extend({}, mediafileElement);
|
|
||||||
|
var sendMediafileCommand = function (mediafile, data) {
|
||||||
|
var updateData = _.extend({}, mediafile);
|
||||||
_.extend(updateData, data);
|
_.extend(updateData, data);
|
||||||
var postData = {};
|
var postData = {};
|
||||||
postData[mediafileElement.uuid] = updateData;
|
postData[mediafile.uuid] = updateData;
|
||||||
$http.post('/rest/core/projector/1/update_elements/', postData);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCurrentlyPresentedMediafile() {
|
// Find Projector where the mediafile is projected
|
||||||
return $scope.presentedMediafiles[0];
|
$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);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
$scope.getTitle = function (presentedMediafile) {
|
$scope.getTitle = function (mediafile) {
|
||||||
return Mediafile.get(presentedMediafile.id).title;
|
return Mediafile.get(mediafile.id).title;
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.getType = function(presentedMediafile) {
|
$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';
|
return mediafile.is_pdf ? 'pdf' : mediafile.is_image ? 'image' : 'video';
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.mediafileGoToPage = function (page) {
|
$scope.mediafileGoToPage = function (mediafile, page) {
|
||||||
var mediafileElement = getCurrentlyPresentedMediafile();
|
|
||||||
if (parseInt(page) > 0) {
|
if (parseInt(page) > 0) {
|
||||||
sendMediafileCommand({
|
sendMediafileCommand(
|
||||||
page: parseInt(page)
|
mediafile,
|
||||||
});
|
{page: parseInt(page)}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
$scope.mediafileZoomIn = function () {
|
$scope.mediafileZoomIn = function (mediafile) {
|
||||||
var mediafileElement = getCurrentlyPresentedMediafile();
|
|
||||||
var scale = 1;
|
var scale = 1;
|
||||||
if (parseFloat(mediafileElement.scale)) {
|
if (parseFloat(mediafile.scale)) {
|
||||||
scale = mediafileElement.scale;
|
scale = mediafile.scale;
|
||||||
}
|
}
|
||||||
sendMediafileCommand({
|
sendMediafileCommand(
|
||||||
scale: scale + 0.2
|
mediafile,
|
||||||
});
|
{scale: scale + 0.2}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
$scope.mediafileFit = function () {
|
$scope.mediafileFit = function (mediafile) {
|
||||||
sendMediafileCommand({
|
sendMediafileCommand(
|
||||||
scale: 'page-fit'
|
mediafile,
|
||||||
});
|
{scale: 'page-fit'}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
$scope.mediafileZoomOut = function () {
|
$scope.mediafileZoomOut = function (mediafile) {
|
||||||
var mediafileElement = getCurrentlyPresentedMediafile();
|
|
||||||
var scale = 1;
|
var scale = 1;
|
||||||
if (parseFloat(mediafileElement.scale)) {
|
if (parseFloat(mediafile.scale)) {
|
||||||
scale = mediafileElement.scale;
|
scale = mediafile.scale;
|
||||||
}
|
}
|
||||||
sendMediafileCommand({
|
sendMediafileCommand(
|
||||||
scale: scale - 0.2
|
mediafile,
|
||||||
});
|
{scale: scale - 0.2}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
$scope.mediafileChangePage = function(pageNum) {
|
$scope.mediafileChangePage = function(mediafile, pageNum) {
|
||||||
sendMediafileCommand({
|
sendMediafileCommand(
|
||||||
pageToDisplay: pageNum
|
mediafile,
|
||||||
});
|
{pageToDisplay: pageNum}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
$scope.mediafileRotate = function () {
|
$scope.mediafileRotate = function (mediafile) {
|
||||||
var mediafileElement = getCurrentlyPresentedMediafile();
|
var rotation = mediafile.rotate;
|
||||||
var rotation = mediafileElement.rotate;
|
|
||||||
if (rotation === 270) {
|
if (rotation === 270) {
|
||||||
rotation = 0;
|
rotation = 0;
|
||||||
} else {
|
} else {
|
||||||
rotation = rotation + 90;
|
rotation = rotation + 90;
|
||||||
}
|
}
|
||||||
sendMediafileCommand({
|
sendMediafileCommand(
|
||||||
rotate: rotation
|
mediafile,
|
||||||
});
|
{rotate: rotation}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
$scope.mediafileScroll = function(scroll) {
|
$scope.mediafileScroll = function(scroll) {
|
||||||
var mediafileElement = getCurrentlyPresentedMediafile();
|
var mediafileElement = getCurrentlyPresentedMediafile();
|
||||||
|
@ -28,12 +28,12 @@
|
|||||||
<h3>{{ getTitle(presentedMediafile) }}</h3>
|
<h3>{{ getTitle(presentedMediafile) }}</h3>
|
||||||
<nav ng-show="getType(presentedMediafile) === 'pdf'" ng-class="getNavStyle(scroll)" class="form-inline">
|
<nav ng-show="getType(presentedMediafile) === 'pdf'" ng-class="getNavStyle(scroll)" class="form-inline">
|
||||||
<div class="btn-group">
|
<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 }"
|
ng-class="{ 'disabled': (presentedMediafile.page - 1) < 1 }"
|
||||||
title="{{ 'Previous page' | translate }}">
|
title="{{ 'Previous page' | translate }}">
|
||||||
<i class="fa fa-backward"></i>
|
<i class="fa fa-backward"></i>
|
||||||
</button>
|
</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 }"
|
ng-class="{ 'disabled': (presentedMediafile.page + 1) > presentedMediafile.numPages }"
|
||||||
title="{{ 'Next page' | translate }}">
|
title="{{ 'Next page' | translate }}">
|
||||||
<i class="fa fa-forward"></i>
|
<i class="fa fa-forward"></i>
|
||||||
@ -42,23 +42,23 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-addon" translate>Page</span>
|
<span class="input-group-addon" translate>Page</span>
|
||||||
<input type="number" min=1 ng-model="presentedMediafile.page" class="form-control" style="width: 80px"
|
<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>
|
<span class="input-group-addon"><translate>of</translate> {{presentedMediafile.numPages}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group">
|
<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>
|
<i class="fa fa-repeat"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group">
|
<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>
|
<i class="fa fa-search-minus"></i>
|
||||||
</button>
|
</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'">
|
ng-class="presentedMediafile.scale=='page-fit' ? 'btn-primary' : 'btn-default'">
|
||||||
<i class="fa fa-arrows-alt"></i>
|
<i class="fa fa-arrows-alt"></i>
|
||||||
</button>
|
</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>
|
<i class="fa fa-search-plus"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -195,13 +195,32 @@
|
|||||||
<!-- projector column -->
|
<!-- projector column -->
|
||||||
<td ng-show="!isDeleteMode"
|
<td ng-show="!isDeleteMode"
|
||||||
os-perms="core.can_manage_projector">
|
os-perms="core.can_manage_projector">
|
||||||
<a class="btn btn-default btn-sm"
|
<div class="btn-group" style="min-width:54px;" uib-dropdown
|
||||||
ng-if="mediafile.is_presentable"
|
ng-if="mediafile.mediafile.is_presentable"
|
||||||
ng-class="{ 'btn-primary': mediafile.isProjected() }"
|
uib-tooltip="{{ 'Projektor' | translate }} {{ mediafile.isProjected() }}"
|
||||||
ng-click="showMediafile(mediafile)"
|
tooltip-enable="mediafile.isProjected() > 0">
|
||||||
title="{{ 'Project mediafile' | translate }}">
|
<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>
|
<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>
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
<!-- delete selection column -->
|
<!-- delete selection column -->
|
||||||
<td ng-show="isDeleteMode" os-perms="mediafiles.can_manage" class="deleteColumn">
|
<td ng-show="isDeleteMode" os-perms="mediafiles.can_manage" class="deleteColumn">
|
||||||
<input type="checkbox" ng-model="mediafile.selected">
|
<input type="checkbox" ng-model="mediafile.selected">
|
||||||
|
@ -21,7 +21,8 @@ angular.module('OpenSlidesApp.motions.projector', ['OpenSlidesApp.motions'])
|
|||||||
'User',
|
'User',
|
||||||
'Config',
|
'Config',
|
||||||
'Projector',
|
'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.
|
// 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
|
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||||
// class.
|
// 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');
|
User.bindAll({}, $scope, 'users');
|
||||||
|
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -917,14 +917,25 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid
|
|||||||
'PdfMakeDocumentProvider',
|
'PdfMakeDocumentProvider',
|
||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
'HTMLValidizer',
|
'HTMLValidizer',
|
||||||
|
'Projector',
|
||||||
|
'ProjectionDefault',
|
||||||
function($scope, $state, $http, ngDialog, MotionForm, Motion, Category, Tag, Workflow, User, Agenda, MotionDocxExport,
|
function($scope, $state, $http, ngDialog, MotionForm, Motion, Category, Tag, Workflow, User, Agenda, MotionDocxExport,
|
||||||
MotionContentProvider, MotionCatalogContentProvider, PdfMakeConverter,
|
MotionContentProvider, MotionCatalogContentProvider, PdfMakeConverter, PdfMakeDocumentProvider,
|
||||||
PdfMakeDocumentProvider, gettextCatalog, HTMLValidizer) {
|
gettextCatalog, HTMLValidizer, Projector, ProjectionDefault) {
|
||||||
Motion.bindAll({}, $scope, 'motions');
|
Motion.bindAll({}, $scope, 'motions');
|
||||||
Category.bindAll({}, $scope, 'categories');
|
Category.bindAll({}, $scope, 'categories');
|
||||||
Tag.bindAll({}, $scope, 'tags');
|
Tag.bindAll({}, $scope, 'tags');
|
||||||
Workflow.bindAll({}, $scope, 'workflows');
|
Workflow.bindAll({}, $scope, 'workflows');
|
||||||
User.bindAll({}, $scope, 'users');
|
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 = {};
|
$scope.alert = {};
|
||||||
|
|
||||||
// setup table sorting
|
// setup table sorting
|
||||||
@ -1192,9 +1203,10 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid
|
|||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
'Projector',
|
'Projector',
|
||||||
'HTMLValidizer',
|
'HTMLValidizer',
|
||||||
|
'ProjectionDefault',
|
||||||
function($scope, $http, ngDialog, MotionForm, Motion, Category, Mediafile, Tag, User, Workflow, Config,
|
function($scope, $http, ngDialog, MotionForm, Motion, Category, Mediafile, Tag, User, Workflow, Config,
|
||||||
motion, MotionContentProvider, PollContentProvider,
|
motion, MotionContentProvider, PollContentProvider, PdfMakeConverter, PdfMakeDocumentProvider,
|
||||||
PdfMakeConverter, PdfMakeDocumentProvider, MotionInlineEditing, gettextCatalog, Projector, HTMLValidizer) {
|
MotionInlineEditing, gettextCatalog, Projector, HTMLValidizer, ProjectionDefault) {
|
||||||
Motion.bindOne(motion.id, $scope, 'motion');
|
Motion.bindOne(motion.id, $scope, 'motion');
|
||||||
Category.bindAll({}, $scope, 'categories');
|
Category.bindAll({}, $scope, 'categories');
|
||||||
Mediafile.bindAll({}, $scope, 'mediafiles');
|
Mediafile.bindAll({}, $scope, 'mediafiles');
|
||||||
@ -1202,6 +1214,12 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid
|
|||||||
User.bindAll({}, $scope, 'users');
|
User.bindAll({}, $scope, 'users');
|
||||||
Workflow.bindAll({}, $scope, 'workflows');
|
Workflow.bindAll({}, $scope, 'workflows');
|
||||||
Motion.loadRelations(motion, 'agenda_item');
|
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.version = motion.active_version;
|
||||||
$scope.isCollapsed = true;
|
$scope.isCollapsed = true;
|
||||||
$scope.commentsFields = Config.get('motions_comments').value;
|
$scope.commentsFields = Config.get('motions_comments').value;
|
||||||
|
@ -16,12 +16,8 @@
|
|||||||
<translate>List of speakers</translate>
|
<translate>List of speakers</translate>
|
||||||
</a>
|
</a>
|
||||||
<!-- project -->
|
<!-- project -->
|
||||||
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
<projector-button model="motion" default-projector-id="defaultProjectorId">
|
||||||
ng-class="{ 'btn-primary': motion.isProjected() }"
|
</projector-button>
|
||||||
ng-click="motion.project()"
|
|
||||||
title="{{ 'Project motion' | translate }}">
|
|
||||||
<i class="fa fa-video-camera"></i>
|
|
||||||
</a>
|
|
||||||
<!-- edit -->
|
<!-- edit -->
|
||||||
<a ng-if="motion.isAllowed('update')" ng-click="openDialog(motion)"
|
<a ng-if="motion.isAllowed('update')" ng-click="openDialog(motion)"
|
||||||
class="btn btn-default btn-sm"
|
class="btn btn-default btn-sm"
|
||||||
|
@ -98,7 +98,6 @@
|
|||||||
ng-click="checkAll()"></i>
|
ng-click="checkAll()"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-11 main-header">
|
<div class="col-xs-11 main-header">
|
||||||
|
|
||||||
<span class="form-inline text-right pull-right">
|
<span class="form-inline text-right pull-right">
|
||||||
<span class="sort-spacer pointer" ng-click="reset_filters()"
|
<span class="sort-spacer pointer" ng-click="reset_filters()"
|
||||||
ng-if="are_filters_set()" ng-disabled="isDeleteMode"
|
ng-if="are_filters_set()" ng-disabled="isDeleteMode"
|
||||||
@ -304,12 +303,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- projector column -->
|
<!-- projector column -->
|
||||||
<div class="col-xs-1 centered" os-perms="core.can_manage_projector">
|
<div class="col-xs-1 centered" os-perms="core.can_manage_projector">
|
||||||
<a class="btn btn-default btn-sm"
|
<projector-button model="motion", default-projector-id="defaultProjectorId">
|
||||||
ng-class="{ 'btn-primary': motion.isProjected() }"
|
</projector-button>
|
||||||
ng-click="motion.project()"
|
|
||||||
title="{{ 'Project motion' | translate }}">
|
|
||||||
<i class="fa fa-video-camera"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- main content column -->
|
<!-- main content column -->
|
||||||
<div class="col-xs-6 content">
|
<div class="col-xs-6 content">
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
import openslides.utils.models
|
import openslides.utils.models
|
||||||
|
|
||||||
|
|
||||||
|
@ -156,8 +156,18 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics'])
|
|||||||
'TopicForm',
|
'TopicForm',
|
||||||
'Topic',
|
'Topic',
|
||||||
'topic',
|
'topic',
|
||||||
function($scope, ngDialog, TopicForm, Topic, topic) {
|
'Projector',
|
||||||
|
'ProjectionDefault',
|
||||||
|
function($scope, ngDialog, TopicForm, Topic, topic, Projector, ProjectionDefault) {
|
||||||
Topic.bindOne(topic.id, $scope, 'topic');
|
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');
|
Topic.loadRelations(topic, 'agenda_item');
|
||||||
$scope.openDialog = function (topic) {
|
$scope.openDialog = function (topic) {
|
||||||
ngDialog.open(TopicForm.getDialog(topic));
|
ngDialog.open(TopicForm.getDialog(topic));
|
||||||
|
@ -11,12 +11,8 @@
|
|||||||
<translate>List of speakers</translate>
|
<translate>List of speakers</translate>
|
||||||
</a>
|
</a>
|
||||||
<!-- project -->
|
<!-- project -->
|
||||||
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
<projector-button model="topic" default-projector-id="defaultProjectorId">
|
||||||
ng-class="{ 'btn-primary': topic.isProjected() }"
|
</projector-button>
|
||||||
ng-click="topic.project()"
|
|
||||||
title="{{ 'Project topic' | translate }}">
|
|
||||||
<i class="fa fa-video-camera"></i>
|
|
||||||
</a>
|
|
||||||
<!-- edit -->
|
<!-- edit -->
|
||||||
<a os-perms="agenda.can_manage" ng-click="openDialog(topic)"
|
<a os-perms="agenda.can_manage" ng-click="openDialog(topic)"
|
||||||
class="btn btn-default btn-sm"
|
class="btn btn-default btn-sm"
|
||||||
|
@ -421,9 +421,19 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users'])
|
|||||||
'User',
|
'User',
|
||||||
'Group',
|
'Group',
|
||||||
'PasswordGenerator',
|
'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');
|
User.bindAll({}, $scope, 'users');
|
||||||
Group.bindAll({where: {id: {'>': 1}}}, $scope, 'groups');
|
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.alert = {};
|
||||||
$scope.groupFilter = undefined;
|
$scope.groupFilter = undefined;
|
||||||
|
|
||||||
|
@ -184,12 +184,8 @@
|
|||||||
ng-class="{ 'activeline': user.isProjected(), 'selected': user.selected }">
|
ng-class="{ 'activeline': user.isProjected(), 'selected': user.selected }">
|
||||||
<!-- projector column -->
|
<!-- projector column -->
|
||||||
<td ng-show="!isSelectMode" os-perms="core.can_manage_projector">
|
<td ng-show="!isSelectMode" os-perms="core.can_manage_projector">
|
||||||
<a class="btn btn-default btn-sm"
|
<projector-button model="user" default-projector-id="defaultProjectorId">
|
||||||
ng-class="{ 'btn-primary': user.isProjected() }"
|
</projector-button>
|
||||||
ng-click="user.project()"
|
|
||||||
title="{{ 'Project user' | translate }}">
|
|
||||||
<i class="fa fa-video-camera"></i>
|
|
||||||
</a>
|
|
||||||
<!-- selection column -->
|
<!-- selection column -->
|
||||||
<td ng-show="isSelectMode" os-perms="users.can_manage" class="deleteColumn">
|
<td ng-show="isSelectMode" os-perms="users.can_manage" class="deleteColumn">
|
||||||
<input type="checkbox" ng-model="user.selected">
|
<input type="checkbox" ng-model="user.selected">
|
||||||
|
@ -138,11 +138,25 @@ def send_data(message):
|
|||||||
projectors = Projector.get_projectors_that_show_this(collection_element)
|
projectors = Projector.get_projectors_that_show_this(collection_element)
|
||||||
send_all = None # The decission is done later
|
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:
|
for projector in projectors:
|
||||||
if send_all is None:
|
if send_all is None:
|
||||||
send_all = projector.need_full_update_for_this(collection_element)
|
send_all = projector.need_full_update_for_this(collection_element)
|
||||||
if send_all:
|
if send_all:
|
||||||
|
if broadcast_projector_data is None:
|
||||||
output = get_projector_element_data(projector)
|
output = get_projector_element_data(projector)
|
||||||
|
else:
|
||||||
|
output = broadcast_projector_data
|
||||||
else:
|
else:
|
||||||
output = []
|
output = []
|
||||||
output.append(collection_element.as_autoupdate_for_projector())
|
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 ModelSerializer as _ModelSerializer
|
||||||
from rest_framework.serializers import ( # noqa
|
from rest_framework.serializers import ( # noqa
|
||||||
MANY_RELATION_KWARGS,
|
MANY_RELATION_KWARGS,
|
||||||
|
BooleanField,
|
||||||
CharField,
|
CharField,
|
||||||
DictField,
|
DictField,
|
||||||
Field,
|
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 GenericViewSet as _GenericViewSet # noqa
|
||||||
from rest_framework.viewsets import ModelViewSet as _ModelViewSet # 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
|
from rest_framework.viewsets import ViewSet as _ViewSet # noqa
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
@ -174,9 +173,5 @@ class ModelViewSet(PermissionMixin, _ModelViewSet):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ReadOnlyModelViewSet(PermissionMixin, _ReadOnlyModelViewSet):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ViewSet(PermissionMixin, _ViewSet):
|
class ViewSet(PermissionMixin, _ViewSet):
|
||||||
pass
|
pass
|
||||||
|
@ -216,17 +216,6 @@ class Speak(TestCase):
|
|||||||
|
|
||||||
def test_begin_speech_with_countdown(self):
|
def test_begin_speech_with_countdown(self):
|
||||||
config['agenda_couple_countdown_and_speakers'] = True
|
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.objects.add(self.user, self.item)
|
||||||
speaker = Speaker.objects.add(get_user_model().objects.get(username='admin'), self.item)
|
speaker = Speaker.objects.add(get_user_model().objects.get(username='admin'), self.item)
|
||||||
self.client.put(
|
self.client.put(
|
||||||
@ -234,32 +223,22 @@ class Speak(TestCase):
|
|||||||
{'speaker': speaker.pk})
|
{'speaker': speaker.pk})
|
||||||
for key, value in Projector.objects.get().config.items():
|
for key, value in Projector.objects.get().config.items():
|
||||||
if value['name'] == 'core/countdown':
|
if value['name'] == 'core/countdown':
|
||||||
self.assertEqual(value['status'], 'running')
|
self.assertTrue(value['running'])
|
||||||
success = True
|
# If created, the countdown should have index 1
|
||||||
|
created = value['index'] == 1
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
success = False
|
created = False
|
||||||
self.assertTrue(success)
|
self.assertTrue(created)
|
||||||
|
|
||||||
def test_end_speech_with_countdown(self):
|
def test_end_speech_with_countdown(self):
|
||||||
config['agenda_couple_countdown_and_speakers'] = True
|
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 = Speaker.objects.add(get_user_model().objects.get(username='admin'), self.item)
|
||||||
speaker.begin_speech()
|
speaker.begin_speech()
|
||||||
self.client.delete(reverse('item-speak', args=[self.item.pk]))
|
self.client.delete(reverse('item-speak', args=[self.item.pk]))
|
||||||
for key, value in Projector.objects.get().config.items():
|
for key, value in Projector.objects.get().config.items():
|
||||||
if value['name'] == 'core/countdown':
|
if value['name'] == 'core/countdown':
|
||||||
self.assertEqual(value['status'], 'stop')
|
self.assertFalse(value['running'])
|
||||||
success = True
|
success = True
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
@ -25,9 +25,11 @@ class ProjectorAPI(TestCase):
|
|||||||
default_projector.save()
|
default_projector.save()
|
||||||
|
|
||||||
response = self.client.get(reverse('projector-detail', args=['1']))
|
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(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(json.loads(response.content.decode()), {
|
self.assertEqual(content, {
|
||||||
'id': 1,
|
'id': 1,
|
||||||
'elements': {
|
'elements': {
|
||||||
'aae4a07b26534cfb9af4232f361dce73':
|
'aae4a07b26534cfb9af4232f361dce73':
|
||||||
@ -36,6 +38,8 @@ class ProjectorAPI(TestCase):
|
|||||||
'name': 'topics/topic'}},
|
'name': 'topics/topic'}},
|
||||||
'scale': 0,
|
'scale': 0,
|
||||||
'scroll': 0,
|
'scroll': 0,
|
||||||
|
'name': 'Defaultprojector',
|
||||||
|
'blank': False,
|
||||||
'width': 1024,
|
'width': 1024,
|
||||||
'height': 768})
|
'height': 768})
|
||||||
|
|
||||||
@ -47,9 +51,11 @@ class ProjectorAPI(TestCase):
|
|||||||
default_projector.save()
|
default_projector.save()
|
||||||
|
|
||||||
response = self.client.get(reverse('projector-detail', args=['1']))
|
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(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(json.loads(response.content.decode()), {
|
self.assertEqual(content, {
|
||||||
'id': 1,
|
'id': 1,
|
||||||
'elements': {
|
'elements': {
|
||||||
'fc6ef43b624043068c8e6e7a86c5a1b0':
|
'fc6ef43b624043068c8e6e7a86c5a1b0':
|
||||||
@ -58,6 +64,8 @@ class ProjectorAPI(TestCase):
|
|||||||
'error': 'Projector element does not exist.'}},
|
'error': 'Projector element does not exist.'}},
|
||||||
'scale': 0,
|
'scale': 0,
|
||||||
'scroll': 0,
|
'scroll': 0,
|
||||||
|
'name': 'Defaultprojector',
|
||||||
|
'blank': False,
|
||||||
'width': 1024,
|
'width': 1024,
|
||||||
'height': 768})
|
'height': 768})
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user