Merge pull request #1875 from emanuelschuetze/template-fixes
Template fixes
This commit is contained in:
commit
48781b95ba
@ -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;
|
||||
},
|
||||
|
@ -126,9 +126,9 @@
|
||||
|
||||
<!-- Select speakers form -->
|
||||
<div class="form-group spacer">
|
||||
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
<uib-alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
{{ alert.msg }}
|
||||
</alert>
|
||||
</uib-alert>
|
||||
<div os-perms="agenda.can_manage" class="input-group">
|
||||
<ui-select ng-model="speakerSelectBox.selected" ng-change="addSpeaker(speakerSelectBox.selected.id)">
|
||||
<ui-select-match placeholder="{{ 'Select or search a participant ...' | translate }}">
|
||||
|
@ -31,7 +31,8 @@
|
||||
<div class="col-sm-6">
|
||||
<form class="form-inline">
|
||||
<!-- delete mode -->
|
||||
<button os-perms="agenda.can_manage" class="btn btn-default"
|
||||
<button os-perms="agenda.can_manage" class="btn"
|
||||
ng-class="$parent.isDeleteMode ? 'btn-primary' : 'btn-default'"
|
||||
ng-click="$parent.isDeleteMode = !$parent.isDeleteMode; uncheckAll()">
|
||||
<i class="fa fa-check-square-o"></i>
|
||||
<translate>Select ...</translate>
|
||||
@ -65,7 +66,8 @@
|
||||
placeholder="{{ 'Search' | translate}}">
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-default" ng-click="isFilterOpen = !isFilterOpen">
|
||||
<button class="btn btn-default" ng-click="isFilterOpen = !isFilterOpen"
|
||||
ng-class="isFilterOpen ? 'btn-primary' : 'btn-default'">
|
||||
<i class="fa fa-filter"></i>
|
||||
<translate>Filter ...</translate>
|
||||
</button>
|
||||
@ -95,6 +97,7 @@
|
||||
</div>
|
||||
|
||||
<div class="spacer-top-lg italic">
|
||||
<span os-perms="agenda.can_see_hidden_items">{{ itemsFiltered.length }} /</span>
|
||||
{{ items.length }} {{ "items" | translate }}<span ng-if="(items|filter:{selected:true}).length > 0">,
|
||||
{{(items|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
|
||||
</div>
|
||||
@ -115,8 +118,8 @@
|
||||
<th class="minimum">
|
||||
<translate>Done</translate>
|
||||
<tbody>
|
||||
<tr ng-repeat="item in items | filter: filter.search | filter: {is_hidden: filter.showHiddenItems}
|
||||
| filter: {closed: filter.showClosedItems}"
|
||||
<tr ng-repeat="item in itemsFiltered = (items | filter: filter.search |
|
||||
filter: {is_hidden: filter.showHiddenItems} | filter: {closed: filter.showClosedItems})"
|
||||
class="animate-item"
|
||||
ng-class="{ 'activeline': item.isProjected(), 'selected': item.selected, 'hiddenrow': item.is_hidden}">
|
||||
<!-- projector column -->
|
||||
@ -131,8 +134,8 @@
|
||||
<td ng-show="isDeleteMode" os-perms="agenda.can_manage" class="deleteColumn">
|
||||
<input type="checkbox" ng-model="item.selected">
|
||||
<!-- agenda data columns -->
|
||||
<td ng-if="!item.quickEdit" ng-mouseover="item.hover=true" ng-mouseleave="item.hover=false">
|
||||
<span ng-repeat="n in [].constructor(item.parentCount) track by $index">–</span>
|
||||
<td ng-if="!item.quickEdit" ng-mouseover="item.hover=true" ng-mouseleave="item.hover=false"
|
||||
style="padding-left: calc(8px + {{ item.parentCount }}*15px)">
|
||||
<strong>
|
||||
<a href="" ng-click="open(item)">
|
||||
{{ item.getTitle() }}
|
||||
@ -144,8 +147,8 @@
|
||||
</div>
|
||||
<div os-perms="agenda.can_manage" class="hoverActions" ng-class="{'hiddenDiv': !item.hover}">
|
||||
<a ui-sref="agenda.item.detail({id: item.id})" translate>List of speakers</a> |
|
||||
<a href="" ng-click="item.quickEdit=true" translate>QuickEdit</a> |
|
||||
<a href="" ng-click="editDialog(item)" translate>Edit</a>
|
||||
<a href="" ng-click="editDialog(item)" translate>Edit</a> |
|
||||
<a href="" ng-click="item.quickEdit=true" translate>QuickEdit</a>
|
||||
<span ng-if="item.content_object.collection == 'core/customslide'"> |
|
||||
<a href="" class="text-danger"
|
||||
ng-bootbox-confirm="{{ 'Are you sure you want to delete this entry?' | translate }}<br>
|
||||
@ -162,9 +165,9 @@
|
||||
<td ng-show="item.quickEdit" os-perms="agenda.can_manage" colspan="3">
|
||||
<form ng-submit="save(item)">
|
||||
<h4>{{ item.getTitle() }} <span class="text-muted">– QuickEdit</span></h4>
|
||||
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
{{alert.msg}}
|
||||
</alert>
|
||||
<uib-alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
{{ alert.msg }}
|
||||
</uib-alert>
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<label for="inputTitle" translate>Title</label>
|
||||
|
@ -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) {
|
||||
|
@ -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');
|
||||
}
|
||||
]);
|
||||
|
||||
}());
|
||||
|
@ -18,63 +18,72 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
}
|
||||
])
|
||||
|
||||
.config(function($stateProvider) {
|
||||
$stateProvider
|
||||
.state('assignments', {
|
||||
url: '/assignments',
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.state('assignments.assignment', {
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.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: "<ui-view/>",
|
||||
})
|
||||
.state('assignments.assignment', {
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.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};
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -39,6 +39,42 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="meta">
|
||||
<div class="title" ng-click="isMeta = !isMeta">
|
||||
<div class="name">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
<translate>Meta information</translate>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="fa fa-lg" ng-class="isMeta ? 'fa-angle-down' : 'fa-angle-up'"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail" uib-collapse="isMeta">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<!-- posts -->
|
||||
<h3 translate>Number of posts to be elected</h3>
|
||||
{{ assignment.open_posts }}<br>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<!-- phase -->
|
||||
<h3 translate>Phase</h3>
|
||||
<span class="label" ng-class="{'label-primary': assignment.phase == 0,
|
||||
'label-warning': assignment.phase == 1,
|
||||
'label-success': assignment.phase == 2 }">
|
||||
{{ phases[assignment.phase].display_name | translate }}
|
||||
</span>
|
||||
<div os-perms="assignments.can_manage" class="spacer">
|
||||
<select ng-model="phaseSelect" class="form-control" ng-change="updatePhase(phaseSelect)">
|
||||
<option value="" translate>--- Set phase ---</option>
|
||||
<option ng-repeat="phase in phases" value="{{ phase.value }}">{{ phase.display_name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="details">
|
||||
<h3 translate>Description</h3>
|
||||
<div class="white-space-pre-line">{{ assignment.description }}</div>
|
||||
@ -55,9 +91,9 @@
|
||||
</ol>
|
||||
|
||||
<div class="form-group">
|
||||
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
<uib-alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
{{ alert.msg }}
|
||||
</alert>
|
||||
</uib-alert>
|
||||
<div os-perms="assignments.can_nominate_other" class="input-group">
|
||||
<ui-select ng-model="candidateSelectBox.selected" ng-change="addCandidate(candidateSelectBox.selected.id)">
|
||||
<ui-select-match placeholder="{{ 'Select or search a participant ...' | translate }}">
|
||||
@ -91,7 +127,8 @@
|
||||
</button>
|
||||
|
||||
<uib-tabset class="spacer">
|
||||
<uib-tab ng-repeat="poll in assignment.polls | orderBy:'-id'" heading="Ballot {{ assignment.polls.length - $index }}">
|
||||
<uib-tab ng-repeat="poll in assignment.polls | orderBy:'-id'"
|
||||
heading="{{ 'Ballot' | translate }} {{ assignment.polls.length - $index }}">
|
||||
<div os-perms="assignments.can_manage" class="spacer">
|
||||
<!-- angular requires to open the link in new tab with "target='_blank'".
|
||||
Otherwise the pdf url can't be open in same window; angular redirects to "/". -->
|
||||
@ -107,17 +144,17 @@
|
||||
2. <translate>Enter votes</translate>
|
||||
</button>
|
||||
<i class="fa fa-arrow-right"></i>
|
||||
<button os-perms-lite="assignments.can_manage" ng-if="!poll.published" ng-click="publishBallot(poll, true)"
|
||||
<button ng-if="!poll.published" ng-click="publishBallot(poll, true)"
|
||||
class="btn btn-default btn-sm">
|
||||
<i class="fa fa-toggle-off"></i>
|
||||
3. <translate>Publish ballot</translate>
|
||||
</button>
|
||||
<button os-perms-lite="assignments.can_manage" ng-if="poll.published" ng-click="publishBallot(poll, false)"
|
||||
<button ng-if="poll.published" ng-click="publishBallot(poll, false)"
|
||||
class="btn btn-default btn-sm">
|
||||
<i class="fa fa-toggle-on"></i>
|
||||
<translate>Published</translate>
|
||||
</button>
|
||||
<a ng-click="deleteBallot(poll)" class="btn btn-default btn-sm">
|
||||
<a ng-click="deleteBallot(poll)" class="btn btn-danger btn-sm">
|
||||
<i class="fa fa-times"></i>
|
||||
<translate>Delete</translate>
|
||||
</a>
|
||||
|
@ -1,6 +1,10 @@
|
||||
<h1 ng-if="model.id" translate>Edit election</h1>
|
||||
<h1 ng-if="!model.id" translate>New election</h1>
|
||||
|
||||
<uib-alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
{{ alert.msg }}
|
||||
</uib-alert>
|
||||
|
||||
<form name="assignmentForm" ng-submit="save(model)">
|
||||
<formly-form model="model" fields="formFields">
|
||||
<button type="submit" ng-disabled="assignmentForm.$invalid" class="btn btn-primary" translate>
|
||||
|
@ -23,7 +23,8 @@
|
||||
<div class="col-sm-6">
|
||||
<form class="form-inline">
|
||||
<!-- delete mode -->
|
||||
<button os-perms="assignments.can_manage" class="btn btn-default"
|
||||
<button os-perms="assignments.can_manage" class="btn"
|
||||
ng-class="$parent.isDeleteMode ? 'btn-primary' : 'btn-default'"
|
||||
ng-click="$parent.isDeleteMode = !$parent.isDeleteMode; uncheckAll()">
|
||||
<i class="fa fa-check-square-o"></i>
|
||||
<translate>Select ...</translate>
|
||||
@ -39,7 +40,8 @@
|
||||
placeholder="{{ 'Search' | translate}}">
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-default" ng-click="isFilterOpen = !isFilterOpen">
|
||||
<button class="btn btn-default" ng-click="isFilterOpen = !isFilterOpen"
|
||||
ng-class="isFilterOpen ? 'btn-primary' : 'btn-default'">
|
||||
<i class="fa fa-filter"></i>
|
||||
<translate>Filter ...</translate>
|
||||
</button>
|
||||
@ -69,6 +71,7 @@
|
||||
</div>
|
||||
|
||||
<div class="spacer-top-lg italic">
|
||||
{{ assignmentsFiltered.length }} /
|
||||
{{ assignments.length }} {{ "elections" | translate }}<span ng-if="(assignments|filter:{selected:true}).length > 0">,
|
||||
{{(assignments|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
|
||||
</div>
|
||||
@ -96,8 +99,8 @@
|
||||
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
|
||||
</i>
|
||||
<tbody>
|
||||
<tr ng-repeat="assignment in assignments | filter: filter.search | filter: {phase: phaseFilter} |
|
||||
orderBy: sortColumn:reverse"
|
||||
<tr ng-repeat="assignment in assignmentsFiltered = (assignments | osFilter: filter.search : getFilterString |
|
||||
filter: {phase: phaseFilter} | orderBy: sortColumn:reverse)"
|
||||
class="animate-item"
|
||||
ng-class="{ 'activeline': assignment.isProjected(), 'selected': assignment.selected }">
|
||||
<!-- projector column -->
|
||||
@ -135,9 +138,9 @@
|
||||
<!-- quickEdit columns -->
|
||||
<td ng-if="assignment.quickEdit" colspan="3">
|
||||
<h4>{{ assignment.title }} <span class="text-muted">– Quick Edit</span></h4>
|
||||
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
{{alert.msg}}
|
||||
</alert>
|
||||
<uib-alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
{{ alert.msg }}
|
||||
</uib-alert>
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<label for="inputTitle" translate>Title</label>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<h1><translate>Ballot</translate> {{ ballot }}</h1>
|
||||
|
||||
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
<uib-alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
{{ alert.msg }}
|
||||
</alert>
|
||||
</uib-alert>
|
||||
|
||||
<p>
|
||||
<translate>Special values</translate>:
|
||||
|
@ -1,4 +1,12 @@
|
||||
<div ng-controller="SlideAssignmentCtrl" class="content scrollcontent">
|
||||
<h1>{{ assignment.title }}</h1>
|
||||
<div class="white-space-pre-line">{{ assignment.description }}</div>
|
||||
|
||||
<!-- candidates -->
|
||||
<h2 translate>Candidates</h2>
|
||||
<ol>
|
||||
<li ng-repeat="related_user in assignment.assignment_related_users">
|
||||
{{ related_user.user.get_full_name() }}
|
||||
<i ng-if="related_user.elected" class="fa fa-star" title="{{ 'is elected' | translate }}"></i>
|
||||
</ol>
|
||||
</div>
|
||||
|
@ -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})
|
||||
|
@ -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; }
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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: "<ui-view/>",
|
||||
})
|
||||
// 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: "<ui-view/>",
|
||||
})
|
||||
.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: "<ui-view/>",
|
||||
})
|
||||
.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: "<ui-view/>",
|
||||
})
|
||||
// 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: "<ui-view/>",
|
||||
})
|
||||
.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: "<ui-view/>",
|
||||
})
|
||||
.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();
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
||||
|
||||
}());
|
||||
|
@ -17,6 +17,12 @@
|
||||
title="{{ 'Project agenda item' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</a>
|
||||
<!-- edit -->
|
||||
<a os-perms="agenda.can_manage" ng-click="openDialog(customslide)"
|
||||
class="btn btn-default btn-sm"
|
||||
title="{{ 'Edit' | translate}}">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</a>
|
||||
</div>
|
||||
<h1>{{ customslide.agenda_item.getTitle() }}</h1>
|
||||
<h2 translate>Agenda item</h2>
|
||||
|
@ -1,6 +1,10 @@
|
||||
<h1 ng-if="model.id" translate>Edit agenda item</h1>
|
||||
<h1 ng-if="!model.id" translate>New agenda item</h1>
|
||||
|
||||
<uib-alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
{{ alert.msg }}
|
||||
</uib-alert>
|
||||
|
||||
<form name="customslideForm" ng-submit="save(model)">
|
||||
<formly-form model="model" fields="formFields">
|
||||
<button type="submit" ng-disabled="customslideForm.$invalid" class="btn btn-primary" translate>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="modal-header">
|
||||
<img src="/static/img/openslides-logo.png" alt="OpenSlides" class="login-logo center-block">
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-body loginForm">
|
||||
<uib-alert ng-repeat="alert in alerts" type="{{ alert.type }}" close="closeAlert($index)">
|
||||
<span ng-bind-html="alert.msg"><span>
|
||||
</uib-alert>
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
<div ng-controller="ProjectorControlCtrl">
|
||||
<!-- live view -->
|
||||
<div class="section" os-perms-lite="core.can_see_projector">
|
||||
<div class="section" os-perms="core.can_see_projector">
|
||||
<a href="#" ng-click="isLiveViewClosed = !isLiveViewClosed">
|
||||
<i class="fa toggle-icon" ng-class="isLiveViewClosed ? 'fa-angle-up' : 'fa-angle-down'"></i>
|
||||
<h4 translate>Live view</h4>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div ng-controller="SlideCountdownCtrl">
|
||||
<div ng-if="visible">
|
||||
<div class="countdown well" style="margin-top: calc({{index}}*100px);">
|
||||
<div class="countdown well" style="margin-top: calc({{index}}*100px);" ng-class="{ 'negative': seconds < 0 }">
|
||||
{{ seconds | osSecondsToTime}}
|
||||
<div class="description">{{ description }}</div>
|
||||
</div>
|
||||
|
@ -18,69 +18,88 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
|
||||
}
|
||||
])
|
||||
|
||||
.config(function($stateProvider) {
|
||||
$stateProvider
|
||||
.state('mediafiles', {
|
||||
url: '/mediafiles',
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.state('mediafiles.mediafile', {
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.state('mediafiles.mediafile.list', {
|
||||
resolve: {
|
||||
mediafiles: function(Mediafile) {
|
||||
return Mediafile.findAll();
|
||||
.config([
|
||||
'$stateProvider',
|
||||
function($stateProvider) {
|
||||
$stateProvider
|
||||
.state('mediafiles', {
|
||||
url: '/mediafiles',
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.state('mediafiles.mediafile', {
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.state('mediafiles.mediafile.list', {
|
||||
resolve: {
|
||||
mediafiles: function(Mediafile) {
|
||||
return Mediafile.findAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.state('mediafiles.mediafile.create', {})
|
||||
.state('mediafiles.mediafile.detail', {
|
||||
url: '/{id:int}',
|
||||
abstract: true,
|
||||
resolve: {
|
||||
mediafile: function(Mediafile, $stateParams) {
|
||||
var id = $stateParams.id;
|
||||
var file = Mediafile.find(id);
|
||||
return file;
|
||||
})
|
||||
.state('mediafiles.mediafile.create', {})
|
||||
.state('mediafiles.mediafile.detail', {
|
||||
url: '/{id:int}',
|
||||
abstract: true,
|
||||
resolve: {
|
||||
mediafile: function(Mediafile, $stateParams) {
|
||||
var id = $stateParams.id;
|
||||
var file = Mediafile.find(id);
|
||||
return file;
|
||||
}
|
||||
},
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.state('mediafiles.mediafile.detail.update', {
|
||||
views: {
|
||||
'@mediafiles.mediafile': {}
|
||||
}
|
||||
},
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.state('mediafiles.mediafile.detail.update', {
|
||||
views: {
|
||||
'@mediafiles.mediafile': {}
|
||||
});
|
||||
}
|
||||
])
|
||||
|
||||
.controller('MediafileListCtrl', [
|
||||
'$scope',
|
||||
'$http',
|
||||
'$timeout',
|
||||
'Upload',
|
||||
'Mediafile',
|
||||
function($scope, $http, $timeout, Upload, Mediafile) {
|
||||
Mediafile.bindAll({}, $scope, 'mediafiles');
|
||||
|
||||
// setup table sorting
|
||||
$scope.sortColumn = 'title';
|
||||
$scope.filterPresent = '';
|
||||
$scope.reverse = false;
|
||||
// function to sort by clicked column
|
||||
$scope.toggleSort = function ( column ) {
|
||||
if ( $scope.sortColumn === column ) {
|
||||
$scope.reverse = !$scope.reverse;
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
.controller('MediafileListCtrl', function($scope, $http, $timeout, Upload, Mediafile) {
|
||||
Mediafile.bindAll({}, $scope, 'mediafiles');
|
||||
|
||||
// setup table sorting
|
||||
$scope.sortColumn = 'title';
|
||||
$scope.filterPresent = '';
|
||||
$scope.reverse = false;
|
||||
// function to sort by clicked column
|
||||
$scope.toggleSort = function ( column ) {
|
||||
if ( $scope.sortColumn === column ) {
|
||||
$scope.reverse = !$scope.reverse;
|
||||
$scope.sortColumn = column;
|
||||
};
|
||||
// define custom search filter string
|
||||
$scope.getFilterString = function (mediafile) {
|
||||
return [
|
||||
mediafile.title,
|
||||
mediafile.mediafile.type,
|
||||
mediafile.mediafile.name,
|
||||
mediafile.uploader.get_short_name()
|
||||
].join(" ");
|
||||
}
|
||||
$scope.sortColumn = column;
|
||||
};
|
||||
|
||||
// delete
|
||||
$scope.delete = function (mediafile) {
|
||||
//TODO: add confirm message
|
||||
Mediafile.destroy(mediafile.id).then(
|
||||
function(success) {
|
||||
//TODO: success message
|
||||
}
|
||||
);
|
||||
};
|
||||
})
|
||||
// delete
|
||||
$scope.delete = function (mediafile) {
|
||||
//TODO: add confirm message
|
||||
Mediafile.destroy(mediafile.id).then(
|
||||
function(success) {
|
||||
//TODO: success message
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.controller('MediafileCreateCtrl', [
|
||||
'$scope',
|
||||
|
@ -51,16 +51,16 @@
|
||||
</th>
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr ng-repeat="mediafile in mediafiles | filter: filter.search |
|
||||
<tr ng-repeat="mediafile in mediafiles | osFilter: filter.search : getFilterString |
|
||||
orderBy: sortColumn:reverse">
|
||||
<td><a ng-href="{{ mediafile.mediafileUrl }}" target="_blank">{{ mediafile.title_or_filename }}</a>
|
||||
<td class="optional">{{ mediafile.mediafile.type }}
|
||||
<td>{{ mediafile.filesize }}
|
||||
<td>{{ mediafile.timestamp }}
|
||||
<td>{{ mediafile.timestamp | date:'yyyy-MM-dd HH:mm:ss' }}
|
||||
<td>{{ mediafile.uploader.get_full_name() }}
|
||||
<td os-perms="mediafiles.can_manage core.can_manage_projector" class="nobr">
|
||||
<!-- projector, TODO: add link to activate slide -->
|
||||
<a href="#TODO" ng-if="mediafile.is_presentable" os-perms-lite="core.can_manage_projector"
|
||||
<a href="" ng-show="mediafile.mediafile.type == 'application/pdf'" os-perms="core.can_manage_projector"
|
||||
class="btn btn-default btn-sm"
|
||||
title="{{ 'Show' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
|
@ -169,6 +169,7 @@ angular.module('OpenSlidesApp.motions', ['OpenSlidesApp.users'])
|
||||
name: name,
|
||||
useClass: jsDataModel,
|
||||
verboseName: gettext('Motion'),
|
||||
agendaSupplement: gettext('Motion'),
|
||||
methods: {
|
||||
getResourceName: function () {
|
||||
return name;
|
||||
@ -298,12 +299,18 @@ angular.module('OpenSlidesApp.motions', ['OpenSlidesApp.users'])
|
||||
}
|
||||
])
|
||||
|
||||
.factory('Category', ['DS', function(DS) {
|
||||
return DS.defineResource({
|
||||
name: 'motions/category',
|
||||
});
|
||||
}])
|
||||
|
||||
.run(['Motion', 'Category', function(Motion, Category) {}]);
|
||||
.factory('Category', [
|
||||
'DS',
|
||||
function(DS) {
|
||||
return DS.defineResource({
|
||||
name: 'motions/category',
|
||||
});
|
||||
}
|
||||
])
|
||||
|
||||
.run([
|
||||
'Motion',
|
||||
'Category',
|
||||
function(Motion, Category) {}
|
||||
]);
|
||||
}());
|
||||
|
@ -4,20 +4,28 @@
|
||||
|
||||
angular.module('OpenSlidesApp.motions.projector', ['OpenSlidesApp.motions'])
|
||||
|
||||
.config(function(slidesProvider) {
|
||||
slidesProvider.registerSlide('motions/motion', {
|
||||
template: 'static/templates/motions/slide_motion.html',
|
||||
});
|
||||
})
|
||||
.config([
|
||||
'slidesProvider',
|
||||
function(slidesProvider) {
|
||||
slidesProvider.registerSlide('motions/motion', {
|
||||
template: 'static/templates/motions/slide_motion.html',
|
||||
});
|
||||
}
|
||||
])
|
||||
|
||||
.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');
|
||||
});
|
||||
.controller('SlideMotionCtrl', [
|
||||
'$scope',
|
||||
'Motion',
|
||||
'User',
|
||||
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');
|
||||
}
|
||||
]);
|
||||
|
||||
}());
|
||||
|
@ -18,121 +18,124 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
}
|
||||
])
|
||||
|
||||
.config(function($stateProvider) {
|
||||
$stateProvider
|
||||
.state('motions', {
|
||||
url: '/motions',
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.state('motions.motion', {
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.state('motions.motion.list', {
|
||||
resolve: {
|
||||
motions: function(Motion) {
|
||||
return Motion.findAll();
|
||||
},
|
||||
categories: function(Category) {
|
||||
return Category.findAll();
|
||||
},
|
||||
tags: function(Tag) {
|
||||
return Tag.findAll();
|
||||
},
|
||||
users: function(User) {
|
||||
return User.findAll();
|
||||
},
|
||||
workflows: function(Workflow) {
|
||||
return Workflow.findAll();
|
||||
.config([
|
||||
'$stateProvider',
|
||||
function($stateProvider) {
|
||||
$stateProvider
|
||||
.state('motions', {
|
||||
url: '/motions',
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.state('motions.motion', {
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.state('motions.motion.list', {
|
||||
resolve: {
|
||||
motions: function(Motion) {
|
||||
return Motion.findAll();
|
||||
},
|
||||
categories: function(Category) {
|
||||
return Category.findAll();
|
||||
},
|
||||
tags: function(Tag) {
|
||||
return Tag.findAll();
|
||||
},
|
||||
users: function(User) {
|
||||
return User.findAll();
|
||||
},
|
||||
workflows: function(Workflow) {
|
||||
return Workflow.findAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.state('motions.motion.detail', {
|
||||
resolve: {
|
||||
motion: function(Motion, $stateParams) {
|
||||
return Motion.find($stateParams.id);
|
||||
},
|
||||
categories: function(Category) {
|
||||
return Category.findAll();
|
||||
},
|
||||
users: function(User) {
|
||||
return User.findAll();
|
||||
},
|
||||
mediafiles: function(Mediafile) {
|
||||
return Mediafile.findAll();
|
||||
},
|
||||
tags: function(Tag) {
|
||||
return Tag.findAll();
|
||||
})
|
||||
.state('motions.motion.detail', {
|
||||
resolve: {
|
||||
motion: function(Motion, $stateParams) {
|
||||
return Motion.find($stateParams.id);
|
||||
},
|
||||
categories: function(Category) {
|
||||
return Category.findAll();
|
||||
},
|
||||
users: function(User) {
|
||||
return User.findAll();
|
||||
},
|
||||
mediafiles: function(Mediafile) {
|
||||
return Mediafile.findAll();
|
||||
},
|
||||
tags: function(Tag) {
|
||||
return Tag.findAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
// redirects to motion detail and opens motion edit form dialog, uses edit url,
|
||||
// used by ui-sref links from agenda only
|
||||
// (from motion controller use MotionForm factory instead to open dialog in front of
|
||||
// current view without redirect)
|
||||
.state('motions.motion.detail.update', {
|
||||
onEnter: ['$stateParams', '$state', 'ngDialog', 'Motion',
|
||||
function($stateParams, $state, ngDialog, Motion) {
|
||||
ngDialog.open({
|
||||
template: 'static/templates/motions/motion-form.html',
|
||||
controller: 'MotionUpdateCtrl',
|
||||
className: 'ngdialog-theme-default wide-form',
|
||||
closeByEscape: false,
|
||||
closeByDocument: false,
|
||||
resolve: {
|
||||
motion: function() {return Motion.find($stateParams.id)}
|
||||
},
|
||||
preCloseCallback: function() {
|
||||
$state.go('motions.motion.detail', {motion: $stateParams.id});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
})
|
||||
// redirects to motion detail and opens motion edit form dialog, uses edit url,
|
||||
// used by ui-sref links from agenda only
|
||||
// (from motion controller use MotionForm factory instead to open dialog in front of
|
||||
// current view without redirect)
|
||||
.state('motions.motion.detail.update', {
|
||||
onEnter: ['$stateParams', '$state', 'ngDialog', 'Motion',
|
||||
function($stateParams, $state, ngDialog, Motion) {
|
||||
ngDialog.open({
|
||||
template: 'static/templates/motions/motion-form.html',
|
||||
controller: 'MotionUpdateCtrl',
|
||||
className: 'ngdialog-theme-default wide-form',
|
||||
closeByEscape: false,
|
||||
closeByDocument: false,
|
||||
resolve: {
|
||||
motion: function() {return Motion.find($stateParams.id)}
|
||||
},
|
||||
preCloseCallback: function() {
|
||||
$state.go('motions.motion.detail', {motion: $stateParams.id});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
]
|
||||
})
|
||||
.state('motions.motion.import', {
|
||||
url: '/import',
|
||||
controller: 'MotionImportCtrl',
|
||||
resolve: {
|
||||
motions: function(Motion) {
|
||||
return Motion.findAll();
|
||||
},
|
||||
categories: function(Category) {
|
||||
return Category.findAll();
|
||||
},
|
||||
users: function(User) {
|
||||
return User.findAll();
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
.state('motions.motion.import', {
|
||||
url: '/import',
|
||||
controller: 'MotionImportCtrl',
|
||||
resolve: {
|
||||
motions: function(Motion) {
|
||||
return Motion.findAll();
|
||||
},
|
||||
categories: function(Category) {
|
||||
return Category.findAll();
|
||||
},
|
||||
users: function(User) {
|
||||
return User.findAll();
|
||||
})
|
||||
// categories
|
||||
.state('motions.category', {
|
||||
url: '/category',
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.state('motions.category.list', {
|
||||
resolve: {
|
||||
categories: function(Category) {
|
||||
return Category.findAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
// categories
|
||||
.state('motions.category', {
|
||||
url: '/category',
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.state('motions.category.list', {
|
||||
resolve: {
|
||||
categories: function(Category) {
|
||||
return Category.findAll();
|
||||
})
|
||||
.state('motions.category.create', {})
|
||||
.state('motions.category.detail', {
|
||||
resolve: {
|
||||
category: function(Category, $stateParams) {
|
||||
return Category.find($stateParams.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.state('motions.category.create', {})
|
||||
.state('motions.category.detail', {
|
||||
resolve: {
|
||||
category: function(Category, $stateParams) {
|
||||
return Category.find($stateParams.id);
|
||||
})
|
||||
.state('motions.category.detail.update', {
|
||||
views: {
|
||||
'@motions.category': {}
|
||||
}
|
||||
}
|
||||
})
|
||||
.state('motions.category.detail.update', {
|
||||
views: {
|
||||
'@motions.category': {}
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
])
|
||||
|
||||
// Service for generic motion form (create and update)
|
||||
.factory('MotionForm', [
|
||||
@ -212,6 +215,15 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
},
|
||||
ngModelElAttrs: {'ckeditor': 'CKEditorOptions'}
|
||||
},
|
||||
{
|
||||
key: 'disable_versioning',
|
||||
type: 'checkbox',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Trivial change'),
|
||||
description: gettextCatalog.getString("Don't create a new version.")
|
||||
},
|
||||
hide: true
|
||||
},
|
||||
{
|
||||
key: 'more',
|
||||
type: 'checkbox',
|
||||
@ -387,6 +399,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
}
|
||||
$scope.sortColumn = column;
|
||||
};
|
||||
// define custom search filter string
|
||||
$scope.getFilterString = function (motion) {
|
||||
if (motion.category) {
|
||||
var category = motion.category.name;
|
||||
@ -394,10 +407,15 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
var category = ''
|
||||
}
|
||||
return [
|
||||
motion.identifier,
|
||||
motion.getTitle(),
|
||||
motion.getText(),
|
||||
motion.getReason(),
|
||||
_.map(motion.submitters, function (submitter) {return submitter.get_short_name()}).join(" "),
|
||||
category].join(" ");
|
||||
_.map(motion.supporters, function (supporter) {return supporter.get_short_name()}).join(" "),
|
||||
_.map(motion.tags, function (tag) {return tag.name}).join(" "),
|
||||
category
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
// collect all states of all workflows
|
||||
@ -492,7 +510,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
User.bindAll({}, $scope, 'users');
|
||||
Workflow.bindAll({}, $scope, 'workflows');
|
||||
Motion.loadRelations(motion, 'agenda_item');
|
||||
|
||||
$scope.version = motion.active_version;
|
||||
$scope.isCollapsed = true;
|
||||
|
||||
// open edit dialog
|
||||
@ -541,6 +559,27 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
$scope.delete_poll = function (poll) {
|
||||
poll.DSDestroy();
|
||||
}
|
||||
// show specific version
|
||||
$scope.showVersion = function (version) {
|
||||
$scope.version = version.id;
|
||||
}
|
||||
// permit specific version
|
||||
$scope.permitVersion = function (version) {
|
||||
$http.put('/rest/motions/motion/' + motion.id + '/manage_version/',
|
||||
{'version_number': version.version_number})
|
||||
.then(function(success) {
|
||||
$scope.version = version.id;
|
||||
});
|
||||
}
|
||||
// delete specific version
|
||||
$scope.deleteVersion = function (version) {
|
||||
$http.delete('/rest/motions/motion/' + motion.id + '/manage_version/',
|
||||
{headers: {'Content-Type': 'application/json'},
|
||||
data: JSON.stringify({version_number: version.version_number})})
|
||||
.then(function(success) {
|
||||
$scope.version = motion.active_version;
|
||||
});
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
@ -571,11 +610,6 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
$scope.model.workflow_id = Config.get('motions_workflow').value;
|
||||
// get all form fields
|
||||
$scope.formFields = MotionForm.getFormFields();
|
||||
for (var i = 0; i < $scope.formFields.length; i++) {
|
||||
if ($scope.formFields[i].key == "identifier") {
|
||||
$scope.formFields[i].hide = true;
|
||||
}
|
||||
}
|
||||
// save motion
|
||||
$scope.save = function (motion) {
|
||||
Motion.create(motion).then(
|
||||
@ -605,10 +639,13 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
Tag.bindAll({}, $scope, 'tags');
|
||||
User.bindAll({}, $scope, 'users');
|
||||
Workflow.bindAll({}, $scope, 'workflows');
|
||||
$scope.alert = {};
|
||||
|
||||
// set initial values for form model
|
||||
$scope.model = motion;
|
||||
// set initial values for form model by create deep copy of motion object
|
||||
// so list/detail view is not updated while editing
|
||||
$scope.model = angular.copy(motion);
|
||||
$scope.model.more = false;
|
||||
|
||||
// get all form fields
|
||||
$scope.formFields = MotionForm.getFormFields();
|
||||
// override default values for update form
|
||||
@ -629,6 +666,13 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
// get reason of latest version
|
||||
$scope.formFields[i].defaultValue = motion.getReason(-1);
|
||||
}
|
||||
if ($scope.formFields[i].key == "disable_versioning" &&
|
||||
Config.get('motions_allow_disable_versioning')) {
|
||||
// check current state if versioning is active
|
||||
if (motion.state.versioning) {
|
||||
$scope.formFields[i].hide = false;
|
||||
}
|
||||
}
|
||||
if ($scope.formFields[i].key == "workflow_id") {
|
||||
// get saved workflow id from state
|
||||
$scope.formFields[i].defaultValue = motion.state.workflow_id;
|
||||
@ -637,9 +681,22 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
|
||||
// save motion
|
||||
$scope.save = function (motion) {
|
||||
// inject the changed motion (copy) object back into DS store
|
||||
Motion.inject(motion);
|
||||
// save change motion object on server
|
||||
Motion.save(motion, { method: 'PATCH' }).then(
|
||||
function(success) {
|
||||
$scope.closeThisDialog();
|
||||
},
|
||||
function (error) {
|
||||
// save error: revert all changes by restore
|
||||
// (refresh) original motion object from server
|
||||
Motion.refresh(motion);
|
||||
var message = '';
|
||||
for (var e in error.data) {
|
||||
message += e + ': ' + error.data[e] + ' ';
|
||||
}
|
||||
$scope.alert = {type: 'danger', msg: message, show: true};
|
||||
}
|
||||
);
|
||||
};
|
||||
@ -858,50 +915,70 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
])
|
||||
|
||||
|
||||
.controller('CategoryListCtrl', function($scope, Category) {
|
||||
Category.bindAll({}, $scope, 'categories');
|
||||
.controller('CategoryListCtrl', [
|
||||
'$scope',
|
||||
'Category',
|
||||
function($scope, Category) {
|
||||
Category.bindAll({}, $scope, 'categories');
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
// delete selected category
|
||||
$scope.delete = function (category) {
|
||||
Category.destroy(category.id);
|
||||
};
|
||||
})
|
||||
|
||||
.controller('CategoryDetailCtrl', function($scope, Category, category) {
|
||||
Category.bindOne(category.id, $scope, 'category');
|
||||
})
|
||||
|
||||
.controller('CategoryCreateCtrl', function($scope, $state, Category) {
|
||||
$scope.category = {};
|
||||
$scope.save = function (category) {
|
||||
Category.create(category).then(
|
||||
function(success) {
|
||||
$state.go('motions.category.list');
|
||||
// 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('CategoryUpdateCtrl', function($scope, $state, Category, category) {
|
||||
$scope.category = category;
|
||||
$scope.save = function (category) {
|
||||
Category.save(category).then(
|
||||
function(success) {
|
||||
$state.go('motions.category.list');
|
||||
}
|
||||
);
|
||||
};
|
||||
});
|
||||
// delete selected category
|
||||
$scope.delete = function (category) {
|
||||
Category.destroy(category.id);
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.controller('CategoryDetailCtrl', [
|
||||
'$scope',
|
||||
'Category',
|
||||
'category',
|
||||
function($scope, Category, category) {
|
||||
Category.bindOne(category.id, $scope, 'category');
|
||||
}
|
||||
])
|
||||
|
||||
.controller('CategoryCreateCtrl', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'Category',
|
||||
function($scope, $state, Category) {
|
||||
$scope.category = {};
|
||||
$scope.save = function (category) {
|
||||
Category.create(category).then(
|
||||
function(success) {
|
||||
$state.go('motions.category.list');
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.controller('CategoryUpdateCtrl', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'Category',
|
||||
'category',
|
||||
function($scope, $state, Category, category) {
|
||||
$scope.category = category;
|
||||
$scope.save = function (category) {
|
||||
Category.save(category).then(
|
||||
function(success) {
|
||||
$state.go('motions.category.list');
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
]);
|
||||
|
||||
}());
|
||||
|
@ -28,10 +28,14 @@
|
||||
<i class="fa fa-pencil"></i>
|
||||
</a>
|
||||
</div>
|
||||
<h1>{{ motion.getTitle() }}</h1>
|
||||
<h1>{{ motion.getTitle(version) }}</h1>
|
||||
<h2>
|
||||
<translate>Motion</translate> {{ motion.identifier }}
|
||||
<span ng-if="motion.versions.length > 1" >| Version {{ motion.active_version }}</span>
|
||||
<span ng-if="motion.versions.length > 1" >| Version {{ motion.getVersion(version).version_number }}</span>
|
||||
<span ng-if="motion.active_version != version" class="label label-warning">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
<translate>This version is not permitted.</translate>
|
||||
</span>
|
||||
<span ng-if="motion.agenda_item.item_number">
|
||||
–
|
||||
<translate>Agenda</translate>: {{ motion.agenda_item.item_number }}
|
||||
@ -193,10 +197,10 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<h3 translate>Text</h3>
|
||||
<div class="white-space-pre-line" ng-bind-html="motion.getText()"></div>
|
||||
<div class="white-space-pre-line" ng-bind-html="motion.getText(version)"></div>
|
||||
|
||||
<!-- reason -->
|
||||
<div ng-if="motion.getReason() != ''">
|
||||
<div ng-if="motion.getReason(version) != ''">
|
||||
<h3 translate>Reason</h3>
|
||||
<div class="white-space-pre-line" ng-bind-html="motion.getReason()"></div>
|
||||
</div>
|
||||
@ -208,6 +212,39 @@
|
||||
<a href="{{ attachment.mediafileUrl }}" target="_blank">{{ attachment.title_or_filename }}</a>
|
||||
</ul>
|
||||
|
||||
<!-- version history -->
|
||||
<div ng-if="motion.versions.length > 1">
|
||||
<h3 translate>Version history</h3>
|
||||
<ul>
|
||||
<li ng-repeat="version in motion.versions">
|
||||
<span ng-if="$parent.version == version.id">
|
||||
<strong><translate>Version</translate> {{ version.version_number }}</strong>
|
||||
</span>
|
||||
<a ng-if="$parent.version != version.id" ng-click="showVersion(version)">
|
||||
<translate>Version</translate> {{ version.version_number }}
|
||||
</a>
|
||||
<!-- active version -->
|
||||
<i ng-if="motion.active_version == version.id" class="fa fa-star"
|
||||
title="{{ 'This is the last active version.' | translate }}"></i>
|
||||
<!-- permit version -->
|
||||
<button os-perms="motions.can_manage" ng-show="motion.active_version != version.id"
|
||||
ng-click="permitVersion(version)"
|
||||
class="btn btn-default btn-xs" title="{{ 'Permit and activate' | translate }}">
|
||||
<i class="fa fa-check"></i>
|
||||
</button>
|
||||
<!-- delete version -->
|
||||
<button os-perms="motions.can_manage" ng-show="motion.active_version != version.id"
|
||||
class="btn btn-default btn-xs"
|
||||
ng-bootbox-confirm="{{ 'Are you sure you want to delete this version?' | translate }}<br>
|
||||
<strong><translate>Version</translate> {{ version.version_number }}</strong>"
|
||||
ng-bootbox-confirm-action="deleteVersion(version)"
|
||||
title="{{ 'Delete' | translate }}">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
<br><translate>Last modified</translate>: {{ version.creation_time | date:'yyyy-MM-dd HH:mm:ss' }}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- log -->
|
||||
<button type="button" class="btn btn-default spacer" ng-click="isCollapsed = !isCollapsed" translate>
|
||||
Show history
|
||||
|
@ -1,6 +1,10 @@
|
||||
<h1 ng-if="model.id" translate>Edit motion</h1>
|
||||
<h1 ng-if="!model.id" translate>New motion</h1>
|
||||
|
||||
<uib-alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
{{ alert.msg }}
|
||||
</uib-alert>
|
||||
|
||||
<form name="motionForm" ng-submit="save(model)">
|
||||
<formly-form model="model" fields="formFields">
|
||||
<button type="submit" ng-disabled="motionForm.$invalid" class="btn btn-primary" translate>
|
||||
|
@ -30,7 +30,8 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<!-- delete mode -->
|
||||
<button os-perms="motions.can_manage" class="btn btn-default"
|
||||
<button os-perms="motions.can_manage" class="btn"
|
||||
ng-class="$parent.isDeleteMode ? 'btn-primary' : 'btn-default'"
|
||||
ng-click="$parent.isDeleteMode = !$parent.isDeleteMode; uncheckAll()">
|
||||
<i class="fa fa-check-square-o"></i>
|
||||
<translate>Select ...</translate>
|
||||
@ -45,7 +46,8 @@
|
||||
placeholder="{{ 'Search' | translate}}">
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-default" ng-click="isFilterOpen = !isFilterOpen">
|
||||
<button class="btn btn-default" ng-click="isFilterOpen = !isFilterOpen"
|
||||
ng-class="isFilterOpen ? 'btn-primary' : 'btn-default'">
|
||||
<i class="fa fa-filter"></i>
|
||||
<translate>Filter ...</translate>
|
||||
</button>
|
||||
@ -75,6 +77,7 @@
|
||||
</div>
|
||||
|
||||
<div class="spacer-top-lg italic">
|
||||
{{ motionsFiltered.length }} /
|
||||
{{ motions.length }} {{ "motions" | translate }}<span ng-if="(motions|filter:{selected:true}).length > 0">,
|
||||
{{(motions|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
|
||||
</div>
|
||||
@ -82,9 +85,9 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<!-- projector column -->
|
||||
<th ng-show="!$parent.isDeleteMode" os-perms="core.can_manage_projector" class="firstColumn">
|
||||
<th ng-show="!$parent.isDeleteMode" os-perms="core.can_manage_projector" class="minimum">
|
||||
<!-- delete selection column -->
|
||||
<th ng-show="$parent.isDeleteMode" os-perms="motions.can_manage" class="firstColumn deleteColumn">
|
||||
<th ng-show="$parent.isDeleteMode" os-perms="motions.can_manage" class="minimum deleteColumn">
|
||||
<input type="checkbox" ng-model="$parent.selectedAll" ng-change="checkAll()">
|
||||
<th ng-click="toggleSort('identifier')" class="sortable minimum">
|
||||
<translate>Identifier</translate>
|
||||
@ -112,8 +115,8 @@
|
||||
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
|
||||
</i>
|
||||
<tbody>
|
||||
<tr ng-repeat="motion in motions | osFilter: filter.search : getFilterString | filter: {state_id: stateFilter} |
|
||||
orderBy: sortColumn:reverse"
|
||||
<tr ng-repeat="motion in motionsFiltered = (motions | osFilter: filter.search : getFilterString |
|
||||
filter: {state_id: stateFilter} | orderBy: sortColumn:reverse)"
|
||||
class="animate-item"
|
||||
ng-class="{ 'activeline': motion.isProjected(), 'selected': motion.selected }">
|
||||
<!-- projector column -->
|
||||
@ -161,9 +164,9 @@
|
||||
<!-- quickEdit columns -->
|
||||
<td ng-if="motion.quickEdit && motion.isAllowed('quickedit')" colspan="5">
|
||||
<h4>{{ motion.getTitle() }} <span class="text-muted">– <translate>QuickEdit</translate></span></h4>
|
||||
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
{{alert.msg}}
|
||||
</alert>
|
||||
<uib-alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
{{ alert.msg }}
|
||||
</uib-alert>
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<label for="inputIdentifier" translate>Identifier</label>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<h1><translate>Vote</translate> {{ voteNumber }}</h1>
|
||||
|
||||
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
<uib-alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
{{ alert.msg }}
|
||||
</alert>
|
||||
</uib-alert>
|
||||
|
||||
<p>
|
||||
<translate>Special values</translate>:
|
||||
|
@ -17,7 +17,7 @@
|
||||
<h1>{{ motion.getTitle() }}</h1>
|
||||
<h2>
|
||||
<translate>Motion</translate> {{ motion.identifier }}
|
||||
<span ng-if="motion.versions.length > 1" >| Version {{ motion.active_version }}</span>
|
||||
<span ng-if="motion.versions.length > 1" >| Version {{ motion.getVersion().version_number }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
|
@ -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) {}
|
||||
]);
|
||||
|
||||
}());
|
||||
|
@ -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');
|
||||
}
|
||||
]);
|
||||
|
||||
}());
|
||||
|
@ -18,120 +18,126 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users'])
|
||||
}
|
||||
])
|
||||
|
||||
.config(function($stateProvider) {
|
||||
$stateProvider
|
||||
.state('users', {
|
||||
url: '/users',
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.state('users.user', {
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.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: "<ui-view/>",
|
||||
})
|
||||
.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: "<ui-view/>",
|
||||
})
|
||||
.state('users.user', {
|
||||
abstract: true,
|
||||
template: "<ui-view/>",
|
||||
})
|
||||
.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: "<ui-view/>",
|
||||
})
|
||||
.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 = [];
|
||||
|
@ -1,9 +1,9 @@
|
||||
<h1 ng-if="model.id" translate>Edit participant</h1>
|
||||
<h1 ng-if="!model.id" translate>New participant</h1>
|
||||
|
||||
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
<uib-alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
{{ alert.msg }}
|
||||
</alert>
|
||||
</uib-alert>
|
||||
|
||||
<form name="userForm" ng-submit="save(model)">
|
||||
<formly-form model="model" fields="formFields">
|
||||
|
@ -40,7 +40,8 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<!-- delete mode -->
|
||||
<button os-perms="users.can_manage" class="btn btn-default"
|
||||
<button os-perms="users.can_manage" class="btn"
|
||||
ng-class="$parent.isDeleteMode ? 'btn-primary' : 'btn-default'"
|
||||
ng-click="$parent.isDeleteMode = !$parent.isDeleteMode; uncheckAll()">
|
||||
<i class="fa fa-check-square-o"></i>
|
||||
<translate>Select ...</translate>
|
||||
@ -55,7 +56,8 @@
|
||||
placeholder="{{ 'Search' | translate}}">
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-default" ng-click="isFilterOpen = !isFilterOpen">
|
||||
<button class="btn btn-default" ng-click="isFilterOpen = !isFilterOpen"
|
||||
ng-class="isFilterOpen ? 'btn-primary' : 'btn-default'">
|
||||
<i class="fa fa-filter"></i>
|
||||
<translate>Filter ...</translate>
|
||||
</button>
|
||||
@ -64,7 +66,12 @@
|
||||
</div>
|
||||
<div uib-collapse="!isFilterOpen" class="row spacer">
|
||||
<div class="col-sm-6 text-right"></div>
|
||||
<div class="col-sm-6 text-right">
|
||||
<div class="col-sm-6 text-right form-inline">
|
||||
<!-- group filter -->
|
||||
<select ng-model="groupFilter" class="form-control" id="groupFilter">
|
||||
<option value="" translate>--- Select group ---</option>
|
||||
<option ng-repeat="group in groups" value="{{ group.id }}">{{ group.name }}</option>
|
||||
</select>
|
||||
<!-- isPresent filter -->
|
||||
<input type="checkbox" ng-model="filterPresent" ng-false-value="''">
|
||||
<translate>Is present</translate>
|
||||
@ -83,6 +90,7 @@
|
||||
</div>
|
||||
|
||||
<div class="spacer-top-lg italic">
|
||||
{{ usersFiltered.length }} /
|
||||
{{ users.length }} {{ "participants" | translate }}<span ng-if="(users|filter:{selected:true}).length > 0">,
|
||||
{{(users|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
|
||||
</div>
|
||||
@ -90,9 +98,9 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<!-- projector column -->
|
||||
<th ng-show="!isDeleteMode" os-perms="core.can_manage_projector" class="firstColumn">
|
||||
<th ng-show="!isDeleteMode" os-perms="core.can_manage_projector" class="minimum">
|
||||
<!-- delete selection column -->
|
||||
<th ng-show="isDeleteMode" os-perms="users.can_manage" class="firstColumn deleteColumn">
|
||||
<th ng-show="isDeleteMode" os-perms="users.can_manage" class="minimum deleteColumn">
|
||||
<input type="checkbox" ng-model="$parent.selectedAll" ng-change="checkAll()">
|
||||
<th ng-click="toggleSort('first_name')" class="sortable">
|
||||
<translate>Name</translate>
|
||||
@ -116,8 +124,9 @@
|
||||
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
|
||||
</i>
|
||||
<tbody>
|
||||
<tr ng-repeat="user in users | filter: filter.search | filter: {is_present: filterPresent} |
|
||||
orderBy: sortColumn:reverse"
|
||||
<tr ng-repeat="user in usersFiltered = (users | filter: filter.search | filter: {groups: groupFilter} |
|
||||
filter: {is_present: filterPresent} | orderBy: sortColumn:reverse)"
|
||||
class="animate-item"
|
||||
ng-class="{ 'activeline': user.isProjected(), 'selected': user.selected }">
|
||||
<!-- projector column -->
|
||||
<td ng-show="!isDeleteMode" os-perms="core.can_manage_projector">
|
||||
|
Loading…
Reference in New Issue
Block a user