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.
title = this.title;
}
if (this.getContentResource().agendaSupplement) {
title = gettextCatalog.getString(this.getContentResource().agendaSupplement) + ' ' + title;
if (this.getContentResource().verboseName) {
title = gettextCatalog.getString(this.getContentResource().verboseName) + ' ' + title;
}
if (this.item_number) {
title = this.item_number + ' ' + title;

View File

@ -184,10 +184,11 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
'$filter',
'$http',
'$state',
'operator',
'Agenda',
'User',
'item',
function ($scope, $filter, $http, $state, Agenda, User, item) {
function ($scope, $filter, $http, $state, operator, Agenda, User, item) {
Agenda.bindOne(item.id, $scope, 'item');
User.bindAll({}, $scope, 'users');
$scope.speakerSelectBox = {};
@ -199,7 +200,6 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
$scope.speakers = $filter('orderBy')(item.speakers, 'weight');
});
// go to detail view of related item (content object)
$scope.open = function (item) {
$state.go(item.content_object.collection.replace('/','.')+'.detail',
@ -242,6 +242,23 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
$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
$scope.beginSpeech = function (speakerId) {
$http.put('/rest/agenda/item/' + item.id + '/speak/', {'speaker': speakerId})

View File

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

View File

@ -1,19 +1,28 @@
<div ng-controller="SlideListOfSpeakersCtrl" class="content scrollcontent">
<h1>
{{ item.title }}
<!-- Title -->
<div id="title">
<h1>{{ item.getTitle() }}</h1>
<h2>
<span translate>List of speakers</span>
<span ng-if="item.speaker_list_closed" class="label label-danger" translate>Closed</span>
</h1>
<!-- List of speakers -->
<!-- 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 -->
<p>
<strong ng-repeat="speaker in item.speakers | filter: {end_time: null, begin_time: '!!'}">
{{ speaker.user.get_full_name() }}
</strong>
<p ng-repeat="speaker in item.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 id="list_of_speakers">
<ol class="nextSpeakers">
<li ng-repeat="speaker in item.speakers | filter: {begin_time: null}">
{{ speaker.user.get_full_name() }}
</ol>

View File

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

View File

@ -534,6 +534,26 @@ img {
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 {
margin-top: 5px;
margin-bottom: 5px;

View File

@ -60,7 +60,6 @@ body{
bottom: 0;
height: 35px;
color: #F5F5F5;
opacity: 0.8;
width: 100%;
font-size: 16px;
padding-left: 50px;
@ -69,6 +68,9 @@ body{
overflow: hidden;
text-align: right;
}
#footer span {
opacity: 0.8;
}
/*** CONTENT with base style elements ***/
.content {
@ -253,3 +255,27 @@ tr.elected td {
}
.spacer-top {
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) {
autoupdate.on_message(function(data) {
// 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([
'mainMenuProvider',
'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) {
$scope.languages = Languages.getLanguages();
$scope.selectedLanguage = filterFilter($scope.languages, {selected: true});
@ -689,7 +627,10 @@ angular.module('OpenSlidesApp.core.site', [
};
$scope.editCurrentSlide = function () {
$.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/message' ) {
$state.go(value.name.replace('/', '.')+'.detail.update', {id: value.id});

View File

@ -169,7 +169,6 @@ angular.module('OpenSlidesApp.motions', ['OpenSlidesApp.users'])
name: name,
useClass: jsDataModel,
verboseName: gettext('Motion'),
agendaSupplement: this.verboseName,
methods: {
getResourceName: function () {
return name;
@ -200,7 +199,7 @@ angular.module('OpenSlidesApp.motions', ['OpenSlidesApp.users'])
if (this.identifier) {
value = ' ' + this.identifier;
}
return value + ': ' + this.getTitle();
return "Motion " + value + ': ' + this.getTitle();
},
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.
// Add it to the coresponding get_requirements method of the ProjectorElement
// class.
var id = $scope.element.id;
Motion.find(id);
User.findAll();
Motion.bindOne(id, $scope, 'motion');
});