Reorganize the current list of speakers. Fixes #2669 and find()-calls.

This commit is contained in:
FinnStutzenstein 2017-02-03 11:54:36 +01:00
parent 45c3da22f9
commit 3c36cd5757
14 changed files with 330 additions and 237 deletions

View File

@ -12,6 +12,8 @@ Agenda:
- Added button to remove all speakers from a list of speakers. - Added button to remove all speakers from a list of speakers.
- Added option to create or edit agenda items as subitems of others. - Added option to create or edit agenda items as subitems of others.
- Fixed security issue: Comments were shown for unprivileged users. - Fixed security issue: Comments were shown for unprivileged users.
- Added option to choose whether to show the current list of speakers slide
as a slide or an overlay.
Core: Core:
- Added support for multiple projectors. - Added support for multiple projectors.

View File

@ -73,13 +73,19 @@ class ListOfSpeakersSlide(ProjectorElement):
return {'agenda_item_id': self.config_entry.get('id')} return {'agenda_item_id': self.config_entry.get('id')}
class CurrentListOfSpeakersMetaClass(ProjectorElement): class CurrentListOfSpeakersSlide(ProjectorElement):
""" """
Main class for the list of speaker slides. Slide for the current list of speakers.
"""
name = 'agenda/current-list-of-speakers'
"""
def get_requirements(self, config_entry): def get_requirements(self, config_entry):
items = self.get_agenda_items(config['projector_currentListOfSpeakers_reference']) # The query mechanism on client needs the referenced projector.
reference_projector = Projector.objects.get(
pk=config['projector_currentListOfSpeakers_reference'])
yield reference_projector
items = self.get_agenda_items(reference_projector)
for item in items: for item in items:
yield item yield item
for speaker in item.speakers.filter(end_time=None): for speaker in item.speakers.filter(end_time=None):
@ -90,8 +96,7 @@ class CurrentListOfSpeakersMetaClass(ProjectorElement):
# Yield last speakers # Yield last speakers
yield speaker.user yield speaker.user
def get_agenda_items(self, projector_id): def get_agenda_items(self, projector):
projector = Projector.objects.get(pk=projector_id)
for element in projector.elements.values(): for element in projector.elements.values():
agenda_item_id = element.get('agenda_item_id') agenda_item_id = element.get('agenda_item_id')
if agenda_item_id is not None: if agenda_item_id is not None:
@ -99,26 +104,23 @@ class CurrentListOfSpeakersMetaClass(ProjectorElement):
def get_collection_elements_required_for_this(self, collection_element, config_entry): def get_collection_elements_required_for_this(self, collection_element, config_entry):
output = super().get_collection_elements_required_for_this(collection_element, config_entry) output = super().get_collection_elements_required_for_this(collection_element, config_entry)
# Full update if agenda_item changes because then we may have new # Full update if agenda_item or referenced projector changes because
# candidates and therefor need new users. # then we may have new candidates and therefor need new users.
items = self.get_agenda_items(config['projector_currentListOfSpeakers_reference']) reference_projector = Projector.objects.get(
pk=config['projector_currentListOfSpeakers_reference'])
is_reference_projector = collection_element == CollectionElement.from_values(
reference_projector.get_collection_string(),
reference_projector.pk)
is_config = (
collection_element.collection_string == 'core/config' and
collection_element.information.get('changed_config') == 'projector_currentListOfSpeakers_reference')
if is_reference_projector or is_config:
output.extend(self.get_requirements_as_collection_elements(config_entry))
else:
items = self.get_agenda_items(reference_projector)
for item in items: for item in items:
if collection_element == CollectionElement.from_values(item.get_collection_string(), item.pk): if collection_element == CollectionElement.from_values(item.get_collection_string(), item.pk):
output.extend(self.get_requirements_as_collection_elements(config_entry)) output.extend(self.get_requirements_as_collection_elements(config_entry))
break break
return output return output
class CurrentListOfSpeakersSlide(CurrentListOfSpeakersMetaClass):
"""
Slide for the current list of speakers.
"""
name = 'agenda/current-list-of-speakers'
class CurrentListOfSpeakersOverlaySlide(CurrentListOfSpeakersMetaClass):
"""
List of speakers overlay.
Subclass of ListOfSpeakers
"""
name = 'agenda/current-list-of-speakers-overlay'

View File

@ -289,101 +289,73 @@ 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', [ .factory('CurrentListOfSpeakersItem', [
'Projector', '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.
'MotionBlock', // TODO: Remove this after refactoring of data loading on start.
'Agenda', 'Agenda',
function (Projector, Assignment, Topic, Motion, MotionBlock, Agenda) { function (Projector, Agenda) {
return { return {
getItem: function (projectorId) { getItem: function (projectorId) {
var elementPromise; var projector = Projector.get(projectorId), item;
return Projector.find(projectorId).then(function (projector) { if (projector) {
// scan all elements
_.forEach(projector.elements, function(element) { _.forEach(projector.elements, function(element) {
switch(element.name) { if (element.agenda_item_id) {
case 'motions/motion': item = Agenda.get(element.agenda_item_id);
elementPromise = Motion.find(element.id).then(function(motion) {
return Motion.loadRelations(motion, 'agenda_item').then(function() {
return motion.agenda_item;
});
});
break;
case 'motions/motion-block':
elementPromise = MotionBlock.find(element.id).then(function(motionBlock) {
return MotionBlock.loadRelations(motionBlock, 'agenda_item').then(function() {
return motionBlock.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; }
}); return item;
} }
}; };
} }
]) ])
.factory('ListOfSpeakersOverlay', [ .factory('CurrentListOfSpeakersSlide', [
'$http', '$http',
'Projector', 'Projector',
'gettextCatalog', function($http, Projector) {
'gettext', var name = 'agenda/current-list-of-speakers';
function($http, Projector, gettextCatalog, gettext) {
var name = 'agenda/current-list-of-speakers-overlay';
return { return {
name: name,
verboseName: gettext('List of speakers overlay'),
project: function (projectorId, overlay) { project: function (projectorId, overlay) {
var isProjectedId = this.isProjected(overlay); var isProjected = this.isProjectedWithOverlayStatus();
if (isProjectedId.length > 0) { _.forEach(isProjected, function (mapping) {
// Deactivate $http.post('/rest/core/projector/' + mapping.projectorId + '/deactivate_elements/',
var projector = Projector.get(isProjectedId[0]); [mapping.uuid]
var uuid; );
_.forEach(projector.elements, function (element) {
if (element.name == 'agenda/current-list-of-speakers-overlay') {
uuid = element.uuid;
}
}); });
$http.post('/rest/core/projector/' + isProjectedId + '/deactivate_elements/',
[uuid]); // The slide was projected, if the id matches. If the overlay is given, also
// the overlay is checked
var wasProjectedBefore = _.some(isProjected, function (mapping) {
var value = (mapping.projectorId === projectorId);
if (overlay !== undefined) {
value = value && (mapping.overlay === overlay);
} }
// Activate, if the projector_id is a new projector. return value;
if (isProjectedId != projectorId) { });
overlay = overlay || false; // set overlay if it wasn't defined
if (!wasProjectedBefore) {
var activate = function () {
return $http.post( return $http.post(
'/rest/core/projector/' + projectorId + '/activate_elements/', '/rest/core/projector/' + projectorId + '/activate_elements/',
[{name: 'agenda/current-list-of-speakers-overlay',stable: true}]); [{name: name,
stable: overlay, // if this is an overlay, it should not be
// removed by changing the main content
overlay: overlay}]
);
};
if (!overlay) {
// clear all elements on this projector, because we are _not_ using the overlay.
$http.post('/rest/core/projector/' + projectorId + '/clear_elements/').then(activate);
} else {
activate();
}
} }
}, },
isProjected: function () { isProjected: function () {
// Returns the ids of all projectors with an agenda-item element. Else return an empty list. // Returns the ids of all projectors with an agenda-item element. Else return an empty list.
var predicate = function (element) { var predicate = function (element) {
var value; return element.name === name;
value = element.name == 'agenda/current-list-of-speakers-overlay';
return value;
}; };
var isProjectedIds = []; var isProjectedIds = [];
Projector.getAll().forEach(function (projector) { Projector.getAll().forEach(function (projector) {
@ -392,7 +364,23 @@ angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users'])
} }
}); });
return isProjectedIds; return isProjectedIds;
},
// Returns a list of mappings between pojector id, overlay and uuid.
isProjectedWithOverlayStatus: function () {
var mapping = [];
_.forEach(Projector.getAll(), function (projector) {
_.forEach(projector.elements, function (element, uuid) {
if (element.name === name) {
mapping.push({
projectorId: projector.id,
uuid: uuid,
overlay: element.overlay || false,
});
} }
});
});
return mapping;
},
}; };
} }
]) ])

View File

@ -16,9 +16,6 @@ angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda'])
slidesProvider.registerSlide('agenda/current-list-of-speakers', { slidesProvider.registerSlide('agenda/current-list-of-speakers', {
template: 'static/templates/agenda/slide-current-list-of-speakers.html', template: 'static/templates/agenda/slide-current-list-of-speakers.html',
}); });
slidesProvider.registerSlide('agenda/current-list-of-speakers-overlay', {
template: 'static/templates/agenda/slide-current-list-of-speakers-overlay.html',
});
} }
]) ])
@ -27,7 +24,9 @@ angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda'])
'Agenda', 'Agenda',
'CurrentListOfSpeakersItem', 'CurrentListOfSpeakersItem',
'Config', 'Config',
function ($scope, Agenda, CurrentListOfSpeakersItem, Config) { 'Projector',
function ($scope, Agenda, CurrentListOfSpeakersItem, Config, Projector) {
$scope.overlay = $scope.element.overlay;
// Watch for changes in the current list of speakers reference // Watch for changes in the current list of speakers reference
$scope.$watch(function () { $scope.$watch(function () {
return Config.lastModified('projector_currentListOfSpeakers_reference'); return Config.lastModified('projector_currentListOfSpeakers_reference');
@ -35,6 +34,12 @@ angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda'])
$scope.currentListOfSpeakersReference = $scope.config('projector_currentListOfSpeakers_reference'); $scope.currentListOfSpeakersReference = $scope.config('projector_currentListOfSpeakers_reference');
$scope.updateCurrentListOfSpeakers(); $scope.updateCurrentListOfSpeakers();
}); });
// Watch for changes in the referenced projector
$scope.$watch(function () {
return Projector.lastModified($scope.currentListOfSpeakersReference);
}, function () {
$scope.updateCurrentListOfSpeakers();
});
// Watch for changes in the current item. // Watch for changes in the current item.
$scope.$watch(function () { $scope.$watch(function () {
return Agenda.lastModified(); return Agenda.lastModified();
@ -42,12 +47,7 @@ angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda'])
$scope.updateCurrentListOfSpeakers(); $scope.updateCurrentListOfSpeakers();
}); });
$scope.updateCurrentListOfSpeakers = function () { $scope.updateCurrentListOfSpeakers = function () {
var itemPromise = CurrentListOfSpeakersItem.getItem($scope.currentListOfSpeakersReference); $scope.agendaItem = CurrentListOfSpeakersItem.getItem($scope.currentListOfSpeakersReference);
if (itemPromise) {
itemPromise.then(function(item) {
$scope.agendaItem = item;
});
}
}; };
} }
]) ])

View File

@ -55,7 +55,7 @@ angular.module('OpenSlidesApp.agenda.site', [
}) })
.state('agenda.current-list-of-speakers', { .state('agenda.current-list-of-speakers', {
url: '/speakers', url: '/speakers',
controller: 'ListOfSpeakersViewCtrl', controller: 'CurrentListOfSpeakersViewCtrl',
data: { data: {
title: gettext('Current list of speakers'), title: gettext('Current list of speakers'),
}, },
@ -588,7 +588,7 @@ angular.module('OpenSlidesApp.agenda.site', [
} }
]) ])
.controller('ListOfSpeakersViewCtrl', [ .controller('CurrentListOfSpeakersViewCtrl', [
'$scope', '$scope',
'$state', '$state',
'$http', '$http',
@ -596,7 +596,9 @@ angular.module('OpenSlidesApp.agenda.site', [
'ProjectionDefault', 'ProjectionDefault',
'Config', 'Config',
'CurrentListOfSpeakersItem', 'CurrentListOfSpeakersItem',
function($scope, $state, $http, Projector, ProjectionDefault, Config, CurrentListOfSpeakersItem) { 'CurrentListOfSpeakersSlide',
function($scope, $state, $http, Projector, ProjectionDefault, Config, CurrentListOfSpeakersItem,
CurrentListOfSpeakersSlide) {
// Watch for changes in the current list of speakers reference // Watch for changes in the current list of speakers reference
$scope.$watch(function () { $scope.$watch(function () {
return Config.lastModified('projector_currentListOfSpeakers_reference'); return Config.lastModified('projector_currentListOfSpeakers_reference');
@ -608,52 +610,44 @@ angular.module('OpenSlidesApp.agenda.site', [
return Projector.lastModified(); return Projector.lastModified();
}, function() { }, function() {
$scope.projectors = Projector.getAll(); $scope.projectors = Projector.getAll();
$scope.updateCurrentListOfSpeakers(); if ($scope.projectors.length === 1) {
var projectiondefault = ProjectionDefault.filter({name: 'agenda_current_list_of_speakers'})[0]; $scope.currentListOfSpeakersAsOverlay = true;
if (projectiondefault) {
$scope.defaultProjectorId = projectiondefault.projector_id;
} }
$scope.updateCurrentListOfSpeakers();
$scope.listOfSpeakersDefaultProjectorId = ProjectionDefault.filter({name: 'agenda_current_list_of_speakers'})[0].projector_id;
}); });
$scope.updateCurrentListOfSpeakers = function () { $scope.updateCurrentListOfSpeakers = function () {
var itemPromise = CurrentListOfSpeakersItem.getItem($scope.currentListOfSpeakersReference); $scope.agendaItem = CurrentListOfSpeakersItem.getItem($scope.currentListOfSpeakersReference);
if (itemPromise) {
itemPromise.then(function(item) {
$scope.AgendaItem = item;
});
}
};
// Project current list of speakers
// same logic as in core/base.js
$scope.projectCurrentLoS = function (projectorId) {
var isCurrentLoSProjectedIds = $scope.isCurrentLoSProjected($scope.mainListTree);
_.forEach(isCurrentLoSProjectedIds, function (id) {
$http.post('/rest/core/projector/' + id + '/clear_elements/');
});
if (_.indexOf(isCurrentLoSProjectedIds, projectorId) == -1) {
$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 ids of all projectors with an element with the name
// 'agenda/current-list-of-speakers'. Elsewise returns an empty list.
var projectorIds = [];
$scope.projectors.forEach(function (projector) {
var key = _.findKey(projector.elements, function (element) {
return element.name == 'agenda/current-list-of-speakers';
});
if (typeof key === 'string') {
projectorIds.push(projector.id);
}
});
return projectorIds;
}; };
// 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() {
$state.go('agenda.item.detail', {id: $scope.AgendaItem.id}); if ($scope.agendaItem) {
$state.go('agenda.item.detail', {id: $scope.agendaItem.id});
}
};
$scope.currentListOfSpeakers = CurrentListOfSpeakersSlide;
// Set the current overlay status
if ($scope.currentListOfSpeakers.isProjected().length) {
var isProjected = $scope.currentListOfSpeakers.isProjectedWithOverlayStatus();
$scope.currentListOfSpeakersAsOverlay = isProjected[0].overlay;
} else {
$scope.currentListOfSpeakersAsOverlay = true;
}
$scope.currentListOfSpeakersItem = function () {
return CurrentListOfSpeakersItem.getItem($scope.currentListOfSpeakersReference);
};
$scope.setOverlay = function (overlay) {
$scope.currentListOfSpeakersAsOverlay = overlay;
var isProjected = $scope.currentListOfSpeakers.isProjectedWithOverlayStatus();
if (isProjected.length) {
_.forEach(isProjected, function (mapping) {
if (mapping.overlay != overlay) { // change the overlay if it is different
$scope.currentListOfSpeakers.project(mapping.projectorId, overlay);
}
});
}
}; };
} }
]) ])

View File

@ -7,41 +7,59 @@
</a> </a>
<!-- manage list of speakers --> <!-- manage list of speakers -->
<button os-perms="agenda.can_manage" <button os-perms="agenda.can_manage"
ng-disabled="!currentListOfSpeakersItem()"
ng-click="goToListOfSpeakers()" ng-click="goToListOfSpeakers()"
class="btn btn-sm btn-default"> class="btn btn-sm btn-default">
<i class="fa fa-microphone"></i> <i class="fa fa-microphone"></i>
<translate>Manage list</translate> <translate>Manage list</translate>
</button> </button>
<!-- project -->
<div os-perms="core.can_manage_projector" class="btn-group" uib-dropdown <!-- Current list of speakers projector button -->
uib-tooltip="{{ 'Projector' | translate }} {{ isCurrentLoSProjected()[0] || '' }}" <div class="btn-group button" uib-dropdown
tooltip-enable="isCurrentLoSProjected().length"> uib-tooltip="{{ 'Project the current list of speakers' | translate }}"
os-perms="core.can_manage_projector">
<button type="button" class="btn btn-default btn-sm" <button type="button" class="btn btn-default btn-sm"
title="{{ 'Project current list of speakers' | translate }}" title="{{ 'Project current list of speakers' | translate }}"
ng-click="projectCurrentLoS(defaultProjectorId)" ng-click="currentListOfSpeakers.project(listOfSpeakersDefaultProjectorId, currentListOfSpeakersAsOverlay)"
ng-class="{ 'btn-primary': isCurrentLoSProjected().length && inArray(isCurrentLoSProjected(), defaultProjectorId) }"> ng-class="{ 'btn-primary': currentListOfSpeakers.isProjected().length && inArray(currentListOfSpeakers.isProjected(), listOfSpeakersDefaultProjectorId)}">
<i class="fa fa-video-camera"></i> <i class="fa fa-video-camera"></i>
<translate>Current list of speakers</translate> <translate>Current list of speakers</translate>
</button> </button>
<button type="button" class="btn btn-default btn-sm" uib-dropdown-toggle <button type="button" class="btn btn-default btn-sm"
ng-class="{ 'btn-primary': isCurrentLoSProjected().length && !inArray(isCurrentLoSProjected(), defaultProjectorId) }"> ng-if="projectors.length > 1"
uib-dropdown-toggle
ng-class="{ 'btn-primary': currentListOfSpeakers.isProjected().length && !inArray(currentListOfSpeakers.isProjected(), listOfSpeakersDefaultProjectorId)}">
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu" role="menu" aria-labelledby="split-button"> <ul class="dropdown-menu" role="menu" aria-labelledby="split-button" ng-if="projectors.length > 1">
<li role="menuitem">
<a href="" ng-click="setOverlay(false); $event.stopPropagation();">
<i class="fa" ng-class="currentListOfSpeakersAsOverlay ? 'fa-circle-o' : 'fa-check-circle-o'"></i>
<translate>Project as slide</translate>
</a>
</li>
<li role="menuitem">
<a href="" ng-click="setOverlay(true); $event.stopPropagation();">
<i class="fa" ng-class="currentListOfSpeakersAsOverlay ? 'fa-check-circle-o' : 'fa-circle-o'"></i>
<translate>Project as overlay</translate>
</a>
</li>
<li class="divider"></li>
<li role="menuitem" ng-repeat="projector in projectors | orderBy:'id'"> <li role="menuitem" ng-repeat="projector in projectors | orderBy:'id'">
<a href="" ng-click="projectCurrentLoS(projector.id)" <a href="" ng-click="currentListOfSpeakers.project(projector.id, currentListOfSpeakersAsOverlay)"
ng-class="{ 'projected': inArray(isCurrentLoSProjected(), projector.id) }"> ng-class="{ 'projected': inArray(currentListOfSpeakers.isProjected(), projector.id) }">
<i class="fa fa-video-camera" ng-show="inArray(isCurrentLoSProjected(), projector.id) "></i> <i class="fa fa-video-camera" ng-show="inArray(currentListOfSpeakers.isProjected(), projector.id)"></i>
{{ projector.name | translate }} {{ projector.name | translate }}
<span ng-if="projector.id == defaultProjectorId">(<translate>Default</translate>)</span> <span ng-if="projector.id == listOfSpeakersDefaultProjectorId">(<translate>Default</translate>)</span>
</a> </a>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
<h1 translate>Current list of speakers</h1> <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>
@ -50,16 +68,16 @@
<div class="details"> <div class="details">
<!-- 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">
{{ speaker.user.get_full_name() }} {{ speaker.user.get_full_name() }}
<!-- Current speaker --> <!-- Current speaker -->
<p ng-repeat="speaker in currentspeakers = (AgendaItem.speakers| filter: {end_time: null, begin_time: '!!'})" <p ng-repeat="speaker in currentspeakers = (agendaItem.speakers| filter: {end_time: null, begin_time: '!!'})"
class="currentSpeaker"> class="currentSpeaker">
<i class="fa fa-microphone fa-lg"></i> {{ speaker.user.get_full_name() }} <i class="fa fa-microphone fa-lg"></i> {{ speaker.user.get_full_name() }}
<!-- Next speakers --> <!-- Next speakers -->
<ol class="nextSpeakers"> <ol class="nextSpeakers">
<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>

View File

@ -0,0 +1,30 @@
<div id="speakerbox">
<h3 translate>List of speakers</h3>
<!-- 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() }}
</p>
<!-- Current speaker -->
<p ng-repeat="speaker in agendaItem.speakers | filter: {end_time: null, begin_time: '!!'} "
class="currentSpeaker">
<i class="fa fa-microphone fa-lg"></i> {{ speaker.user.get_full_name() }}
</p>
<!-- Next speakers -->
<ol class="nextSpeakers">
<li ng-repeat="speaker in nextSpeakers = (agendaItem.speakers
| filter: {begin_time: null})
| orderBy:'weight'
| limitTo: 3">
{{ speaker.user.get_full_name() }}
</li>
</ol>
<p ng-if="nextSpeakers.length > 3" class="lastSpeakers">
<i>+ {{ nextSpeakers.length - 3 }}</i>
</p>
</div>

View File

@ -0,0 +1,33 @@
<div class="content scrollcontent">
<div class="title">
<h1 translate>Current list of speakers</h1>
<h2> {{ agendaItem.getTitle() }}
<span class="slimlabel label label-danger ng-scope"
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() }}
</p>
<!-- 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() }}
</p>
<!-- Next speakers -->
<ol class="nextSpeakers">
<li ng-repeat="speaker in agendaItem.speakers
| filter: {begin_time: null}
| orderBy:'weight'">
{{ speaker.user.get_full_name() }}
</li>
</ol>
</div>

View File

@ -1,19 +0,0 @@
<div ng-controller="SlideCurrentListOfSpeakersCtrl">
<div ng-if="agendaItem.speakers && agendaItem.speakers.length">
<div id="speakerbox">
<h3 translate>List of speakers</h3>
<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() }}
<p ng-repeat="speaker in agendaItem.speakers | filter: {end_time: null, begin_time: '!!'} "
class="currentSpeaker">
<i class="fa fa-microphone fa-lg"></i> {{ speaker.user.get_full_name() }}
<ol class="nextSpeakers">
<li ng-repeat="speaker in nextSpeakers = (agendaItem.speakers | filter: {begin_time: null}) | orderBy:'weight' | limitTo: 3">
{{ speaker.user.get_full_name() }}
</ol>
<p ng-if="nextSpeakers.length > 3" class="lastSpeakers">
<i>+ {{ nextSpeakers.length - 3 }}</i>
</div>
</div>

View File

@ -1,23 +1,8 @@
<div ng-controller="SlideCurrentListOfSpeakersCtrl" class="content scrollcontent"> <div ng-controller="SlideCurrentListOfSpeakersCtrl">
<div class="title">
<h1 translate>Current list of speakers</h1> <ng-include src="'static/templates/agenda/partial-slide-current-list-of-speakers.html'"
<h2> {{ agendaItem.getTitle() }} ng-if="!overlay"></ng-include>
<span class="slimlabel label label-danger ng-scope" style="" <ng-include src="'static/templates/agenda/partial-slide-current-list-of-speakers-overlay.html'"
ng-if="agendaItem.speaker_list_closed" translate>Closed</span> ng-if="overlay"></ng-include>
</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> </div>

View File

@ -230,9 +230,10 @@ h3 {
position: absolute; position: absolute;
background: #d3d3d3; background: #d3d3d3;
border-radius: 7px; border-radius: 7px;
border: 1px solid; border: 1px solid #999;
padding: 3px 7px 10px 19px; padding: 0px 7px 10px 19px;
z-index: 99; z-index: 99;
box-shadow: 3px 3px 10px 1px rgba(0,0,0,0.5);
} }
ul, ol { ul, ol {
margin: 0 0 10px 2em; margin: 0 0 10px 2em;

View File

@ -712,7 +712,8 @@ angular.module('OpenSlidesApp.core', [
value.name != 'agenda/item-list' && value.name != 'agenda/item-list' &&
value.name != 'core/clock' && value.name != 'core/clock' &&
value.name != 'core/countdown' && value.name != 'core/countdown' &&
value.name != 'core/message' ) { value.name != 'core/message' &&
value.name != 'agenda/current-list-of-speakers' ) {
return_dict = { return_dict = {
'state': value.name.replace('/', '.')+'.detail.update', 'state': value.name.replace('/', '.')+'.detail.update',
'param': {id: value.id} 'param': {id: value.id}

View File

@ -1036,7 +1036,7 @@ angular.module('OpenSlidesApp.core.site', [
'Config', 'Config',
'Projector', 'Projector',
'CurrentListOfSpeakersItem', 'CurrentListOfSpeakersItem',
'ListOfSpeakersOverlay', 'CurrentListOfSpeakersSlide',
'ProjectionDefault', 'ProjectionDefault',
'ProjectorMessage', 'ProjectorMessage',
'Countdown', 'Countdown',
@ -1044,7 +1044,7 @@ angular.module('OpenSlidesApp.core.site', [
'ngDialog', 'ngDialog',
'ProjectorMessageForm', 'ProjectorMessageForm',
function($scope, $http, $interval, $state, $q, $filter, Config, Projector, CurrentListOfSpeakersItem, function($scope, $http, $interval, $state, $q, $filter, Config, Projector, CurrentListOfSpeakersItem,
ListOfSpeakersOverlay, ProjectionDefault, ProjectorMessage, Countdown, gettextCatalog, CurrentListOfSpeakersSlide, ProjectionDefault, ProjectorMessage, Countdown, gettextCatalog,
ngDialog, ProjectorMessageForm) { ngDialog, ProjectorMessageForm) {
ProjectorMessage.bindAll({}, $scope, 'messages'); ProjectorMessage.bindAll({}, $scope, 'messages');
@ -1078,7 +1078,6 @@ angular.module('OpenSlidesApp.core.site', [
cancelIntervalTimers(); cancelIntervalTimers();
}); });
$scope.listofspeakers = ListOfSpeakersOverlay;
$scope.$watch(function () { $scope.$watch(function () {
return Projector.lastModified(); return Projector.lastModified();
}, function () { }, function () {
@ -1086,20 +1085,14 @@ angular.module('OpenSlidesApp.core.site', [
if (!$scope.active_projector) { if (!$scope.active_projector) {
$scope.changeProjector($filter('orderBy')($scope.projectors, 'id')[0]); $scope.changeProjector($filter('orderBy')($scope.projectors, 'id')[0]);
} }
if ($scope.projectors.length === 1) {
$scope.currentListOfSpeakersAsOverlay = true;
}
$scope.messageDefaultProjectorId = ProjectionDefault.filter({name: 'messages'})[0].projector_id; $scope.messageDefaultProjectorId = ProjectionDefault.filter({name: 'messages'})[0].projector_id;
$scope.countdownDefaultProjectorId = ProjectionDefault.filter({name: 'countdowns'})[0].projector_id; $scope.countdownDefaultProjectorId = ProjectionDefault.filter({name: 'countdowns'})[0].projector_id;
$scope.getDefaultOverlayProjector(); $scope.listOfSpeakersDefaultProjectorId = ProjectionDefault.filter({name: 'agenda_current_list_of_speakers'})[0].projector_id;
}); });
// gets the default projector where the current list of speakers overlay will be displayed
$scope.getDefaultOverlayProjector = function () {
var projectiondefault = ProjectionDefault.filter({name: 'agenda_current_list_of_speakers'})[0];
if (projectiondefault) {
$scope.listofSpeakersDefaultProjectorId = projectiondefault.projector_id;
} else {
$scope.listOfSpeakersDefaultProjectorId = 1;
}
};
// watch for changes in projector_broadcast and currentListOfSpeakersReference // watch for changes in projector_broadcast and currentListOfSpeakersReference
var last_broadcast; var last_broadcast;
$scope.$watch(function () { $scope.$watch(function () {
@ -1172,11 +1165,35 @@ angular.module('OpenSlidesApp.core.site', [
ProjectorMessage.destroy(message.id); ProjectorMessage.destroy(message.id);
}; };
/* Current list of speakers */
$scope.currentListOfSpeakers = CurrentListOfSpeakersSlide;
// Set the current overlay status
if ($scope.currentListOfSpeakers.isProjected().length) {
var isProjected = $scope.currentListOfSpeakers.isProjectedWithOverlayStatus();
$scope.currentListOfSpeakersAsOverlay = isProjected[0].overlay;
} else {
$scope.currentListOfSpeakersAsOverlay = true;
}
// go to the list of speakers(management) of the currently displayed list of speakers reference slide // go to the list of speakers(management) of the currently displayed list of speakers reference slide
$scope.goToListOfSpeakers = function() { $scope.goToListOfSpeakers = function() {
CurrentListOfSpeakersItem.getItem($scope.currentListOfSpeakersReference).then(function (success) { var item = $scope.currentListOfSpeakersItem();
$state.go('agenda.item.detail', {id: success.id}); if (item) {
$state.go('agenda.item.detail', {id: item.id});
}
};
$scope.currentListOfSpeakersItem = function () {
return CurrentListOfSpeakersItem.getItem($scope.currentListOfSpeakersReference);
};
$scope.setOverlay = function (overlay) {
$scope.currentListOfSpeakersAsOverlay = overlay;
var isProjected = $scope.currentListOfSpeakers.isProjectedWithOverlayStatus();
if (isProjected.length) {
_.forEach(isProjected, function (mapping) {
if (mapping.overlay != overlay) { // change the overlay if it is different
$scope.currentListOfSpeakers.project(mapping.projectorId, overlay);
}
}); });
}
}; };
} }
]) ])

View File

@ -268,14 +268,55 @@
<h4 translate>List of speakers</h4> <h4 translate>List of speakers</h4>
</a> </a>
<div uib-collapse="!isSpeakerList" ng-cloak> <div uib-collapse="!isSpeakerList" ng-cloak>
<projector-button model="listofspeakers" default-projector-id="listOfSpeakersDefaultProjectorId">
</projector-button> <!-- Current list of speakers projector button -->
<div class="btn-group" os-perms="agenda.can_manage"> <div class="btn-group button" uib-dropdown
<a ng-click="goToListOfSpeakers()" class="btn btn-default btn-sm" uib-tooltip="{{ 'Project the current list of speakers' | translate }}"
title="{{ 'Manage current list of speakers' | translate}}"> os-perms="core.can_manage_projector">
<i class="fa fa-microphone"></i> <button type="button" class="btn btn-default btn-sm"
title="{{ 'Project current list of speakers' | translate }}"
ng-click="currentListOfSpeakers.project(listOfSpeakersDefaultProjectorId, currentListOfSpeakersAsOverlay)"
ng-class="{ 'btn-primary': currentListOfSpeakers.isProjected().length && inArray(currentListOfSpeakers.isProjected(), listOfSpeakersDefaultProjectorId)}">
<i class="fa fa-video-camera"></i>
<translate>Current list of speakers</translate>
</button>
<button type="button" class="btn btn-default btn-sm slimDropDown"
ng-if="projectors.length > 1"
uib-dropdown-toggle
ng-class="{ 'btn-primary': currentListOfSpeakers.isProjected().length && !inArray(currentListOfSpeakers.isProjected(), listOfSpeakersDefaultProjectorId)}">
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="split-button" ng-if="projectors.length > 1">
<li role="menuitem">
<a href="" ng-click="setOverlay(false); $event.stopPropagation();">
<i class="fa" ng-class="currentListOfSpeakersAsOverlay ? 'fa-circle-o' : 'fa-check-circle-o'"></i>
<translate>Project as slide</translate>
</a> </a>
</li>
<li role="menuitem">
<a href="" ng-click="setOverlay(true); $event.stopPropagation();">
<i class="fa" ng-class="currentListOfSpeakersAsOverlay ? 'fa-check-circle-o' : 'fa-circle-o'"></i>
<translate>Project as overlay</translate>
</a>
</li>
<li class="divider"></li>
<li role="menuitem" ng-repeat="projector in projectors | orderBy:'id'">
<a href="" ng-click="currentListOfSpeakers.project(projector.id, currentListOfSpeakersAsOverlay)"
ng-class="{ 'projected': inArray(currentListOfSpeakers.isProjected(), projector.id) }">
<i class="fa fa-video-camera" ng-show="inArray(currentListOfSpeakers.isProjected(), projector.id)"></i>
{{ projector.name | translate }}
<span ng-if="projector.id == listOfSpeakersDefaultProjectorId">(<translate>Default</translate>)</span>
</a>
</li>
</ul>
</div> </div>
<button os-perms="agenda.can_manage"
ng-disabled="!currentListOfSpeakersItem()"
ng-click="goToListOfSpeakers()" class="btn btn-default btn-sm"
uib-tooltip="{{ 'Manage the current list of speakers' | translate}}">
<i class="fa fa-microphone"></i>
</button>
</div> </div>
</div> </div>
</div><!-- end div ProjectorControlCtrl --> </div><!-- end div ProjectorControlCtrl -->