Several template improvements

- Move Languages services from site.js to base.js
  Now the detected browser language is used as projector language.
- Use verboseName instead of agendaSupplement.
- Show submitters on projector (load Users on Controller).
- Improve list of speakers (slide and item detail).
This commit is contained in:
Emanuel Schuetze 2016-01-13 20:27:07 +01:00
parent 82f5239b8c
commit bc60b7e7ca
11 changed files with 210 additions and 128 deletions

View File

@ -52,8 +52,8 @@ angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users'])
// in the DS store. // in the DS store.
title = this.title; title = this.title;
} }
if (this.getContentResource().agendaSupplement) { if (this.getContentResource().verboseName) {
title = gettextCatalog.getString(this.getContentResource().agendaSupplement) + ' ' + title; title = gettextCatalog.getString(this.getContentResource().verboseName) + ' ' + title;
} }
if (this.item_number) { if (this.item_number) {
title = this.item_number + ' ' + title; title = this.item_number + ' ' + title;

View File

@ -184,10 +184,11 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
'$filter', '$filter',
'$http', '$http',
'$state', '$state',
'operator',
'Agenda', 'Agenda',
'User', 'User',
'item', 'item',
function ($scope, $filter, $http, $state, Agenda, User, item) { function ($scope, $filter, $http, $state, operator, Agenda, User, item) {
Agenda.bindOne(item.id, $scope, 'item'); Agenda.bindOne(item.id, $scope, 'item');
User.bindAll({}, $scope, 'users'); User.bindAll({}, $scope, 'users');
$scope.speakerSelectBox = {}; $scope.speakerSelectBox = {};
@ -199,7 +200,6 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
$scope.speakers = $filter('orderBy')(item.speakers, 'weight'); $scope.speakers = $filter('orderBy')(item.speakers, 'weight');
}); });
// go to detail view of related item (content object) // go to detail view of related item (content object)
$scope.open = function (item) { $scope.open = function (item) {
$state.go(item.content_object.collection.replace('/','.')+'.detail', $state.go(item.content_object.collection.replace('/','.')+'.detail',
@ -242,6 +242,23 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
$scope.speakers = item.speakers; $scope.speakers = item.speakers;
}; };
// check if user is allowed to see 'add me' / 'remove me' button
$scope.isAllowed = function (action) {
var nextUsers = [];
var nextSpeakers = $filter('filter')($scope.speakers, {'begin_time': null});
angular.forEach(nextSpeakers, function (speaker) {
nextUsers.push(speaker.user_id);
});
if (action == 'add') {
return (operator.hasPerms('agenda.can_be_speaker') &&
!item.speaker_list_closed &&
$.inArray(operator.user.id, nextUsers) == -1);
}
if (action == 'remove') {
return ($.inArray(operator.user.id, nextUsers) != -1);
}
}
// begin speech of selected/next speaker // begin speech of selected/next speaker
$scope.beginSpeech = function (speakerId) { $scope.beginSpeech = function (speakerId) {
$http.put('/rest/agenda/item/' + item.id + '/speak/', {'speaker': speakerId}) $http.put('/rest/agenda/item/' + item.id + '/speak/', {'speaker': speakerId})

View File

@ -7,7 +7,7 @@
</a> </a>
<a href="" ng-click="open(item)" class="btn btn-sm btn-default"> <a href="" ng-click="open(item)" class="btn btn-sm btn-default">
<i class="fa fa-angle-double-left fa-lg"></i> <i class="fa fa-angle-double-left fa-lg"></i>
{{item.getContentResource().verboseName}} {{ item.getContentResource().verboseName }}
</a> </a>
<!-- project list of speakers --> <!-- project list of speakers -->
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm" <a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
@ -22,20 +22,25 @@
ng-click="item.project()" ng-click="item.project()"
title="{{ 'Project item' | translate }}"> title="{{ 'Project item' | translate }}">
<i class="fa fa-video-camera"></i> <i class="fa fa-video-camera"></i>
{{ item.getContentResource().verboseName }}
</a> </a>
</div> </div>
<h1>{{ item.getTitle() }}</h1> <h1>{{ item.getTitle() }}</h1>
<h2><translate>List of speakers</translate></h2> <h2>
<translate>List of speakers</translate>
<span ng-if="item.speaker_list_closed" class="slimlabel label label-danger"
translate>Closed</span>
</h2>
</div> </div>
</div> </div>
<div class="details"> <div class="details listOfSpeakers">
<div class="pull-right"> <div class="pull-right">
<span os-perms="agenda.can_manage"> <span os-perms="agenda.can_manage">
<button ng-if="item.speaker_list_closed" ng-click="closeList(false)" <button ng-if="item.speaker_list_closed" ng-click="closeList(false)"
class="btn btn-sm btn-default"> class="btn btn-sm btn-default">
<i class="fa fa-toggle-off"></i> <i class="fa fa-toggle-off"></i>
<translate>Close</translate> <translate>Closed</translate>
</button> </button>
<button ng-if="!item.speaker_list_closed" ng-click="closeList(true)" <button ng-if="!item.speaker_list_closed" ng-click="closeList(true)"
class="btn btn-sm btn-default"> class="btn btn-sm btn-default">
@ -45,32 +50,30 @@
</span> </span>
</div> </div>
<!-- TODO: <!-- Start/Stop controls -->
* show only 'add me' OR 'remove me' button
-->
<div os-perms="agenda.can_manage"> <div os-perms="agenda.can_manage">
<button ng-click="beginSpeech()" <button ng-click="beginSpeech()"
class="btn btn-primary"> class="btn btn-sm btn-primary">
<i class="fa fa-play"></i> <i class="fa fa-microphone"></i>
<translate>Start next speaker</translate> <translate>Begin next speech</translate>
</button> </button>
<button ng-click="endSpeech()" <button ng-click="endSpeech()"
class="btn btn-default"> class="btn btn-sm btn-default">
<i class="fa fa-stop"></i> <i class="fa fa-microphone-slash"></i>
<translate>Stop current speaker</translate> <translate>End current speech</translate>
</button> </button>
</div> </div>
<div> <!-- Last speakers -->
<div class="spacer">
<h3 translate>Last speakers</h3>
<button ng-click="showOldSpeakers = !showOldSpeakers" <button ng-click="showOldSpeakers = !showOldSpeakers"
class="btn btn-sm btn-default spacer"> class="btn btn-xs btn-default">
<i ng-if="!showOldSpeakers" class="fa fa-toggle-off"></i> <translate ng-if="!showOldSpeakers">Show</translate>
<i ng-if="showOldSpeakers"class="fa fa-toggle-on"></i> <translate ng-if="showOldSpeakers">Hide</translate>
<translate>Show all speakers</translate>
</button> </button>
<div uib-collapse="!showOldSpeakers"> <div uib-collapse="!showOldSpeakers">
<h3 translate>Old speakers:</h3> <ol class="indentation-lg">
<ol>
<li ng-repeat="speaker in item.speakers | filter: {end_time: '!!'}"> <li ng-repeat="speaker in item.speakers | filter: {end_time: '!!'}">
{{ speaker.user.get_full_name() }} {{ speaker.user.get_full_name() }}
<small class="grey"> <small class="grey">
@ -78,46 +81,50 @@
{{ speaker.end_time | date:'yyyy-MM-dd HH:mm:ss' }}] {{ speaker.end_time | date:'yyyy-MM-dd HH:mm:ss' }}]
</small> </small>
<button os-perms="agenda.can_manage" ng-click="removeSpeaker(speaker.id)" <button os-perms="agenda.can_manage" ng-click="removeSpeaker(speaker.id)"
class="btn btn-default btn-xs"> class="btn btn-default btn-xs" title="{{ 'Remove' | translate }}">
<i class="fa fa-times"></i> <i class="fa fa-times"></i>
</button> </button>
</ol> </ol>
</div> </div>
</div> </div>
<h3 translate class="">Current speaker:</h3> <!-- Current speaker -->
<strong ng-repeat="speaker in item.speakers | filter: {end_time: null, begin_time: '!!'}"> <h3 translate class="">Current speaker</h3>
<strong class="indentation" ng-repeat="speaker in item.speakers |
filter: {end_time: null, begin_time: '!!'}">
{{ speaker.user.get_full_name() }} {{ speaker.user.get_full_name() }}
<button os-perms="agenda.can_manage" ng-click="removeSpeaker(speaker.id)"
class="btn btn-default btn-xs">
<i class="fa fa-times"></i>
</button>
<button os-perms="agenda.can_manage" ng-click="endSpeech()" <button os-perms="agenda.can_manage" ng-click="endSpeech()"
class="btn btn-default btn-xs"> class="btn btn-default btn-xs" title="{{ 'End speech' | translate }}">
<i class="fa fa-stop"></i> <i class="fa fa-microphone-slash"></i>
</button>
<button os-perms="agenda.can_manage" ng-click="removeSpeaker(speaker.id)"
class="btn btn-default btn-xs" title="{{ 'Remove' | translate }}">
<i class="fa fa-times"></i>
</button> </button>
</strong> </strong>
<h3 translate>Next speakers:</h3> <!-- Next speakers -->
<div class="row" ng-show="speakers.length > 0"> <h3 translate>Next speakers</h3>
<div ui-tree="treeOptions" class="col-sm-6"> <div ng-show="speakers.length > 0">
<div ui-tree="treeOptions" class="halfWidth">
<ol ui-tree-nodes="" ng-model="speakers"> <ol ui-tree-nodes="" ng-model="speakers">
<li ng-repeat="speaker in speakers | filter: {begin_time: null}" ui-tree-node> <li ng-repeat="speaker in speakers | filter: {begin_time: null}" ui-tree-node>
<i os-perms="agenda.can_manage" ui-tree-handle="" class="fa fa-arrows-v"></i> <i os-perms="agenda.can_manage" ui-tree-handle="" class="fa fa-arrows-v"></i>
{{ $index + 1 }}. {{ $index + 1 }}.
{{ speaker.user.get_full_name() }} {{ speaker.user.get_full_name() }}
<button os-perms="agenda.can_manage" ng-click="removeSpeaker(speaker.id)"
class="btn btn-default btn-xs">
<i class="fa fa-times"></i>
</button>
<button os-perms="agenda.can_manage" ng-click="beginSpeech(speaker.id)" <button os-perms="agenda.can_manage" ng-click="beginSpeech(speaker.id)"
class="btn btn-default btn-xs"> class="btn btn-default btn-xs" title="{{ 'Begin speech' | translate }}">
<i class="fa fa-play"></i> <i class="fa fa-microphone"></i>
</button>
<button os-perms="agenda.can_manage" ng-click="removeSpeaker(speaker.id)"
class="btn btn-default btn-xs" title="{{ 'Remove' | translate }}">
<i class="fa fa-times"></i>
</button> </button>
</ol> </ol>
</div> </div>
</div> </div>
<!-- Select speakers form -->
<div class="form-group spacer"> <div class="form-group spacer">
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}"> <alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
{{ alert.msg }} {{ alert.msg }}
@ -137,12 +144,12 @@
</a> </a>
</span> </span>
</div> </div>
<p os-perm="agenda.can_be_speaker"> <p class="spacer">
<button ng-click="addSpeaker()" class="btn btn-default"> <button ng-if="isAllowed('add')" ng-click="addSpeaker()" class="btn btn-default">
<i class="fa fa-plus"></i> <i class="fa fa-plus"></i>
<translate>Add me</translate> <translate>Add me</translate>
</button> </button>
<button ng-click="removeSpeaker()" class="btn btn-default"> <button ng-if="isAllowed('remove')" ng-click="removeSpeaker()" class="btn btn-default">
<i class="fa fa-minus"></i> <i class="fa fa-minus"></i>
<translate>Remove me</translate> <translate>Remove me</translate>
</button> </button>

View File

@ -1,19 +1,28 @@
<div ng-controller="SlideListOfSpeakersCtrl" class="content scrollcontent"> <div ng-controller="SlideListOfSpeakersCtrl" class="content scrollcontent">
<h1>
{{ item.title }} <!-- Title -->
<span translate>List of speakers</span> <div id="title">
<span ng-if="item.speaker_list_closed" class="label label-danger" translate>Closed</span> <h1>{{ item.getTitle() }}</h1>
</h1> <h2>
<!-- List of speakers --> <span translate>List of speakers</span>
<!-- TODO: show last old speakers --> <span ng-if="(item.speakers | filter: {begin_time: null}).length > 0">
&ndash; {{ (item.speakers | filter: {begin_time: null}).length }} <translate>speakers</translate>
<span ng-if="item.speaker_list_closed" class="slimlabel label label-danger" translate>Closed</span>
</h2>
</div>
<!-- Last speakers -->
<p ng-repeat="speaker in item.speakers | filter: {end_time: '!!', begin_time: '!!'} |
limitTo: config('agenda_show_last_speakers')" class="lastSpeakers">
{{ speaker.user.get_full_name() }}
<!-- Current speaker --> <!-- Current speaker -->
<p> <p ng-repeat="speaker in item.speakers | filter: {end_time: null, begin_time: '!!'}"
<strong ng-repeat="speaker in item.speakers | filter: {end_time: null, begin_time: '!!'}"> class="currentSpeaker">
{{ speaker.user.get_full_name() }} <i class="fa fa-microphone fa-lg"></i> {{ speaker.user.get_full_name() }}
</strong>
<!-- Next speakers --> <!-- Next speakers -->
<ol id="list_of_speakers"> <ol class="nextSpeakers">
<li ng-repeat="speaker in item.speakers | filter: {begin_time: null}"> <li ng-repeat="speaker in item.speakers | filter: {begin_time: null}">
{{ speaker.user.get_full_name() }} {{ speaker.user.get_full_name() }}
</ol> </ol>

View File

@ -80,7 +80,6 @@ angular.module('OpenSlidesApp.assignments', [])
name: name, name: name,
useClass: jsDataModel, useClass: jsDataModel,
verboseName: gettext('Election'), verboseName: gettext('Election'),
agendaSupplement: this.verboseName,
phases: phases, phases: phases,
getPhases: function () { getPhases: function () {
if (!this.phases) { if (!this.phases) {

View File

@ -348,7 +348,7 @@ img {
.col2 .projector_min .icon a { .col2 .projector_min .icon a {
color: #fff; color: #fff;
display: block; display: block;
} }
.col2 .projector_full { .col2 .projector_full {
margin-left: 30px; margin-left: 30px;
@ -534,6 +534,26 @@ img {
padding-left: 20px; padding-left: 20px;
} }
.slimlabel.label {
padding: 0px 10px;
}
.indentation, .indentation20 {
margin-left: 20px;
}
.indentation-lg {
margin-left: 35px !important;
}
.halfWidth {
width: 50%;
}
.listOfSpeakers h3 {
padding-bottom: 0;
}
.smallhr { .smallhr {
margin-top: 5px; margin-top: 5px;
margin-bottom: 5px; margin-bottom: 5px;

View File

@ -60,7 +60,6 @@ body{
bottom: 0; bottom: 0;
height: 35px; height: 35px;
color: #F5F5F5; color: #F5F5F5;
opacity: 0.8;
width: 100%; width: 100%;
font-size: 16px; font-size: 16px;
padding-left: 50px; padding-left: 50px;
@ -69,6 +68,9 @@ body{
overflow: hidden; overflow: hidden;
text-align: right; text-align: right;
} }
#footer span {
opacity: 0.8;
}
/*** CONTENT with base style elements ***/ /*** CONTENT with base style elements ***/
.content { .content {
@ -252,4 +254,28 @@ tr.elected td {
font-size: 140%; font-size: 140%;
} }
.spacer-top { .spacer-top {
margin-top: 25px; margin-top: 25px;
}
/*** List of speakers ***/
.slimlabel.label {
padding: 0px 10px;
}
.lastSpeakers {
color: #9a9898;
margin-left: 27px;
}
.currentSpeaker {
font-weight: bold;
margin-left: 0px;
}
.currentSpeaker i {
padding-right: 5px;
}
.nextSpeakers {
margin-left: 8px;
}
.nextSpeakers li {
line-height: 150%;
}

View File

@ -66,6 +66,69 @@ angular.module('OpenSlidesApp.core', [
} }
]) ])
// gets all in OpenSlides available languages
.factory('Languages', [
'gettext',
'gettextCatalog',
function (gettext, gettextCatalog) {
return {
// get all available languages
getLanguages: function () {
var current = gettextCatalog.getCurrentLanguage();
// Define here new languages...
var languages = [
{ code: 'en', name: gettext('English') },
{ code: 'de', name: gettext('German') },
{ code: 'fr', name: gettext('French') }
];
angular.forEach(languages, function (language) {
if (language.code == current)
language.selected = true;
});
return languages
},
// get detected browser language code
getBrowserLanguage: function () {
var lang = navigator.language || navigator.userLanguage;
if (lang.indexOf('-') !== -1)
lang = lang.split('-')[0];
if (lang.indexOf('_') !== -1)
lang = lang.split('_')[0];
return lang;
},
// set current language and return updated languages object array
setCurrentLanguage: function (lang) {
var languages = this.getLanguages();
angular.forEach(languages, function (language) {
language.selected = false;
if (language.code == lang) {
language.selected = true;
gettextCatalog.setCurrentLanguage(lang);
if (lang != 'en') {
gettextCatalog.loadRemote("static/i18n/" + lang + ".json");
}
}
});
return languages;
}
}
}
])
// set browser language as default language for OpenSlides
.run([
'gettextCatalog',
'Languages',
function(gettextCatalog, Languages) {
// set detected browser language as default language (fallback: 'en')
Languages.setCurrentLanguage(Languages.getBrowserLanguage());
// Set this to true for debug. Helps to find untranslated strings by
// adding "[MISSING]:".
gettextCatalog.debug = false;
}
])
.run(['DS', 'autoupdate', function(DS, autoupdate) { .run(['DS', 'autoupdate', function(DS, autoupdate) {
autoupdate.on_message(function(data) { autoupdate.on_message(function(data) {
// TODO: when MODEL.find() is called after this // TODO: when MODEL.find() is called after this

View File

@ -63,19 +63,6 @@ angular.module('OpenSlidesApp.core.site', [
} }
]) ])
// set browser language as default language for OpenSlides
.run([
'gettextCatalog',
'Languages',
function(gettextCatalog, Languages) {
// set detected browser language as default language (fallback: 'en')
Languages.setCurrentLanguage(Languages.getBrowserLanguage());
// Set this to true for debug. Helps to find untranslated strings by
// adding "[MISSING]:".
gettextCatalog.debug = false;
}
])
.config([ .config([
'mainMenuProvider', 'mainMenuProvider',
'gettext', 'gettext',
@ -367,55 +354,6 @@ angular.module('OpenSlidesApp.core.site', [
} }
]) ])
// gets all in OpenSlides available languages
.factory('Languages', [
'gettext',
'gettextCatalog',
function (gettext, gettextCatalog) {
return {
// get all available languages
getLanguages: function () {
var current = gettextCatalog.getCurrentLanguage();
// Define here new languages...
var languages = [
{ code: 'en', name: gettext('English') },
{ code: 'de', name: gettext('German') },
{ code: 'fr', name: gettext('French') }
];
angular.forEach(languages, function (language) {
if (language.code == current)
language.selected = true;
});
return languages
},
// get detected browser language code
getBrowserLanguage: function () {
var lang = navigator.language || navigator.userLanguage;
if (lang.indexOf('-') !== -1)
lang = lang.split('-')[0];
if (lang.indexOf('_') !== -1)
lang = lang.split('_')[0];
return lang;
},
// set current language and return updated languages object array
setCurrentLanguage: function (lang) {
var languages = this.getLanguages();
angular.forEach(languages, function (language) {
language.selected = false;
if (language.code == lang) {
language.selected = true;
gettextCatalog.setCurrentLanguage(lang);
if (lang != 'en') {
gettextCatalog.loadRemote("static/i18n/" + lang + ".json");
}
}
});
return languages;
}
}
}
])
.controller("LanguageCtrl", function ($scope, gettextCatalog, Languages, filterFilter) { .controller("LanguageCtrl", function ($scope, gettextCatalog, Languages, filterFilter) {
$scope.languages = Languages.getLanguages(); $scope.languages = Languages.getLanguages();
$scope.selectedLanguage = filterFilter($scope.languages, {selected: true}); $scope.selectedLanguage = filterFilter($scope.languages, {selected: true});
@ -689,10 +627,13 @@ angular.module('OpenSlidesApp.core.site', [
}; };
$scope.editCurrentSlide = function () { $scope.editCurrentSlide = function () {
$.each(Projector.get(1).elements, function(key, value) { $.each(Projector.get(1).elements, function(key, value) {
if (value.name != 'core/clock' && if (value.name == 'agenda/list-of-speakers') {
$state.go('agenda.item.detail', {id: value.id});
} else if (
value.name != 'core/clock' &&
value.name != 'core/countdown' && value.name != 'core/countdown' &&
value.name != 'core/message' ) { value.name != 'core/message' ) {
$state.go(value.name.replace('/', '.')+'.detail.update', {id: value.id }); $state.go(value.name.replace('/', '.')+'.detail.update', {id: value.id});
} }
}); });
}; };

View File

@ -169,7 +169,6 @@ angular.module('OpenSlidesApp.motions', ['OpenSlidesApp.users'])
name: name, name: name,
useClass: jsDataModel, useClass: jsDataModel,
verboseName: gettext('Motion'), verboseName: gettext('Motion'),
agendaSupplement: this.verboseName,
methods: { methods: {
getResourceName: function () { getResourceName: function () {
return name; return name;
@ -200,7 +199,7 @@ angular.module('OpenSlidesApp.motions', ['OpenSlidesApp.users'])
if (this.identifier) { if (this.identifier) {
value = ' ' + this.identifier; value = ' ' + this.identifier;
} }
return value + ': ' + this.getTitle(); return "Motion " + value + ': ' + this.getTitle();
}, },
isAllowed: function (action) { isAllowed: function (action) {
/* /*

View File

@ -10,12 +10,13 @@ angular.module('OpenSlidesApp.motions.projector', ['OpenSlidesApp.motions'])
}); });
}) })
.controller('SlideMotionCtrl', function($scope, Motion) { .controller('SlideMotionCtrl', function($scope, Motion, 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.
var id = $scope.element.id; var id = $scope.element.id;
Motion.find(id); Motion.find(id);
User.findAll();
Motion.bindOne(id, $scope, 'motion'); Motion.bindOne(id, $scope, 'motion');
}); });