diff --git a/openslides/agenda/static/js/agenda/base.js b/openslides/agenda/static/js/agenda/base.js index 366393f3d..916764013 100644 --- a/openslides/agenda/static/js/agenda/base.js +++ b/openslides/agenda/static/js/agenda/base.js @@ -52,11 +52,11 @@ angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users']) // in the DS store. title = this.title; } - if (this.getContentResource().verboseName) { - title = gettextCatalog.getString(this.getContentResource().verboseName) + ' ' + title; + if (this.getContentResource().agendaSupplement) { + title = gettextCatalog.getString(this.getContentResource().agendaSupplement) + ' ' + title; } if (this.item_number) { - title = this.item_number + ' ' + title; + title = this.item_number + ' ยท ' + title; } return title; }, diff --git a/openslides/agenda/static/templates/agenda/item-detail.html b/openslides/agenda/static/templates/agenda/item-detail.html index 8e1cb7b7d..1ca45ef01 100644 --- a/openslides/agenda/static/templates/agenda/item-detail.html +++ b/openslides/agenda/static/templates/agenda/item-detail.html @@ -126,9 +126,9 @@
- + {{ alert.msg }} - +
diff --git a/openslides/agenda/static/templates/agenda/item-list.html b/openslides/agenda/static/templates/agenda/item-list.html index 206af3cef..8655e0dc5 100644 --- a/openslides/agenda/static/templates/agenda/item-list.html +++ b/openslides/agenda/static/templates/agenda/item-list.html @@ -31,7 +31,8 @@
-
- @@ -95,6 +97,7 @@
+ {{ itemsFiltered.length }} / {{ items.length }} {{ "items" | translate }}, {{(items|filter:{selected:true}).length}} {{ "selected" | translate }}
@@ -115,8 +118,8 @@ Done - @@ -131,8 +134,8 @@ - - + {{ item.getTitle() }} @@ -144,8 +147,8 @@
List of speakers | - QuickEdit | - Edit + Edit | + QuickEdit |

{{ item.getTitle() }} – QuickEdit

- - {{alert.msg}} - + + {{ alert.msg }} +
diff --git a/openslides/assignments/static/js/assignments/base.js b/openslides/assignments/static/js/assignments/base.js index 6c00b822a..487195755 100644 --- a/openslides/assignments/static/js/assignments/base.js +++ b/openslides/assignments/static/js/assignments/base.js @@ -80,6 +80,7 @@ angular.module('OpenSlidesApp.assignments', []) name: name, useClass: jsDataModel, verboseName: gettext('Election'), + agendaSupplement: gettext('Election'), phases: phases, getPhases: function () { if (!this.phases) { diff --git a/openslides/assignments/static/js/assignments/projector.js b/openslides/assignments/static/js/assignments/projector.js index cef9a1a0f..89cb71e2d 100644 --- a/openslides/assignments/static/js/assignments/projector.js +++ b/openslides/assignments/static/js/assignments/projector.js @@ -4,19 +4,29 @@ angular.module('OpenSlidesApp.assignments.projector', ['OpenSlidesApp.assignments']) -.config(function(slidesProvider) { - slidesProvider.registerSlide('assignments/assignment', { - template: 'static/templates/assignments/slide_assignment.html', - }); -}) +.config([ + 'slidesProvider', + function(slidesProvider) { + slidesProvider.registerSlide('assignments/assignment', { + template: 'static/templates/assignments/slide_assignment.html', + }); + } +]) -.controller('SlideAssignmentCtrl', function($scope, Assignment) { - // 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; - Assignment.find(id); - Assignment.bindOne(id, $scope, 'assignment'); -}); +.controller('SlideAssignmentCtrl', [ + '$scope', + 'Assignment', + 'User', + function($scope, Assignment, 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; + Assignment.find(id); + Assignment.bindOne(id, $scope, 'assignment'); + User.findAll(); + User.bindAll({}, $scope, 'users'); + } +]); }()); diff --git a/openslides/assignments/static/js/assignments/site.js b/openslides/assignments/static/js/assignments/site.js index 456ad18aa..63c3383ec 100644 --- a/openslides/assignments/static/js/assignments/site.js +++ b/openslides/assignments/static/js/assignments/site.js @@ -18,63 +18,72 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) } ]) -.config(function($stateProvider) { - $stateProvider - .state('assignments', { - url: '/assignments', - abstract: true, - template: "", - }) - .state('assignments.assignment', { - abstract: true, - template: "", - }) - .state('assignments.assignment.list', { - resolve: { - assignments: function(Assignment) { - return Assignment.findAll(); - }, - phases: function(Assignment) { - return Assignment.getPhases(); +.config([ + '$stateProvider', + function($stateProvider) { + $stateProvider + .state('assignments', { + url: '/assignments', + abstract: true, + template: "", + }) + .state('assignments.assignment', { + abstract: true, + template: "", + }) + .state('assignments.assignment.list', { + resolve: { + assignments: function(Assignment) { + return Assignment.findAll(); + }, + phases: function(Assignment) { + return Assignment.getPhases(); + }, + users: function(User) { + return User.findAll(); + }, } - } - }) - .state('assignments.assignment.detail', { - controller: 'AssignmentDetailCtrl', - resolve: { - assignment: function(Assignment, $stateParams) { - return Assignment.find($stateParams.id); - }, - users: function(User) { - return User.findAll(); + }) + .state('assignments.assignment.detail', { + controller: 'AssignmentDetailCtrl', + resolve: { + assignment: function(Assignment, $stateParams) { + return Assignment.find($stateParams.id); + }, + users: function(User) { + return User.findAll(); + }, + phases: function(Assignment) { + return Assignment.getPhases(); + } } - } - }) - // redirects to assignment detail and opens assignment edit form dialog, uses edit url, - // used by ui-sref links from agenda only - // (from assignment controller use AssignmentForm factory instead to open dialog in front - // of current view without redirect) - .state('assignments.assignment.detail.update', { - onEnter: ['$stateParams', '$state', 'ngDialog', 'Assignment', - function($stateParams, $state, ngDialog, Assignment) { - ngDialog.open({ - template: 'static/templates/assignments/assignment-form.html', - controller: 'AssignmentUpdateCtrl', - className: 'ngdialog-theme-default wide-form', - closeByEscape: false, - closeByDocument: false, - resolve: { - assignment: function() {return Assignment.find($stateParams.id)} - }, - preCloseCallback: function() { - $state.go('assignments.assignment.detail', {assignment: $stateParams.id}); - return true; - } - }); - } - ] - }); -}) + }) + // redirects to assignment detail and opens assignment edit form dialog, uses edit url, + // used by ui-sref links from agenda only + // (from assignment controller use AssignmentForm factory instead to open dialog in front + // of current view without redirect) + .state('assignments.assignment.detail.update', { + onEnter: ['$stateParams', '$state', 'ngDialog', 'Assignment', + function($stateParams, $state, ngDialog, Assignment) { + ngDialog.open({ + template: 'static/templates/assignments/assignment-form.html', + controller: 'AssignmentUpdateCtrl', + className: 'ngdialog-theme-default wide-form', + closeByEscape: false, + closeByDocument: false, + resolve: { + assignment: function() {return Assignment.find($stateParams.id)} + }, + preCloseCallback: function() { + $state.go('assignments.assignment.detail', {assignment: $stateParams.id}); + return true; + } + }); + } + ] + }); + } +]) // Service for generic assignment form (create and update) .factory('AssignmentForm', [ @@ -119,7 +128,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) key: 'open_posts', type: 'input', templateOptions: { - label: gettextCatalog.getString('Number of members to be elected'), + label: gettextCatalog.getString('Number of posts to be elected'), type: 'number', required: true } @@ -153,12 +162,22 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) $scope.filterPresent = ''; $scope.reverse = false; // function to sort by clicked column - $scope.toggleSort = function ( column ) { + $scope.toggleSort = function (column) { if ( $scope.sortColumn === column ) { $scope.reverse = !$scope.reverse; } $scope.sortColumn = column; }; + // define custom search filter string + $scope.getFilterString = function (assignment) { + return [ + assignment.title, + assignment.description, + $scope.phases[assignment.phase].display_name, + _.map(assignment.assignment_related_users, + function (candidate) {return candidate.user.get_short_name()}).join(" "), + ].join(" "); + } // open new/edit dialog $scope.openDialog = function (assignment) { @@ -222,11 +241,14 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) 'Assignment', 'User', 'assignment', - function($scope, $http, gettext, ngDialog, AssignmentForm, operator, Assignment, User, assignment) { + 'phases', + function($scope, $http, gettext, ngDialog, AssignmentForm, operator, Assignment, User, assignment, phases) { User.bindAll({}, $scope, 'users'); Assignment.bindOne(assignment.id, $scope, 'assignment'); Assignment.loadRelations(assignment, 'agenda_item'); $scope.candidateSelectBox = {}; + // get all item types via OPTIONS request + $scope.phases = phases.data.actions.POST.phase.choices; $scope.alert = {}; // open edit dialog @@ -286,6 +308,11 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) else return false; }; + // update phase + $scope.updatePhase = function (phase_id) { + assignment.phase = phase_id; + Assignment.save(assignment); + } // create new ballot $scope.createBallot = function () { $http.post('/rest/assignments/assignment/' + assignment.id + '/create_poll/') @@ -383,16 +410,31 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) 'AssignmentForm', 'assignment', function($scope, $state, Assignment, AssignmentForm, assignment) { - // set initial values for form model - $scope.model = assignment; + $scope.alert = {}; + // set initial values for form model by create deep copy of assignment object + // so list/detail view is not updated while editing + $scope.model = angular.copy(assignment); // get all form fields $scope.formFields = AssignmentForm.getFormFields(); // save assignment $scope.save = function (assignment) { + // inject the changed assignment (copy) object back into DS store + Assignment.inject(assignment); + // save change motion object on server Assignment.save(assignment).then( function(success) { $scope.closeThisDialog(); + }, + function (error) { + // save error: revert all changes by restore + // (refresh) original assignment object from server + Assignment.refresh(assignment); + var message = ''; + for (var e in error.data) { + message += e + ': ' + error.data[e] + ' '; + } + $scope.alert = {type: 'danger', msg: message, show: true}; } ); }; diff --git a/openslides/assignments/static/templates/assignments/assignment-detail.html b/openslides/assignments/static/templates/assignments/assignment-detail.html index edbc6221c..ac3b15606 100644 --- a/openslides/assignments/static/templates/assignments/assignment-detail.html +++ b/openslides/assignments/static/templates/assignments/assignment-detail.html @@ -39,6 +39,42 @@
+
+
+
+ + Meta information +
+
+ +
+
+
+
+
+ +

Number of posts to be elected

+ {{ assignment.open_posts }}
+
+
+ +

Phase

+ + {{ phases[assignment.phase].display_name | translate }} + +
+ +
+
+
+
+
+

Description

{{ assignment.description }}
@@ -55,9 +91,9 @@
- + {{ alert.msg }} - +
@@ -91,7 +127,8 @@ - +
@@ -107,17 +144,17 @@ 2. Enter votes - - - + Delete diff --git a/openslides/assignments/static/templates/assignments/assignment-form.html b/openslides/assignments/static/templates/assignments/assignment-form.html index 76244282a..60fd97b94 100644 --- a/openslides/assignments/static/templates/assignments/assignment-form.html +++ b/openslides/assignments/static/templates/assignments/assignment-form.html @@ -1,6 +1,10 @@

Edit election

New election

+ + {{ alert.msg }} + +
- @@ -69,6 +71,7 @@
+ {{ assignmentsFiltered.length }} / {{ assignments.length }} {{ "elections" | translate }}, {{(assignments|filter:{selected:true}).length}} {{ "selected" | translate }}
@@ -96,8 +99,8 @@ ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'"> - @@ -135,9 +138,9 @@

{{ assignment.title }} – Quick Edit

- - {{alert.msg}} - + + {{ alert.msg }} +
diff --git a/openslides/assignments/static/templates/assignments/assignmentpoll-form.html b/openslides/assignments/static/templates/assignments/assignmentpoll-form.html index 36c7a61f9..90fc67bff 100644 --- a/openslides/assignments/static/templates/assignments/assignmentpoll-form.html +++ b/openslides/assignments/static/templates/assignments/assignmentpoll-form.html @@ -1,8 +1,8 @@

Ballot {{ ballot }}

- + {{ alert.msg }} - +

Special values: diff --git a/openslides/assignments/static/templates/assignments/slide_assignment.html b/openslides/assignments/static/templates/assignments/slide_assignment.html index eb870254e..341633bcd 100644 --- a/openslides/assignments/static/templates/assignments/slide_assignment.html +++ b/openslides/assignments/static/templates/assignments/slide_assignment.html @@ -1,4 +1,12 @@

{{ assignment.title }}

{{ assignment.description }}
+ + +

Candidates

+
    +
  1. + {{ related_user.user.get_full_name() }} + +
diff --git a/openslides/assignments/views.py b/openslides/assignments/views.py index 44f6eab89..f24d805a5 100644 --- a/openslides/assignments/views.py +++ b/openslides/assignments/views.py @@ -172,7 +172,7 @@ class AssignmentViewSet(ModelViewSet): if not request.user.has_perm('assignments.can_manage'): self.permission_denied(request) if assignment.phase == assignment.PHASE_FINISHED: - detail = _('You can not delete someones candidature to this election because it is finished.') + detail = _("You can not delete someone's candidature to this election because it is finished.") raise ValidationError({'detail': detail}) if not assignment.is_candidate(user) and not assignment.is_elected(user): raise ValidationError({'detail': _('User %s has no status in this election.') % user}) diff --git a/openslides/core/static/css/app.css b/openslides/core/static/css/app.css index 596b8e03c..d278a6923 100644 --- a/openslides/core/static/css/app.css +++ b/openslides/core/static/css/app.css @@ -458,6 +458,10 @@ img { padding-right: 10px; } +.col2 .countdown_timer.negative { + color: #CC0000; +} + .col2 .notNull { color: red; font-weight: bold; @@ -589,6 +593,10 @@ img { width: 250px; } +.loginForm .input-group-addon i { + width: 15px; +} + .modal-header { padding: 5px; } @@ -648,23 +656,21 @@ img { /* ngAnimate classes */ .animate-item.ng-enter { - -webkit-animation: fade-in 1s linear; - animation: fade-in 1.5s linear; + -webkit-animation: fade-in 0.5s linear; + animation: fade-in 0.5s linear; } .animate-item.ng-leave { - -webkit-animation: fade-out 1s linear; - animation: fade-out 1.5s linear; + -webkit-animation: fade-out 0.25s linear; + animation: fade-out 0.25s linear; } @keyframes fade-out { 0% { opacity: 1; background: none; } - 25% { opacity: 1; background: #f8efc0; } 100% { opacity: 0; background: none; } } @keyframes fade-in { 0% { opacity: 0; background: none; } - 25% { opacity: 1; background: #dff0d8; } 100% { opacity: 1; background: none; } } diff --git a/openslides/core/static/js/core/base.js b/openslides/core/static/js/core/base.js index 57d570357..59201377e 100644 --- a/openslides/core/static/js/core/base.js +++ b/openslides/core/static/js/core/base.js @@ -12,23 +12,27 @@ angular.module('OpenSlidesApp.core', [ 'ui.tree', ]) -.config(['DSProvider', 'DSHttpAdapterProvider', function(DSProvider, DSHttpAdapterProvider) { - // Reloads everything after 5 minutes. - // TODO: * find a way only to reload things that are still needed - DSProvider.defaults.maxAge = 5 * 60 * 1000; // 5 minutes - DSProvider.defaults.reapAction = 'none'; - DSProvider.defaults.basePath = '/rest'; - DSProvider.defaults.afterReap = function(model, items) { - if (items.length > 5) { - model.findAll({}, {bypassCache: true}); - } else { - _.forEach(items, function (item) { - model.refresh(item[model.idAttribute]); - }); - } - }; - DSHttpAdapterProvider.defaults.forceTrailingSlash = true; -}]) +.config([ + 'DSProvider', + 'DSHttpAdapterProvider', + function(DSProvider, DSHttpAdapterProvider) { + // Reloads everything after 5 minutes. + // TODO: * find a way only to reload things that are still needed + DSProvider.defaults.maxAge = 5 * 60 * 1000; // 5 minutes + DSProvider.defaults.reapAction = 'none'; + DSProvider.defaults.basePath = '/rest'; + DSProvider.defaults.afterReap = function(model, items) { + if (items.length > 5) { + model.findAll({}, {bypassCache: true}); + } else { + _.forEach(items, function (item) { + model.refresh(item[model.idAttribute]); + }); + } + }; + DSHttpAdapterProvider.defaults.forceTrailingSlash = true; + } +]) .factory('autoupdate', [ 'DS', @@ -129,23 +133,27 @@ angular.module('OpenSlidesApp.core', [ } ]) -.run(['DS', 'autoupdate', function(DS, autoupdate) { - autoupdate.on_message(function(data) { - // TODO: when MODEL.find() is called after this - // a new request is fired. This could be a bug in DS +.run([ + 'DS', + 'autoupdate', + function(DS, autoupdate) { + autoupdate.on_message(function(data) { + // TODO: when MODEL.find() is called after this + // a new request is fired. This could be a bug in DS - // TODO: Do not send the status code to the client, but make the decission - // on the server side. It is an implementation detail, that tornado - // sends request to wsgi, which should not concern the client. - console.log("Received object: " + data.collection + ", " + data.id); - if (data.status_code == 200) { - DS.inject(data.collection, data.data); - } else if (data.status_code == 404) { - DS.eject(data.collection, data.id); - } - // TODO: handle other statuscodes - }); -}]) + // TODO: Do not send the status code to the client, but make the decission + // on the server side. It is an implementation detail, that tornado + // sends request to wsgi, which should not concern the client. + console.log("Received object: " + data.collection + ", " + data.id); + if (data.status_code == 200) { + DS.inject(data.collection, data.data); + } else if (data.status_code == 404) { + DS.eject(data.collection, data.id); + } + // TODO: handle other statuscodes + }); + } +]) .factory('loadGlobalData', [ '$rootScope', @@ -310,37 +318,37 @@ angular.module('OpenSlidesApp.core', [ * be removed. See http://www.js-data.io/docs/dsdefaults#onconflict for * more information. */ -.factory('Projector', ['DS', function(DS) { - return DS.defineResource({ - name: 'core/projector', - onConflict: 'replace', - }); -}]) +.factory('Projector', [ + 'DS', + function(DS) { + return DS.defineResource({ + name: 'core/projector', + onConflict: 'replace', + }); + } +]) /* Converts number of seconds into string "hh:mm:ss" or "mm:ss" */ .filter('osSecondsToTime', [ function () { return function (totalseconds) { var time; - var total = Math.abs(totalseconds); - if (parseInt(totalseconds)) { - var hh = Math.floor(total / 3600); - var mm = Math.floor(total % 3600 / 60); - var ss = Math.floor(total % 60); - var zero = "0"; - // Add leading "0" for double digit values - hh = (zero+hh).slice(-2); - mm = (zero+mm).slice(-2); - ss = (zero+ss).slice(-2); - if (hh == "00") - time = mm + ':' + ss; - else - time = hh + ":" + mm + ":" + ss; - if (totalseconds < 0) - time = "-"+time; - } else { - time = "--:--"; - } + // floor returns the largest integer of the absolut value of totalseconds + var total = Math.floor(Math.abs(totalseconds)); + var hh = Math.floor(total / 3600); + var mm = Math.floor(total % 3600 / 60); + var ss = Math.floor(total % 60); + var zero = "0"; + // Add leading "0" for double digit values + hh = (zero+hh).slice(-2); + mm = (zero+mm).slice(-2); + ss = (zero+ss).slice(-2); + if (hh == "00") + time = mm + ':' + ss; + else + time = hh + ":" + mm + ":" + ss; + if (totalseconds < 0) + time = "-"+time; return time; }; } diff --git a/openslides/core/static/js/core/projector.js b/openslides/core/static/js/core/projector.js index 7a61df0b8..17207d867 100644 --- a/openslides/core/static/js/core/projector.js +++ b/openslides/core/static/js/core/projector.js @@ -6,70 +6,80 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core']) // Provider to register slides in a .config() statement. -.provider('slides', function() { - var slidesMap = {}; +.provider('slides', [ + function() { + var slidesMap = {}; - this.registerSlide = function(name, config) { - slidesMap[name] = config; - return this; - }; + this.registerSlide = function(name, config) { + slidesMap[name] = config; + return this; + }; - this.$get = function($templateRequest, $q) { - var self = this; - return { - getElements: function(projector) { - var elements = []; - var factory = this; - _.forEach(projector.elements, function(element) { - if (element.name in slidesMap) { - element.template = slidesMap[element.name].template; - elements.push(element); + this.$get = function($templateRequest, $q) { + var self = this; + return { + getElements: function(projector) { + var elements = []; + var factory = this; + _.forEach(projector.elements, function(element) { + if (element.name in slidesMap) { + element.template = slidesMap[element.name].template; + elements.push(element); + } else { + console.log("Unknown slide: " + element.name); + } + }); + return elements; + } + }; + }; + } +]) + +.config([ + 'slidesProvider', + function(slidesProvider) { + slidesProvider.registerSlide('core/customslide', { + template: 'static/templates/core/slide_customslide.html', + }); + + slidesProvider.registerSlide('core/clock', { + template: 'static/templates/core/slide_clock.html', + }); + + slidesProvider.registerSlide('core/countdown', { + template: 'static/templates/core/slide_countdown.html', + }); + + slidesProvider.registerSlide('core/message', { + template: 'static/templates/core/slide_message.html', + }); + } +]) + +.controller('ProjectorCtrl', [ + '$scope', + 'Projector', + 'slides', + function($scope, Projector, slides) { + Projector.find(1).then(function() { + $scope.$watch(function () { + return Projector.lastModified(1); + }, function () { + $scope.elements = []; + _.forEach(slides.getElements(Projector.get(1)), function(element) { + if (!element.error) { + $scope.elements.push(element); } else { - console.log("Unknown slide: " + element.name); + console.error("Error for slide " + element.name + ": " + element.error); } }); - return elements; - } - }; - }; -}) - -.config(function(slidesProvider) { - slidesProvider.registerSlide('core/customslide', { - template: 'static/templates/core/slide_customslide.html', - }); - - slidesProvider.registerSlide('core/clock', { - template: 'static/templates/core/slide_clock.html', - }); - - slidesProvider.registerSlide('core/countdown', { - template: 'static/templates/core/slide_countdown.html', - }); - - slidesProvider.registerSlide('core/message', { - template: 'static/templates/core/slide_message.html', - }); -}) - -.controller('ProjectorCtrl', function($scope, Projector, slides) { - Projector.find(1).then(function() { - $scope.$watch(function () { - return Projector.lastModified(1); - }, function () { - $scope.elements = []; - _.forEach(slides.getElements(Projector.get(1)), function(element) { - if (!element.error) { - $scope.elements.push(element); - } else { - console.error("Error for slide " + element.name + ": " + element.error); - } + $scope.scroll = -5 * Projector.get(1).scroll; + $scope.scale = 100 + 20 * Projector.get(1).scale; }); - $scope.scroll = -5 * Projector.get(1).scroll; - $scope.scale = 100 + 20 * Projector.get(1).scale; }); - }); -}) + } +]) .controller('SlideCustomSlideCtrl', [ '$scope', diff --git a/openslides/core/static/js/core/site.js b/openslides/core/static/js/core/site.js index 5aa3e5cfb..452c62d8f 100644 --- a/openslides/core/static/js/core/site.js +++ b/openslides/core/static/js/core/site.js @@ -84,209 +84,244 @@ angular.module('OpenSlidesApp.core.site', [ } ]) -.config(function($urlRouterProvider, $locationProvider) { - // define fallback url and html5Mode - $urlRouterProvider.otherwise('/'); - $locationProvider.html5Mode(true); -}) +.config([ + '$urlRouterProvider', + '$locationProvider', + function($urlRouterProvider, $locationProvider) { + // define fallback url and html5Mode + $urlRouterProvider.otherwise('/'); + $locationProvider.html5Mode(true); + } +]) -.config(function($httpProvider) { - // Combine the django csrf system with the angular csrf system - $httpProvider.defaults.xsrfCookieName = 'csrftoken'; - $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken'; -}) +.config([ + '$httpProvider', + function($httpProvider) { + // Combine the django csrf system with the angular csrf system + $httpProvider.defaults.xsrfCookieName = 'csrftoken'; + $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken'; + } +]) -.config(function(uiSelectConfig) { - uiSelectConfig.theme = 'bootstrap'; -}) +.config([ + 'uiSelectConfig', + function(uiSelectConfig) { + uiSelectConfig.theme = 'bootstrap'; + } +]) -.config(function($stateProvider, $urlMatcherFactoryProvider) { - // Make the trailing slash optional - $urlMatcherFactoryProvider.strictMode(false); +.config([ + '$stateProvider', + '$urlMatcherFactoryProvider', + function($stateProvider, $urlMatcherFactoryProvider) { + // Make the trailing slash optional + $urlMatcherFactoryProvider.strictMode(false); - // Use stateProvider.decorator to give default values to our states - $stateProvider.decorator('views', function(state, parent) { - var result = {}, - views = parent(state); + // Use stateProvider.decorator to give default values to our states + $stateProvider.decorator('views', function(state, parent) { + var result = {}, + views = parent(state); - if (state.abstract || state.data && state.data.extern) { - return views; - } + if (state.abstract || state.data && state.data.extern) { + return views; + } - angular.forEach(views, function(config, name) { + angular.forEach(views, function(config, name) { - // Sets default values for templateUrl - var patterns = state.name.split('.'), - templateUrl, - controller, - defaultControllers = { - create: 'CreateCtrl', - update: 'UpdateCtrl', - list: 'ListCtrl', - detail: 'DetailCtrl', - }; + // Sets default values for templateUrl + var patterns = state.name.split('.'), + templateUrl, + controller, + defaultControllers = { + create: 'CreateCtrl', + update: 'UpdateCtrl', + list: 'ListCtrl', + detail: 'DetailCtrl', + }; - // templateUrl - if (_.last(patterns).match(/(create|update)/)) { - // When state_patterns is in the form "app.module.create" or - // "app.module.update", use the form template. - templateUrl = 'static/templates/' + patterns[0] + '/' + patterns[1] + '-form.html'; + // templateUrl + if (_.last(patterns).match(/(create|update)/)) { + // When state_patterns is in the form "app.module.create" or + // "app.module.update", use the form template. + templateUrl = 'static/templates/' + patterns[0] + '/' + patterns[1] + '-form.html'; + } else { + // Replaces the first point through a slash (the app name) + var appName = state.name.replace('.', '/'); + // Replaces any folowing points though a - + templateUrl = 'static/templates/' + appName.replace(/\./g, '-') + '.html'; + } + config.templateUrl = state.templateUrl || templateUrl; + + // controller + if (patterns.length >= 3) { + controller = _.capitalize(patterns[1]) + defaultControllers[_.last(patterns)]; + config.controller = state.controller || controller; + } + result[name] = config; + }); + return result; + }) + + .decorator('url', function(state, parent) { + var defaultUrl; + + if (state.abstract) { + defaultUrl = ''; } else { - // Replaces the first point through a slash (the app name) - var appName = state.name.replace('.', '/'); - // Replaces any folowing points though a - - templateUrl = 'static/templates/' + appName.replace(/\./g, '-') + '.html'; - } - config.templateUrl = state.templateUrl || templateUrl; + var patterns = state.name.split('.'), + defaultUrls = { + create: '/new', + update: '/edit', + list: '', + // The id is expected to be an integer, if not, the url has to + // be defined manually + detail: '/{id:int}', + }; - // controller - if (patterns.length >= 3) { - controller = _.capitalize(patterns[1]) + defaultControllers[_.last(patterns)]; - config.controller = state.controller || controller; + defaultUrl = defaultUrls[_.last(patterns)]; } - result[name] = config; + + state.url = state.url || defaultUrl; + return parent(state); }); - return result; - }) + } +]) - .decorator('url', function(state, parent) { - var defaultUrl; - - if (state.abstract) { - defaultUrl = ''; - } else { - var patterns = state.name.split('.'), - defaultUrls = { - create: '/new', - update: '/edit', - list: '', - // The id is expected to be an integer, if not, the url has to - // be defined manually - detail: '/{id:int}', - }; - - defaultUrl = defaultUrls[_.last(patterns)]; - } - - state.url = state.url || defaultUrl; - return parent(state); - }); -}) - -.config(function($stateProvider, $locationProvider) { - // Core urls - $stateProvider - .state('dashboard', { - url: '/', - templateUrl: 'static/templates/dashboard.html' - }) - .state('projector', { - url: '/projector', - data: {extern: true}, - onEnter: function($window) { - $window.location.href = this.url; - } - }) - .state('core', { - url: '/core', - abstract: true, - template: "", - }) - // legal notice and version - .state('legalnotice', { - url: '/legalnotice', - controller: 'LegalNoticeCtrl', - }) - //config - .state('config', { - url: '/config', - controller: 'ConfigCtrl', - resolve: { - configOption: function($http) { - return $http({ 'method': 'OPTIONS', 'url': '/rest/core/config/' }); - } - } - }) - // customslide - .state('core.customslide', { - url: '/customslide', - abstract: true, - template: "", - }) - .state('core.customslide.detail', { - resolve: { - customslide: function(Customslide, $stateParams) { - return Customslide.find($stateParams.id); - } - } - }) - .state('core.customslide.detail.update', { - onEnter: ['$stateParams', 'ngDialog', 'Customslide', function($stateParams, ngDialog, Customslide) { - ngDialog.open({ - template: 'static/templates/core/customslide-form.html', - controller: 'CustomslideUpdateCtrl', - className: 'ngdialog-theme-default wide-form', - resolve: { customslide: function() { - return Customslide.find($stateParams.id) }} - }); - }] - }) - // tag - .state('core.tag', { - url: '/tag', - abstract: true, - template: "", - }) - .state('core.tag.list', { - resolve: { - tags: function(Tag) { - return Tag.findAll(); - } - } - }) - .state('core.tag.create', {}) - .state('core.tag.detail', { - resolve: { - tag: function(Tag, $stateParams) { - return Tag.find($stateParams.id); - } - } - }) - .state('core.tag.detail.update', { - views: { - '@core.tag': {} - } - }); - - $locationProvider.html5Mode(true); -}) - -// Helper to add ui.router states at runtime. -// Needed for the django url_patterns. -.provider('runtimeStates', function($stateProvider) { - this.$get = function($q, $timeout, $state) { - return { - addState: function(name, state) { - $stateProvider.state(name, state); - } - }; - }; -}) - -// Load the django url patterns -.run(function(runtimeStates, $http) { - $http.get('/core/url_patterns/').then(function(data) { - for (var pattern in data.data) { - runtimeStates.addState(pattern, { - 'url': data.data[pattern], +.config([ + '$stateProvider', + '$locationProvider', + function($stateProvider, $locationProvider) { + // Core urls + $stateProvider + .state('dashboard', { + url: '/', + templateUrl: 'static/templates/dashboard.html' + }) + .state('projector', { + url: '/projector', data: {extern: true}, onEnter: function($window) { $window.location.href = this.url; } + }) + .state('core', { + url: '/core', + abstract: true, + template: "", + }) + // legal notice and version + .state('legalnotice', { + url: '/legalnotice', + controller: 'LegalNoticeCtrl', + }) + //config + .state('config', { + url: '/config', + controller: 'ConfigCtrl', + resolve: { + configOption: function($http) { + return $http({ 'method': 'OPTIONS', 'url': '/rest/core/config/' }); + } + } + }) + // customslide + .state('core.customslide', { + url: '/customslide', + abstract: true, + template: "", + }) + .state('core.customslide.detail', { + resolve: { + customslide: function(Customslide, $stateParams) { + return Customslide.find($stateParams.id); + } + } + }) + // redirects to customslide detail and opens customslide edit form dialog, uses edit url, + // used by ui-sref links from agenda only + // (from customslide controller use CustomSlideForm factory instead to open dialog in front + // of current view without redirect) + .state('core.customslide.detail.update', { + onEnter: ['$stateParams', '$state', 'ngDialog', 'Customslide', + function($stateParams, $state, ngDialog, Customslide) { + ngDialog.open({ + template: 'static/templates/core/customslide-form.html', + controller: 'CustomslideUpdateCtrl', + className: 'ngdialog-theme-default wide-form', + resolve: { + customslide: function() {return Customslide.find($stateParams.id) } + }, + preCloseCallback: function() { + $state.go('core.customslide.detail', {customslide: $stateParams.id}); + return true; + } + }); + }] + }) + // tag + .state('core.tag', { + url: '/tag', + abstract: true, + template: "", + }) + .state('core.tag.list', { + resolve: { + tags: function(Tag) { + return Tag.findAll(); + } + } + }) + .state('core.tag.create', {}) + .state('core.tag.detail', { + resolve: { + tag: function(Tag, $stateParams) { + return Tag.find($stateParams.id); + } + } + }) + .state('core.tag.detail.update', { + views: { + '@core.tag': {} + } }); - } - }); -}) + + $locationProvider.html5Mode(true); + } +]) + +// Helper to add ui.router states at runtime. +// Needed for the django url_patterns. +.provider('runtimeStates', [ + '$stateProvider', + function($stateProvider) { + this.$get = function($q, $timeout, $state) { + return { + addState: function(name, state) { + $stateProvider.state(name, state); + } + }; + }; + } +]) + +// Load the django url patterns +.run([ + 'runtimeStates', + '$http', + function(runtimeStates, $http) { + $http.get('/core/url_patterns/').then(function(data) { + for (var pattern in data.data) { + runtimeStates.addState(pattern, { + 'url': data.data[pattern], + data: {extern: true}, + onEnter: function($window) { + $window.location.href = this.url; + } + }); + } + }); + } +]) // angular formly config options .run([ @@ -311,42 +346,46 @@ angular.module('OpenSlidesApp.core.site', [ // html-tag os-form-field to generate generic from fields // TODO: make it possible to use other fields then config fields -.directive('osFormField', function($parse, Config) { - function getHtmlType(type) { - return { - string: 'text', - text: 'textarea', - integer: 'number', - boolean: 'checkbox', - choice: 'choice', - }[type]; - } - - return { - restrict: 'E', - scope: true, - templateUrl: '/static/templates/config-form-field.html', - link: function ($scope, iElement, iAttrs, controller, transcludeFn) { - var field = $parse(iAttrs.field)($scope); - var config = Config.get(field.key); - $scope.type = getHtmlType(field.input_type); - if ($scope.type == 'choice') { - $scope.choices = field.choices; - } - $scope.label = field.label; - $scope.key = 'field-' + field.key; - $scope.value = config.value; - $scope.help_text = field.help_text; - $scope.default_value = field.default_value; - $scope.reset = function () { - $scope.value = $scope.default_value; - $scope.save(field.key, $scope.value); - } +.directive('osFormField', [ + '$parse', + 'Config', + function($parse, Config) { + function getHtmlType(type) { + return { + string: 'text', + text: 'textarea', + integer: 'number', + boolean: 'checkbox', + choice: 'choice', + }[type]; } - }; -}) -.controller("MainMenuCtrl", [ + return { + restrict: 'E', + scope: true, + templateUrl: '/static/templates/config-form-field.html', + link: function ($scope, iElement, iAttrs, controller, transcludeFn) { + var field = $parse(iAttrs.field)($scope); + var config = Config.get(field.key); + $scope.type = getHtmlType(field.input_type); + if ($scope.type == 'choice') { + $scope.choices = field.choices; + } + $scope.label = field.label; + $scope.key = 'field-' + field.key; + $scope.value = config.value; + $scope.help_text = field.help_text; + $scope.default_value = field.default_value; + $scope.reset = function () { + $scope.value = $scope.default_value; + $scope.save(field.key, $scope.value); + } + } + }; + } +]) + +.controller('MainMenuCtrl', [ '$scope', 'mainMenu', function ($scope, mainMenu) { @@ -354,15 +393,21 @@ angular.module('OpenSlidesApp.core.site', [ } ]) -.controller("LanguageCtrl", function ($scope, gettextCatalog, Languages, filterFilter) { - $scope.languages = Languages.getLanguages(); - $scope.selectedLanguage = filterFilter($scope.languages, {selected: true}); - // controller to switch app language - $scope.switchLanguage = function (lang) { - $scope.languages = Languages.setCurrentLanguage(lang); +.controller('LanguageCtrl', [ + '$scope', + 'gettextCatalog', + 'Languages', + 'filterFilter', + function ($scope, gettextCatalog, Languages, filterFilter) { + $scope.languages = Languages.getLanguages(); $scope.selectedLanguage = filterFilter($scope.languages, {selected: true}); - }; -}) + // controller to switch app language + $scope.switchLanguage = function (lang) { + $scope.languages = Languages.setCurrentLanguage(lang); + $scope.selectedLanguage = filterFilter($scope.languages, {selected: true}); + }; + } +]) // Projector Sidebar Controller .controller('ProjectorSidebarCtrl', [ @@ -388,25 +433,46 @@ angular.module('OpenSlidesApp.core.site', [ ]) // Config Controller -.controller('ConfigCtrl', function($scope, Config, configOption) { - Config.bindAll({}, $scope, 'configs'); - $scope.configGroups = configOption.data.config_groups; +.controller('ConfigCtrl', [ + '$scope', + 'Config', + 'configOption', + function($scope, Config, configOption) { + Config.bindAll({}, $scope, 'configs'); + $scope.configGroups = configOption.data.config_groups; - // save changed config value - $scope.save = function(key, value) { - Config.get(key).value = value; - Config.save(key); - }; -}) + // save changed config value + $scope.save = function(key, value) { + Config.get(key).value = value; + Config.save(key); + }; + } +]) // Provide generic customslide form fields for create and update view -.factory('CustomslideFormFieldFactory', [ +.factory('CustomslideForm', [ 'gettextCatalog', 'CKEditorOptions', 'Mediafile', function (gettextCatalog, CKEditorOptions, Mediafile) { return { + // ngDialog for customslide form + getDialog: function (customslide) { + if (customslide) { + var resolve = { + customslide: function(Customslide) {return Customslide.find(customslide.id);} + }; + } + return { + template: 'static/templates/core/customslide-form.html', + controller: (customslide) ? 'CustomslideUpdateCtrl' : 'CustomslideCreateCtrl', + className: 'ngdialog-theme-default wide-form', + closeByEscape: false, + closeByDocument: false, + resolve: (resolve) ? resolve : null + } + }, getFormFields: function () { return [ { @@ -641,20 +707,32 @@ angular.module('OpenSlidesApp.core.site', [ ]) // Customslide Controllers -.controller('CustomslideDetailCtrl', function($scope, Customslide, customslide) { - Customslide.bindOne(customslide.id, $scope, 'customslide'); - Customslide.loadRelations(customslide, 'agenda_item'); -}) +.controller('CustomslideDetailCtrl', [ + '$scope', + 'ngDialog', + 'CustomslideForm', + 'Customslide', + 'customslide', + function($scope, ngDialog, CustomslideForm, Customslide, customslide) { + Customslide.bindOne(customslide.id, $scope, 'customslide'); + Customslide.loadRelations(customslide, 'agenda_item'); + + // open edit dialog + $scope.openDialog = function (customslide) { + ngDialog.open(CustomslideForm.getDialog(customslide)); + }; + } +]) .controller('CustomslideCreateCtrl', [ '$scope', '$state', 'Customslide', - 'CustomslideFormFieldFactory', - function($scope, $state, Customslide, CustomslideFormFieldFactory) { + 'CustomslideForm', + function($scope, $state, Customslide, CustomslideForm) { $scope.customslide = {}; // get all form fields - $scope.formFields = CustomslideFormFieldFactory.getFormFields(); + $scope.formFields = CustomslideForm.getFormFields(); // save form $scope.save = function (customslide) { @@ -671,19 +749,34 @@ angular.module('OpenSlidesApp.core.site', [ '$scope', '$state', 'Customslide', - 'CustomslideFormFieldFactory', + 'CustomslideForm', 'customslide', - function($scope, $state, Customslide, CustomslideFormFieldFactory, customslide) { - // set initial values for form model - $scope.model = customslide; + function($scope, $state, Customslide, CustomslideForm, customslide) { + $scope.alert = {}; + // set initial values for form model by create deep copy of customslide object + // so list/detail view is not updated while editing + $scope.model = angular.copy(customslide); // get all form fields - $scope.formFields = CustomslideFormFieldFactory.getFormFields(); + $scope.formFields = CustomslideForm.getFormFields(); // save form $scope.save = function (customslide) { + // inject the changed customslide (copy) object back into DS store + Customslide.inject(customslide); + // save change customslide object on server Customslide.save(customslide).then( function(success) { $scope.closeThisDialog(); + }, + function (error) { + // save error: revert all changes by restore + // (refresh) original customslide object from server + Customslide.refresh(customslide); + var message = ''; + for (var e in error.data) { + message += e + ': ' + error.data[e] + ' '; + } + $scope.alert = {type: 'danger', msg: message, show: true}; } ); }; @@ -691,58 +784,78 @@ angular.module('OpenSlidesApp.core.site', [ ]) // Tag Controller -.controller('TagListCtrl', function($scope, Tag) { - Tag.bindAll({}, $scope, 'tags'); +.controller('TagListCtrl', [ + '$scope', + 'Tag', + function($scope, Tag) { + Tag.bindAll({}, $scope, 'tags'); - // setup table sorting - $scope.sortColumn = 'name'; - $scope.reverse = false; - // function to sort by clicked column - $scope.toggleSort = function ( column ) { - if ( $scope.sortColumn === column ) { - $scope.reverse = !$scope.reverse; - } - $scope.sortColumn = column; - }; - - // save changed tag - $scope.save = function (tag) { - Tag.save(tag); - }; - $scope.delete = function (tag) { - Tag.destroy(tag.id).then( - function(success) { - //TODO: success message + // setup table sorting + $scope.sortColumn = 'name'; + $scope.reverse = false; + // function to sort by clicked column + $scope.toggleSort = function ( column ) { + if ( $scope.sortColumn === column ) { + $scope.reverse = !$scope.reverse; } - ); - }; -}) + $scope.sortColumn = column; + }; -.controller('TagDetailCtrl', function($scope, Tag, tag) { - Tag.bindOne(tag.id, $scope, 'tag'); -}) + // save changed tag + $scope.save = function (tag) { + Tag.save(tag); + }; + $scope.delete = function (tag) { + Tag.destroy(tag.id).then( + function(success) { + //TODO: success message + } + ); + }; + } +]) -.controller('TagCreateCtrl', function($scope, $state, Tag) { - $scope.tag = {}; - $scope.save = function (tag) { - Tag.create(tag).then( - function(success) { - $state.go('core.tag.list'); - } - ); - }; -}) +.controller('TagDetailCtrl', [ + '$scope', + 'Tag', + 'tag', + function($scope, Tag, tag) { + Tag.bindOne(tag.id, $scope, 'tag'); + } +]) -.controller('TagUpdateCtrl', function($scope, $state, Tag, tag) { - $scope.tag = tag; - $scope.save = function (tag) { - Tag.save(tag).then( - function(success) { - $state.go('core.tag.list'); - } - ); - }; -}) +.controller('TagCreateCtrl', [ + '$scope', + '$state', + 'Tag', + function($scope, $state, Tag) { + $scope.tag = {}; + $scope.save = function (tag) { + Tag.create(tag).then( + function(success) { + $state.go('core.tag.list'); + } + ); + }; + } +]) + +.controller('TagUpdateCtrl', [ + '$scope', + '$state', + 'Tag', + 'tag', + function($scope, $state, Tag, tag) { + $scope.tag = tag; + $scope.save = function (tag) { + Tag.save(tag).then( + function(success) { + $state.go('core.tag.list'); + } + ); + }; + } +]) // counter of new (unread) chat messages .value('NewChatMessages', []) @@ -793,14 +906,17 @@ angular.module('OpenSlidesApp.core.site', [ } ]) -.directive('osFocusMe', function ($timeout) { - return { - link: function (scope, element, attrs, model) { - $timeout(function () { - element[0].focus(); - }); - } - }; -}); +.directive('osFocusMe', [ + '$timeout', + function ($timeout) { + return { + link: function (scope, element, attrs, model) { + $timeout(function () { + element[0].focus(); + }); + } + }; + } +]); }()); diff --git a/openslides/core/static/templates/core/customslide-detail.html b/openslides/core/static/templates/core/customslide-detail.html index 383ea335a..4f5ef9dfd 100644 --- a/openslides/core/static/templates/core/customslide-detail.html +++ b/openslides/core/static/templates/core/customslide-detail.html @@ -17,6 +17,12 @@ title="{{ 'Project agenda item' | translate }}"> + + + +

{{ customslide.agenda_item.getTitle() }}

Agenda item

diff --git a/openslides/core/static/templates/core/customslide-form.html b/openslides/core/static/templates/core/customslide-form.html index 2ad1ce6cc..48257ac1a 100644 --- a/openslides/core/static/templates/core/customslide-form.html +++ b/openslides/core/static/templates/core/customslide-form.html @@ -1,6 +1,10 @@

Edit agenda item

New agenda item

+ + {{ alert.msg }} + + + + +
Last modified: {{ version.creation_time | date:'yyyy-MM-dd HH:mm:ss' }} + +
+
- @@ -75,6 +77,7 @@
+ {{ motionsFiltered.length }} / {{ motions.length }} {{ "motions" | translate }}, {{(motions|filter:{selected:true}).length}} {{ "selected" | translate }}
@@ -82,9 +85,9 @@ - + - + Identifier @@ -112,8 +115,8 @@ ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'"> - @@ -161,9 +164,9 @@

{{ motion.getTitle() }} QuickEdit

- - {{alert.msg}} - + + {{ alert.msg }} +
diff --git a/openslides/motions/static/templates/motions/motionpoll-form.html b/openslides/motions/static/templates/motions/motionpoll-form.html index 097a5fd4a..203c51671 100644 --- a/openslides/motions/static/templates/motions/motionpoll-form.html +++ b/openslides/motions/static/templates/motions/motionpoll-form.html @@ -1,8 +1,8 @@

Vote {{ voteNumber }}

- + {{ alert.msg }} - +

Special values: diff --git a/openslides/motions/static/templates/motions/slide_motion.html b/openslides/motions/static/templates/motions/slide_motion.html index ca0546b1e..b020ddc83 100644 --- a/openslides/motions/static/templates/motions/slide_motion.html +++ b/openslides/motions/static/templates/motions/slide_motion.html @@ -17,7 +17,7 @@

{{ motion.getTitle() }}

Motion {{ motion.identifier }} - | Version {{ motion.active_version }} + | Version {{ motion.getVersion().version_number }}

diff --git a/openslides/users/static/js/users/base.js b/openslides/users/static/js/users/base.js index 6f6e37ca6..81ef0fac6 100644 --- a/openslides/users/static/js/users/base.js +++ b/openslides/users/static/js/users/base.js @@ -60,80 +60,98 @@ angular.module('OpenSlidesApp.users', []) } ]) -.factory('User', ['DS', 'Group', 'jsDataModel', function(DS, Group, jsDataModel) { - var name = 'users/user'; - return DS.defineResource({ - name: name, - useClass: jsDataModel, - computed: { - full_name: function () { - return this.get_full_name(); +.factory('User', [ + 'DS', + 'Group', + 'jsDataModel', + function(DS, Group, jsDataModel) { + var name = 'users/user'; + return DS.defineResource({ + name: name, + useClass: jsDataModel, + computed: { + full_name: function () { + return this.get_full_name(); + }, + short_name: function () { + return this.get_short_name(); + }, }, - short_name: function () { - return this.get_short_name(); - }, - }, - methods: { - getResourceName: function () { - return name; - }, - get_short_name: function() { - // should be the same as in the python user model. - var firstName = _.trim(this.first_name), - lastName = _.trim(this.last_name), - name; + methods: { + getResourceName: function () { + return name; + }, + get_short_name: function() { + // should be the same as in the python user model. + var title = _.trim(this.title), + firstName = _.trim(this.first_name), + lastName = _.trim(this.last_name), + name = ''; - if (firstName && lastName) { - // TODO: check config - name = [firstName, lastName].join(' '); - } else { - name = firstName || lastName || this.username; - } - return name; - }, - get_full_name: function() { - // should be the same as in the python user model. - var firstName = _.trim(this.first_name), - lastName = _.trim(this.last_name), - structure_level = _.trim(this.structure_level), - name; - - if (firstName && lastName) { - // TODO: check config - name = [firstName, lastName].join(' '); - } else { - name = firstName || lastName || this.username; - } - if (structure_level) { - name = name + " (" + structure_level + ")"; - } - return name; - }, - getPerms: function() { - var allPerms = []; - var allGroups = this.groups; - // Add registered group - allGroups.push(2); - _.forEach(allGroups, function(groupId) { - var group = Group.get(groupId); - if (group) { - _.forEach(group.permissions, function(perm) { - allPerms.push(perm); - }); + if (title) { + name = title + ' '; } - }); - return _.uniq(allPerms); + if (firstName && lastName) { + name += [firstName, lastName].join(' '); + } else { + name += firstName || lastName || this.username; + } + return name; + }, + get_full_name: function() { + // should be the same as in the python user model. + var title = _.trim(this.title), + firstName = _.trim(this.first_name), + lastName = _.trim(this.last_name), + structure_level = _.trim(this.structure_level), + name = ''; + + if (title) { + name = title + ' '; + } + if (firstName && lastName) { + name += [firstName, lastName].join(' '); + } else { + name += firstName || lastName || this.username; + } + if (structure_level) { + name += " (" + structure_level + ")"; + } + return name; + }, + getPerms: function() { + var allPerms = []; + var allGroups = this.groups; + // Add registered group + allGroups.push(2); + _.forEach(allGroups, function(groupId) { + var group = Group.get(groupId); + if (group) { + _.forEach(group.permissions, function(perm) { + allPerms.push(perm); + }); + } + }); + return _.uniq(allPerms); + }, }, - }, - }); -}]) + }); + } +]) -.factory('Group', ['DS', function(DS) { - return DS.defineResource({ - name: 'users/group', - }); -}]) +.factory('Group', [ + 'DS', + function(DS) { + return DS.defineResource({ + name: 'users/group', + }); + } +]) -.run(['User', 'Group', function(User, Group) {}]); +.run([ + 'User', + 'Group', + function(User, Group) {} +]); }()); diff --git a/openslides/users/static/js/users/projector.js b/openslides/users/static/js/users/projector.js index c375c0259..f7f085b7f 100644 --- a/openslides/users/static/js/users/projector.js +++ b/openslides/users/static/js/users/projector.js @@ -4,19 +4,26 @@ angular.module('OpenSlidesApp.users.projector', ['OpenSlidesApp.users']) -.config(function(slidesProvider) { - slidesProvider.registerSlide('users/user', { - template: 'static/templates/users/slide_user.html', - }); -}) +.config([ + 'slidesProvider', + function(slidesProvider) { + slidesProvider.registerSlide('users/user', { + template: 'static/templates/users/slide_user.html', + }); + } +]) -.controller('SlideUserCtrl', function($scope, 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; - User.find(id); - User.bindOne(id, $scope, 'user'); -}); +.controller('SlideUserCtrl', [ + '$scope', + 'User', + function($scope, 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; + User.find(id); + User.bindOne(id, $scope, 'user'); + } +]); }()); diff --git a/openslides/users/static/js/users/site.js b/openslides/users/static/js/users/site.js index aa3b49075..d7134b80b 100644 --- a/openslides/users/static/js/users/site.js +++ b/openslides/users/static/js/users/site.js @@ -18,120 +18,126 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users']) } ]) -.config(function($stateProvider) { - $stateProvider - .state('users', { - url: '/users', - abstract: true, - template: "", - }) - .state('users.user', { - abstract: true, - template: "", - }) - .state('users.user.list', { - resolve: { - users: function(User) { - return User.findAll(); - } - } - }) - .state('users.user.create', { - resolve: { - groups: function(Group) { - return Group.findAll(); - } - } - }) - .state('users.user.detail', { - resolve: { - user: function(User, $stateParams) { - return User.find($stateParams.id); - }, - groups: function(Group) { - return Group.findAll(); - } - } - }) - .state('users.user.detail.profile', { - views: { - '@users.user': {}, - }, - url: '/profile', - controller: 'UserProfileCtrl', - }) - .state('users.user.detail.password', { - views: { - '@users.user': {}, - }, - url: '/password', - controller: 'UserPasswordCtrl', - }) - .state('users.user.import', { - url: '/import', - controller: 'UserImportCtrl', - resolve: { - groups: function(Group) { - return Group.findAll(); - } - } - }) - // groups - .state('users.group', { - url: '/groups', - abstract: true, - template: "", - }) - .state('users.group.list', { - resolve: { - groups: function(Group) { - return Group.findAll(); - } - } - }) - .state('users.group.create', { - resolve: { - permissions: function($http) { - return $http({ 'method': 'OPTIONS', 'url': '/rest/users/group/' }); - } - } - }) - .state('users.group.detail', { - resolve: { - group: function(Group, $stateParams) { - return Group.find($stateParams.id); - } - } - }) - .state('users.group.detail.update', { - views: { - '@users.group': {} - }, - resolve: { - permissions: function($http) { - return $http({ 'method': 'OPTIONS', 'url': '/rest/users/group/' }); - } - } - }) - .state('login', { - template: null, - url: '/login', - params: { guest_enabled: false }, - onEnter: ['$state', '$stateParams', 'ngDialog', function($state, $stateParams, ngDialog) { - ngDialog.open({ - template: 'static/templates/core/login-form.html', - controller: 'LoginFormCtrl', - showClose: $stateParams.guest_enabled, - closeByEscape: $stateParams.guest_enabled, - closeByDocument: $stateParams.guest_enabled, - preCloseCallback: function() { - $state.go('dashboard'); - return true; +.config([ + '$stateProvider', + function($stateProvider) { + $stateProvider + .state('users', { + url: '/users', + abstract: true, + template: "", + }) + .state('users.user', { + abstract: true, + template: "", + }) + .state('users.user.list', { + resolve: { + users: function(User) { + return User.findAll(); + }, + groups: function(Group) { + return Group.findAll(); } - }); - }] - }); -}) + } + }) + .state('users.user.create', { + resolve: { + groups: function(Group) { + return Group.findAll(); + } + } + }) + .state('users.user.detail', { + resolve: { + user: function(User, $stateParams) { + return User.find($stateParams.id); + }, + groups: function(Group) { + return Group.findAll(); + } + } + }) + .state('users.user.detail.profile', { + views: { + '@users.user': {}, + }, + url: '/profile', + controller: 'UserProfileCtrl', + }) + .state('users.user.detail.password', { + views: { + '@users.user': {}, + }, + url: '/password', + controller: 'UserPasswordCtrl', + }) + .state('users.user.import', { + url: '/import', + controller: 'UserImportCtrl', + resolve: { + groups: function(Group) { + return Group.findAll(); + } + } + }) + // groups + .state('users.group', { + url: '/groups', + abstract: true, + template: "", + }) + .state('users.group.list', { + resolve: { + groups: function(Group) { + return Group.findAll(); + } + } + }) + .state('users.group.create', { + resolve: { + permissions: function($http) { + return $http({ 'method': 'OPTIONS', 'url': '/rest/users/group/' }); + } + } + }) + .state('users.group.detail', { + resolve: { + group: function(Group, $stateParams) { + return Group.find($stateParams.id); + } + } + }) + .state('users.group.detail.update', { + views: { + '@users.group': {} + }, + resolve: { + permissions: function($http) { + return $http({ 'method': 'OPTIONS', 'url': '/rest/users/group/' }); + } + } + }) + .state('login', { + template: null, + url: '/login', + params: { guest_enabled: false }, + onEnter: ['$state', '$stateParams', 'ngDialog', function($state, $stateParams, ngDialog) { + ngDialog.open({ + template: 'static/templates/core/login-form.html', + controller: 'LoginFormCtrl', + showClose: $stateParams.guest_enabled, + closeByEscape: $stateParams.guest_enabled, + closeByDocument: $stateParams.guest_enabled, + preCloseCallback: function() { + $state.go('dashboard'); + return true; + } + }); + }] + }); + } +]) .run([ 'operator', @@ -143,6 +149,7 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users']) // Put the operator into the root scope $http.get('/users/whoami/').success(function(data) { operator.setUser(data.user_id); + $rootScope.guest_enabled = data.guest_enabled; if (data.user_id === null && !data.guest_enabled) { // redirect to login dialog if use is not logged in $state.go('login', {guest_enabled: data.guest_enabled}); @@ -224,47 +231,6 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users']) } ]) -/* - * Like osPerms but does only hide the DOM-Elements - * - * This is the Code from angular.js ngShow. -*/ -.directive('osPermsLite', [ - '$animate', - function($animate) { - var NG_HIDE_CLASS = 'os-perms-lite'; - var NG_HIDE_IN_PROGRESS_CLASS = 'os-perms-lite-animate'; - return { - restrict: 'A', - multiElement: true, - link: function(scope, element, $attr) { - var perms; - if ($attr.osPermsLite[0] === '!') { - perms = _.trimLeft($attr.osPermsLite, '!'); - } else { - perms = $attr.osPermsLite; - } - scope.$watch( - function (scope) { - return scope.operator.hasPerms(perms); - }, function ngShowWatchAction(value) { - if ($attr.osPermsLite[0] === '!') { - value = !value; - } - // we're adding a temporary, animation-specific class for ng-hide since this way - // we can control when the element is actually displayed on screen without having - // to have a global/greedy CSS selector that breaks when other animations are run. - // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 - $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { - tempClasses: NG_HIDE_IN_PROGRESS_CLASS - }); - } - ); - } - }; - } -]) - // Service for generic assignment form (create and update) .factory('UserForm', [ '$http', @@ -289,8 +255,16 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users']) } }, // angular-formly fields for user form - getFormFields: function () { + getFormFields: function (hideOnCreateForm) { return [ + { + key: 'username', + type: 'input', + templateOptions: { + label: gettextCatalog.getString('Username') + }, + hide: hideOnCreateForm + }, { key: 'title', type: 'input', @@ -373,7 +347,8 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users']) templateOptions: { label: gettextCatalog.getString('Is present'), description: gettextCatalog.getString('Designates whether this user is in the room or not.') - } + }, + defaultValue: true }, { key: 'is_active', @@ -383,7 +358,8 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users']) description: gettextCatalog.getString( 'Designates whether this user should be treated as ' + 'active. Unselect this instead of deleting the account.') - } + }, + defaultValue: true }]; } } @@ -399,7 +375,7 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users']) 'Group', function($scope, $state, ngDialog, UserForm, User, Group) { User.bindAll({}, $scope, 'users'); - Group.bindAll({}, $scope, 'groups'); + Group.bindAll({where: {id: {'>': 2}}}, $scope, 'groups'); $scope.alert = {}; // setup table sorting @@ -475,7 +451,7 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users']) 'Group', function($scope, ngDialog, UserForm, User, user, Group) { User.bindOne(user.id, $scope, 'user'); - Group.bindAll({}, $scope, 'groups'); + Group.bindAll({where: {id: {'>': 2}}}, $scope, 'groups'); // open edit dialog $scope.openDialog = function (user) { @@ -492,8 +468,9 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users']) 'Group', function($scope, $state, User, UserForm, Group) { Group.bindAll({where: {id: {'>': 2}}}, $scope, 'groups'); + $scope.alert = {}; // get all form fields - $scope.formFields = UserForm.getFormFields(); + $scope.formFields = UserForm.getFormFields(true); // save user $scope.save = function (user) { @@ -503,6 +480,13 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users']) User.create(user).then( function(success) { $scope.closeThisDialog(); + }, + function (error) { + var message = ''; + for (var e in error.data) { + message += e + ': ' + error.data[e] + ' '; + } + $scope.alert = {type: 'danger', msg: message, show: true}; } ); }; @@ -838,13 +822,13 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users']) ]) .controller('LoginFormCtrl', [ + '$rootScope', '$scope', '$http', '$stateParams', 'operator', 'gettextCatalog', - 'Config', - function ($scope, $http, $stateParams, operator, gettextCatalog, Config) { + function ($rootScope, $scope, $http, $stateParams, operator, gettextCatalog) { $scope.alerts = []; // get login info-text from server @@ -861,7 +845,7 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users']) $scope.alerts.splice(index, 1); }; // check if guest login is allowed - $scope.guestAllowed = $stateParams.guest_enabled; + $scope.guestAllowed = $rootScope.guest_enabled; // login $scope.login = function () { $scope.alerts = []; diff --git a/openslides/users/static/templates/users/user-form.html b/openslides/users/static/templates/users/user-form.html index 22116dfb6..00d4b3012 100644 --- a/openslides/users/static/templates/users/user-form.html +++ b/openslides/users/static/templates/users/user-form.html @@ -1,9 +1,9 @@

Edit participant

New participant

- + {{ alert.msg }} - + diff --git a/openslides/users/static/templates/users/user-list.html b/openslides/users/static/templates/users/user-list.html index a64ea257b..4412bc03c 100644 --- a/openslides/users/static/templates/users/user-list.html +++ b/openslides/users/static/templates/users/user-list.html @@ -40,7 +40,8 @@
-
- @@ -64,7 +66,12 @@
-
+
+ + Is present @@ -83,6 +90,7 @@
+ {{ usersFiltered.length }} / {{ users.length }} {{ "participants" | translate }}, {{(users|filter:{selected:true}).length}} {{ "selected" | translate }}
@@ -90,9 +98,9 @@ - + - + Name @@ -116,8 +124,9 @@ ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'"> -