New assignment and mediafile tables and table generics

This commit is contained in:
FinnStutzenstein 2016-11-18 13:50:40 +01:00
parent d068765db9
commit 4f717b6610
11 changed files with 924 additions and 731 deletions

View File

@ -244,8 +244,11 @@ angular.module('OpenSlidesApp.assignments.site', [
'AssignmentCatalogContentProvider',
'PdfMakeDocumentProvider',
'User',
'osTableFilter',
'osTableSort',
function($scope, ngDialog, AssignmentForm, Assignment, Tag, Agenda, phases, Projector, ProjectionDefault,
gettextCatalog, AssignmentContentProvider, AssignmentCatalogContentProvider, PdfMakeDocumentProvider, User) {
gettextCatalog, AssignmentContentProvider, AssignmentCatalogContentProvider, PdfMakeDocumentProvider,
User, osTableFilter, osTableSort) {
Assignment.bindAll({}, $scope, 'assignments');
Tag.bindAll({}, $scope, 'tags');
$scope.$watch(function () {
@ -259,72 +262,76 @@ angular.module('OpenSlidesApp.assignments.site', [
$scope.phases = phases;
$scope.alert = {};
// 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;
// Filtering
$scope.filter = osTableFilter.createInstance();
$scope.filter.multiselectFilters = {
tag: [],
phase: [],
};
$scope.filter.propertyList = ['title', 'description'];
$scope.filter.propertyFunctionList = [
function (assignment) {
return gettextCatalog.getString($scope.phases[assignment.phase].display_name);
},
];
$scope.filter.propertyDict = {
'assignment_related_users': function (candidate) {
return candidate.user.get_short_name();
},
'tags': function (tag) {
return tag.name;
},
};
$scope.getItemId = {
tag: function (assignment) {return assignment.tags_id;},
phase: function (assignment) {return assignment.phase;},
};
// Sorting
$scope.sort = osTableSort.createInstance();
$scope.sort.column = 'title';
$scope.sortOptions = [
{name: 'agenda_item.getItemNumberWithAncestors()',
display_name: 'Item'},
{name: 'title',
display_name: 'Title'},
{name: 'open_posts',
display_name: 'Open posts'},
{name: 'phase',
display_name: 'Phase'},
];
$scope.hasTag = function (assignment, tag) {
return _.indexOf(assignment.tags_id, tag.id) > -1;
};
$scope.toggleTag = function (assignment, tag) {
if ($scope.hasTag(assignment, tag)) {
assignment.tags_id = _.filter(assignment.tags_id, function (tag_id){
return tag_id != tag.id;
});
} else {
assignment.tags_id.push(tag.id);
}
$scope.sortColumn = column;
Assignment.save(assignment);
};
// 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(" "),
_.map(assignment.tags,
function (tag) {
return tag.name;
}
).join(" "),
].join(" ");
// update phase
$scope.updatePhase = function (assignment, phase_id) {
assignment.phase = phase_id;
Assignment.save(assignment);
};
// open new/edit dialog
$scope.openDialog = function (assignment) {
ngDialog.open(AssignmentForm.getDialog(assignment));
};
// cancel QuickEdit mode
$scope.cancelQuickEdit = function (assignment) {
// revert all changes by restore (refresh) original assignment object from server
Assignment.refresh(assignment);
assignment.quickEdit = false;
};
// save changed assignment
$scope.save = function (assignment) {
Assignment.save(assignment).then(
function(success) {
assignment.quickEdit = false;
$scope.alert.show = false;
},
function(error) {
var message = '';
for (var e in error.data) {
message += e + ': ' + error.data[e] + ' ';
}
$scope.alert = { type: 'danger', msg: message, show: true };
});
};
// *** delete mode functions ***
$scope.isDeleteMode = false;
// *** select mode functions ***
$scope.isSelectMode = false;
// check all checkboxes
$scope.checkAll = function () {
angular.forEach($scope.assignments, function (assignment) {
assignment.selected = $scope.selectedAll;
});
};
// uncheck all checkboxes if isDeleteMode is closed
// uncheck all checkboxes if isSelectMode is closed
$scope.uncheckAll = function () {
if (!$scope.isDeleteMode) {
if (!$scope.isSelectMode) {
$scope.selectedAll = false;
angular.forEach($scope.assignments, function (assignment) {
assignment.selected = false;
@ -337,7 +344,7 @@ angular.module('OpenSlidesApp.assignments.site', [
if (assignment.selected)
Assignment.destroy(assignment.id);
});
$scope.isDeleteMode = false;
$scope.isSelectMode = false;
$scope.uncheckAll();
};
// delete single assignment

View File

@ -9,10 +9,6 @@
<i class="fa fa-tags fa-lg"></i>
<translate>Tags</translate>
</a>
<a href="" ng-click="makePDF_assignmentList()" class="btn btn-default btn-sm">
<i class="fa fa-file-pdf-o fa-lg"></i>
<translate>PDF</translate>
</a>
</div>
<h1 translate>Elections</h1>
</div>
@ -20,31 +16,35 @@
<div class="details">
<div class="row form-group">
<div class="col-sm-6">
<form class="form-inline">
<!-- delete mode -->
<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>
</button>
</form>
</div>
<div class="col-sm-6">
<div class="form-inline text-right">
<div class="form-group">
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-search"></i></div>
<input type="text" ng-model="filter.search" class="form-control"
placeholder="{{ 'Search' | translate}}">
</div>
</div>
<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>
<div class="col-sm-12">
<!-- delete mode -->
<button os-perms="assignments.can_manage" class="btn"
ng-class="$parent.isSelectMode ? 'btn-primary' : 'btn-default'"
ng-click="$parent.isSelectMode = !$parent.isSelectMode; uncheckAll()">
<i class="fa fa-check-square-o"></i>
<translate>Select ...</translate>
</button>
<!-- export dropdown -->
<div class="dropdown pull-right" uib-dropdown>
<button type="button" class="btn btn-default" id="dropdownExport" uib-dropdown-toggle>
<i class="fa fa-upload"></i>
<span ng-if="assignmentsFiltered.length == assignments.length" translate>
Export all
</span>
<span ng-if="assignmentsFiltered.length != assignments.length" translate>
Export filtered
</span>
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownExport">
<!-- PDF export -->
<li>
<a href="" ng-click="makePDF_assignmentList()">
<i class="fa fa-file-pdf-o fa-lg"></i>
<translate>PDF</translate>
</a>
</li>
</ul>
</div>
</div>
</div>
@ -58,10 +58,10 @@
</select>
</div>
</div>
<div uib-collapse="!isDeleteMode" class="row spacer">
<div uib-collapse="!isSelectMode" class="row spacer">
<div class="col-sm-12 text-left">
<!-- delete button -->
<a ng-show="isDeleteMode" os-perms="assignments.can_manage"
<a ng-show="isSelectMode" os-perms="assignments.can_manage"
ng-bootbox-confirm="{{ 'Are you sure you want to delete all selected elections?' | translate }}"
ng-bootbox-confirm-action="deleteMultiple()"
class="btn btn-primary">
@ -76,134 +76,247 @@
{{ assignments.length }} {{ "elections" | translate }}<span ng-if="(assignments|filter:{selected:true}).length > 0">,
{{(assignments|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
</div>
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<!-- projector column -->
<th ng-show="!isDeleteMode" os-perms="core.can_manage_projector" class="minimum">
<!-- delete selection column -->
<th ng-show="isDeleteMode" os-perms="assignments.can_manage" class="minimum deleteColumn">
<input type="checkbox" ng-model="$parent.selectedAll" ng-change="checkAll()">
<!-- agenda item column -->
<th ng-click="toggleSort('agenda_item.item_number')" class="sortable">
<translate translate-comment="short form of agenda item">Item</translate>
<i class="pull-right fa" ng-show="sortColumn === 'agenda_item.item_number' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
<!-- title column -->
<th ng-click="toggleSort('title')" class="sortable">
<translate>Title</translate>
<i class="pull-right fa" ng-show="sortColumn === 'title' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
<!-- candicates / posts column -->
<th ng-click="toggleSort('open_posts')" class="sortable optional">
<translate>Candidates</translate> &middot; <translate>Posts</translate>
<i class="pull-right fa" ng-show="sortColumn === 'open_posts' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
<!-- phase column -->
<th ng-click="toggleSort('phase')" class="sortable optional">
<translate>Phase</translate>
<i class="pull-right fa" ng-show="sortColumn === 'phase' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
<tbody>
<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().length, 'selected': assignment.selected }">
<!-- projector -->
<td ng-show="!isDeleteMode" os-perms="core.can_manage_projector">
<projector-button model="assignment" default-projector-id="defaultProjectorId">
</projector-button>
<!-- delete selection -->
<td ng-show="isDeleteMode" os-perms="assignments.can_manage" class="deleteColumn">
<input type="checkbox" ng-model="assignment.selected">
<!-- agenda item number -->
<td ng-if="!assignment.quickEdit">
{{ assignment.agenda_item.item_number }}
<!-- title -->
<td ng-if="!assignment.quickEdit" ng-mouseover="assignment.hover=true" ng-mouseleave="assignment.hover=false">
<strong><a ui-sref="assignments.assignment.detail({id: assignment.id})">{{ assignment.title }}</a></strong>
<span ng-repeat="tag in assignment.tags" class="label label-default">
{{ tag.name }}
<div class="os-table container-fluid">
<div class="row header-row">
<div class="col-xs-1 centered" ng-show="isSelectMode">
<i class="fa text-danger pointer" ng-class=" selectedAll ? 'fa-check-square-o' : 'fa-square-o'"
ng-click="checkAll()"></i>
</div>
<div class="col-xs-11 main-header">
<span class="form-inline text-right pull-right">
<!-- clear all filters -->
<span class="sort-spacer pointer" ng-click="filter.reset()"
ng-if="filter.areFiltersSet()" ng-disabled="isSelectMode"
ng-class="{'disabled': isSelectMode}">
<i class="fa fa-times-circle"></i>
<translate>Filter</translate>
</span>
<div os-perms="assignments.can_manage" class="hoverActions" ng-class="{'hiddenDiv': !assignment.hover}">
<a href="" ng-click="openDialog(assignment)" translate>Edit</a> |
<a href="" ng-click="assignment.quickEdit=true" translate>QuickEdit</a> |
<a href="" class="text-danger"
<!-- Tag filter -->
<span uib-dropdown ng-if="tags.length > 0">
<span class="pointer" id="dropdownTag" uib-dropdown-toggle
ng-class="{'bold': filter.multiselectFilters.tag.length > 0, 'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>Tag</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right"
aria-labelledby="dropdownTag">
<li ng-repeat="tag in tags">
<a href ng-click="filter.operateMultiselectFilter('tag', tag.id, isSelectMode)">
<i class="fa fa-check" ng-if="filter.MultiselectFilters.tag.indexOf(tag.id) > -1"></i>
{{ tag.name }}
</a>
</li>
<li class="divider"></li>
<li>
<a href ng-click="filter.operateMultiselectFilter('tag', -1, isSelectMode)">
<i class="fa fa-check" ng-if="filter.multiselectFilters.tag.indexOf(-1) > -1"></i>
<translate>No tag set</translate>
</a>
</li>
</ul>
</span>
<!-- Phase filter -->
<span uib-dropdown>
<span class="pointer" id="dropdownPhase" uib-dropdown-toggle
ng-class="{'bold': filter.multiselectFilters.phase.length > 0, 'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>Phase</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right"
aria-labelledby="dropdownPhase">
<li ng-repeat="phase in phases">
<a href ng-click="filter.operateMultiselectFilter('phase', phase.value, isSelectMode)">
<i class="fa fa-check" ng-if="filter.multiselectFilters.phase.indexOf(phase.value) > -1"></i>
{{ phase.display_name | translate }}
</a>
</li>
</ul>
</span>
<!-- dropdown sort -->
<span uib-dropdown>
<span class="pointer" id="dropdownSort" uib-dropdown-toggle
ng-class="{'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>Sort</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownSort">
<!-- all sortOptions -->
<li ng-repeat="option in sortOptions">
<a ng-click="sort.toggle(option.name)">
{{ option.display_name | translate }}
<span class="spacer-right pull-right"></span>
<i class="pull-right fa"
ng-style="{'visibility': sort.column === option.name ? 'visible' : 'hidden'}"
ng-class="sort.reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
</a>
</li>
</ul>
</span>
<!-- search field -->
<span class="form-group">
<span class="input-group">
<span class="input-group-addon"><i class="fa fa-search"></i></span>
<input type="text" ng-model="filter.filterString" class="form-control"
placeholder="{{ 'Search' | translate}}">
</span>
</span>
</span>
<!-- show all selected multiselectoptions -->
<span ng-repeat="tag in tags" class="pointer spacer-left-lg"
ng-if="filter.multiselectFilters.tag.indexOf(tag.id) > -1"
ng-click="filter.operateMultiselectFilter('tag', tag.id, isSelectMode)"
ng-class="{'disabled': isSelectMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ tag.name }}
</span>
</span>
<span ng-if="filter.multiselectFilters.tag.indexOf(-1) > -1" class="pointer spacer-left-lg"
ng-click="filter.operateMultiselectFilter('tag', -1, isSelectMode)"
ng-class="{'disabled': isSelectMode}">
<i class="fa fa-times-circle"></i>
<translate>No tag set</translate>
</span>
<span ng-repeat="phase in phases" class="pointer spacer-left-lg"
ng-if="filter.multiselectFilters.phase.indexOf(phase.value) > -1"
ng-click="filter.operateMultiselectFilter('phase', phase.value, isSelectMode)"
ng-class="{'disabled': isSelectMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ phase.display_name | translate }}
</span>
</span>
<span>
</span>
</div>
</div>
<!-- main table -->
<div class="row data-row" ng-mouseover="assignment.hover=true"
ng-mouseleave="assignment.hover=false"
ng-class="{'projected': assignment.isProjected().length}"
ng-repeat="assignment in assignmentsFiltered = (assignments
| osFilter: filter.filterString : filter.getObjectQueryString
| MultiselectFilter: filter.multiselectFilters.tag : getItemId.tag
| MultiselectFilter: filter.multiselectFilters.phase : getItemId.phase
| orderBy: sort.column : sort.reverse)">
<!-- select column -->
<div ng-show="isSelectMode" os-perms="assignments.can_manage" class="col-xs-1 centered">
<i class="fa text-danger pointer" ng-click="assignment.selected=!assignment.selected"
ng-class="assignment.selected ? 'fa-check-square-o' : 'fa-square-o'"></i>
</div>
<!-- projector column -->
<div class="col-xs-1 centered projector" os-perms="core.can_manage_projector">
<projector-button model="assignment", default-projector-id="defaultProjectorId">
</projector-button>
</div>
<!-- main contant column -->
<div class="col-xs-6 content">
<div class="title-col">
<!-- title and phase -->
<div>
<strong>
<a ui-sref="assignments.assignment.detail({id: assignment.id})">{{ assignment.title }}</a>
</strong>
<span style="padding: 5px;" ng-mouseover="assignment.phaseHover=true" ng-mouseleave="assignment.phaseHover=false">
<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>
<span os-perms="assignments.can_manage" ng-class="{'hiddenDiv': !assignment.phaseHover}" uib-dropdown>
<i class="fa fa-cog pointer" uib-dropdown-toggle id="phaseDropdown{{ assignment.id }}"></i>
<ul uib-dropdown-menu aria-labelledby="phaseDropdown{{ assignment.id }}"
class="dropdown-menu">
<li ng-repeat="phase in phases">
<a href>
<i class="fa fa-check" ng-if="assignment.phase == phase.value"></i>
<span class="pointer" ng-click="updatePhase(assignment, phase.value)">{{ phase.display_name | translate }}</a>
</a>
</li>
</ul>
</span>
</span>
</div>
<!-- hover menu -->
<div os-perms="assignments.can_manage" ng-class="{'hiddenDiv': !assignment.hover}">
<small>
<a href="" ng-click="openDialog(assignment)" translate>Edit</a> &middot;
<a href="" class="text-danger"
ng-bootbox-confirm="{{ 'Are you sure you want to delete this entry?' | translate }}<br>
<b>{{ assignment.title }}</b>"
ng-bootbox-confirm-action="delete(assignment)" translate>Delete</a>
</small>
</div>
</div>
</div>
<!-- candidates / posts -->
<td ng-if="!assignment.quickEdit" class="optional">
<span class="badge">{{ assignment.assignment_related_users.length }}</span>
/
<span class="badge">{{ assignment.open_posts }}</span>
<!-- additional content column -->
<div class="col-xs-4 content" ng-style="{'width': isSelectMode ? 'calc(50% - 120px)' : 'calc(50% - 70px)'}">
<div style="width: 60%;" class="optional">
<small>
<!-- Tag dropdown for manage user -->
<div os-perms="assignments.can_manage" ng-show="tags.length > 0"
ng-mouseover="assignment.tagHover=true"
ng-mouseleave="assignment.tagHover=false">
<span uib-dropdown>
<span id="dropdownTags{{ assignment.id }}" class="pointer"
uib-dropdown-toggle uib-tooltip="{{ 'Add a tag' | translate }}"
tooltip-class="nobr">
<span ng-if="assignment.tags.length == 0" ng-show="assignment.hover">
<i class="fa fa-tags"></i>
<i class="fa fa-plus"></i>
</span>
<span ng-if="assignment.tags.length > 0">
<i class="fa fa-tags"></i>
<span ng-repeat="tag in assignment.tags">
{{ tag.name }}<span ng-if="!$last">,</span>
</span>
<i class="fa fa-cog fa-lg spacer-left" ng-show="assignment.tagHover"></i>
</span>
</span>
<ul class="dropdown-menu" aria-labelledby="dropdownTags{{ assignment.id }}">
<li ng-repeat="tag in tags">
<a href ng-click="toggleTag(assignment, tag)">
<i class="fa fa-check" ng-if="hasTag(assignment, tag)"></i>
{{ tag.name }}
</a>
</li>
</ul>
</span>
</div>
<!-- Tag string for normal user -->
<div os-perms="!assignments.can_manage" ng-show="assignment.tags.length > 0">
<i class="fa fa-tags spacer-right"></i>
<span ng-repeat="tag in assignment.tags">
{{ tag.name }}<span ng-if="!$last">,</span>
</span>
</div>
</small>
</div>
<!-- phase -->
<td ng-if="!assignment.quickEdit" class="optional">
<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 }}
<div style="width: 20%;" class="pull-right nobr optional centered">
<span class="badge"
uib-tooltip="{{ assignment.assignment_related_users.length }} {{ 'Candidates' | translate }}
{{ (assignment.open_posts - assignment.assignment_related_users.length) > 0 ? '(' + (assignment.open_posts - assignment.assignment_related_users.length) + ' ' + ('needed' | translate) + ')' : '' }}"
tooltip-class="nobr"
ng-class="{'badge-info': assignment.assignment_related_users.length < assignment.open_posts}">
{{ assignment.assignment_related_users.length }}
</span>
</div>
<div style="width: 20%;" class="pull-right nobr">
<div class="centered">{{ assignment.agenda_item.getItemNumberWithAncestors() }}</div>
</div>
</div>
</div> <!-- main table -->
</div> <!-- end container -->
<!-- quickEdit columns -->
<td ng-if="assignment.quickEdit" colspan="4">
<h4>{{ assignment.title }} <span class="text-muted">&ndash; Quick Edit</span></h4>
<div uib-alert ng-show="alert.show" ng-class="'alert-' + (alert.type || 'warning')" ng-click="alert={}" close="alert={}">
{{ alert.msg }}
</div>
<div class="row">
<div class="col-xs-6">
<label for="inputTitle" translate>Title</label>
<input type="text" ng-model="assignment.title" class="form-control input-sm" id="inputTitle">
</div>
<div class="col-xs-6">
<label for="inputPosts" translate>Number of members to be elected</label>
<input type="number" ng-model="assignment.open_posts" class="form-control input-sm" id="inputPosts">
</div>
</div>
<div class="row">
<div class="col-xs-6">
<label for="selectPhase" translate>Phase</label>
<select ng-options="phase.value as phase.display_name | translate for phase in phases"
ng-model="assignment.phase" class="form-control" id="selectPhase">
</select>
</div>
<div class="col-xs-6">
<label for="selectTags" translate>Tags</label>
<select multiple chosen
ng-model="assignment.tags_id"
ng-options="tag.id as tag.name for tag in tags"
search-contains="true"
id="selectTag"
class="form-control">
</select>
</div>
</div>
<div class="spacer">
<button ng-click="cancelQuickEdit(assignment)" class="btn btn-default pull-left" translate>
Cancel
</button> &nbsp;
<button ng-click="save(assignment)" class="btn btn-primary" translate>
Update
</button>
<a href="" ng-click="openDialog(assignment)"
class="pull-right" translate>Edit election ...</a>
</div>
</table>
</div>

View File

@ -864,23 +864,6 @@ img {
}
/** Motion **/
#motion-os-table .identifier-col {
width: 50px;
min-height: 1px;
}
#motion-os-table .identifier-col > div {
text-align: center;
}
#motion-os-table .title-col {
width: calc(100% - 50px);
}
#motion-os-table .title-col, .os-table small {
color: #555;
}
.motion-toolbar, .speakers-toolbar {
background-color: #f5f5f5;
border-bottom: 1px solid #ddd;
@ -900,6 +883,9 @@ img {
}
/** OS-Table **/
.os-table small {
color: #555;
}
.os-table .row {
border: 1px solid #ddd;
border-top: 0px;
@ -913,6 +899,11 @@ img {
padding: 10px 0px 10px 0px;
}
.os-table .id-col {
width: 50px;
min-height: 1px;
}
.os-table .header-row {
border-top: 1px solid #ddd;
background-color: #f5f5f5;
@ -960,17 +951,13 @@ img {
padding-right: 10px;
}
.os-table .dropdown {
display: inline-block;
}
.os-table .header-row .dropdown > span, .os-table .sort-spacer {
padding: 5px 10px 5px 10px;
}
.os-table .title {
font-size: 115%;
margin-right: 10px;
margin-right: 5px;
padding: 0;
background-color: transparent;
}
@ -1079,16 +1066,6 @@ img {
margin-left: 0px !important;
}
.dropdown-entries {
white-space: nowrap;
}
.dropdown-entries > li {
padding: 5px 10px 5px 10px;
width: 100%;
cursor: pointer;
}
.slimDropDown {
padding-left: 4px !important;
padding-right: 4px !important;
@ -1511,6 +1488,8 @@ tr.selected td {
#chatbox { width: 100%; top: 40px; }
.badge { font-size: 10px; }
/* show replacement elements, if any */
.optional-show { display: block !important; }

View File

@ -796,19 +796,6 @@ angular.module('OpenSlidesApp.core', [
}
])
.filter('osFilter', [
function () {
return function (array, string, getFilterString) {
if (!string) {
return array;
}
return Array.prototype.filter.call(array, function (item) {
return getFilterString(item).toLowerCase().indexOf(string.toLowerCase()) > -1;
});
};
}
])
// mark HTML as "trusted"
.filter('trusted', [
'$sce',
@ -838,7 +825,6 @@ angular.module('OpenSlidesApp.core', [
};
})
// Make sure that the DS factories are loaded by making them a dependency
.run([
'ChatMessage',

View File

@ -342,61 +342,107 @@ angular.module('OpenSlidesApp.core.site', [
}
])
.factory('Multiselect', [
/* This factory handles the filtering of the OS-data-tables. It contains
* all logic needed for the table header filtering. Things to configure:
* - multiselectFilters: A dict associating the filter name to a list (empty per default). E.g.
* { tag: [],
* category: [], }
* - booleanFilters: A dict containing a dict for every filter. The value property is a must.
* For displaying properties like displayName, choiceYes and choiceNo could be usefull. E.g.
* { isPresent: {
* value: undefined,
* displayName: gettext('Is present'), } }
* - propertyList, propertyFunctionList, propertyDict: See function getObjectQueryString
*/
.factory('osTableFilter', [
function () {
var instance = function () {
var areFiltersSet = function () {
var areFiltersSet = false;
_.forEach(self.filters, function (filterList, filter) {
if (filterList.length > 0) {
areFiltersSet = true;
var createInstance = function () {
var self = {
multiselectFilters: {},
booleanFilters: {},
filterString: '',
};
self.areFiltersSet = function () {
var areFiltersSet = _.find(self.multiselectFilters, function (filterList) {
return filterList.length > 0;
});
areFiltersSet = areFiltersSet || _.find(self.booleanFilters, function (filterDict) {
return filterDict.value !== undefined;
});
areFiltersSet = areFiltersSet || (self.filterString !== '');
return areFiltersSet !== false;
};
self.reset = function () {
_.forEach(self.multiselectFilters, function (filterList, filter) {
self.multiselectFilters[filter] = [];
});
_.forEach(self.booleanFilters, function (filterDict, filter) {
self.booleanFilters[filter].value = undefined;
});
self.filterString = '';
};
self.operateMultiselectFilter = function (filter, id, danger) {
if (!danger) {
if (_.indexOf(self.multiselectFilters[filter], id) > -1) {
// remove id
self.multiselectFilters[filter].splice(_.indexOf(self.multiselectFilters[filter], id), 1);
} else {
// add id
self.multiselectFilters[filter].push(id);
}
});
return areFiltersSet;
};
var resetFilters = function () {
_.forEach(self.filters, function (filterList, filter) {
self.filters[filter] = [];
});
};
var getFilterString = function (obj) {
var newList = [];
_.forEach(self.propertyList, function (property) {
newList.push(obj[property]);
});
_.forEach(self.propertyFunctionList, function (fn) {
newList.push(fn(obj));
});
_.forEach(self.propertyDict, function (idFunction, property) {
newList.push(_.map(obj[property], idFunction).join(' '));
});
return newList.join(' ');
};
var operate = function (filter, id) {
if (_.indexOf(self.filters[filter], id) > -1) {
// remove id
self.filters[filter] = _.filter(self.filters[filter], function (_id) {
return _id != id;
});
} else {
// add id
self.filters[filter].push(id);
}
};
var self = {
filters: {},
areFiltersSet: areFiltersSet,
resetFilters: resetFilters,
operate: operate,
getFilterString: getFilterString,
/* Three things are could be given to create the query string:
* - propertyList: Just a list of object's properties like ['title', 'name']
* - propertyFunktionList: A list of functions returning a property (e.g. [function(motion) {return motion.getTitle();}] for retrieving the motions title)
* - propertyDict: A dict association properties that are lists to functions on how to handle them.
* E.g.: {'tags': function (tag) {return tag.name;}, }
* The list of tags will be mapped with this function to a list of strings (tag names).
*/
self.getObjectQueryString = function (obj) {
var stringList = [];
_.forEach(self.propertyList, function (property) {
stringList.push(obj[property]);
});
_.forEach(self.propertyFunctionList, function (fn) {
stringList.push(fn(obj));
});
_.forEach(self.propertyDict, function (idFunction, property) {
stringList.push(_.map(obj[property], idFunction).join(' '));
});
return stringList.join(' ');
};
resetFilters(); //Initiate filters
return self;
};
return {
instance: instance
createInstance: createInstance
};
}
])
/* This factory takes care of the sorting of OS-data-tables. Things to configure:
* - column: the default column which is the list sorted by (e.g.
* instance.column='title')
*/
.factory('osTableSort', [
function () {
var createInstance = function () {
var self = {
column: '',
reverse: false,
};
self.toggle = function (column) {
if (self.column === column) {
self.reverse = !self.reverse;
}
self.column = column;
};
return self;
};
return {
createInstance: createInstance
};
}
])
@ -407,6 +453,9 @@ angular.module('OpenSlidesApp.core.site', [
* Then, all items in the array are passed, if the item_id (get with id_function) matches
* one of the ids in filterArray. id_function could also return a list of ids. Example:
* Item 1 has two tags with ids [1, 4]. filterArray == [3, 4] --> match
*
* If -1 is in the array items without an id will not be filtered. This is for implementing
* a filter option like: "All items without a category"
*/
.filter('MultiselectFilter', [
function () {
@ -414,12 +463,13 @@ angular.module('OpenSlidesApp.core.site', [
if (filterArray.length === 0) {
return array;
}
var itemsWithoutProperty = _.indexOf(filterArray, -1) > -1;
return Array.prototype.filter.call(array, function (item) {
var id = idFunction(item);
if (!id) {
return false;
} else if (typeof id === 'number') {
if (typeof id === 'number') {
id = [id];
} else if (id === null || !id.length) {
return itemsWithoutProperty;
}
return _.intersection(id, filterArray).length > 0;
});
@ -427,6 +477,19 @@ angular.module('OpenSlidesApp.core.site', [
}
])
.filter('osFilter', [
function () {
return function (array, string, getFilterString) {
if (!string) {
return array;
}
return Array.prototype.filter.call(array, function (item) {
return getFilterString(item).toLowerCase().indexOf(string.toLowerCase()) > -1;
});
};
}
])
// Load the django url patterns
.run([
'runtimeStates',

View File

@ -57,7 +57,10 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
'User',
'Projector',
'ProjectionDefault',
function($scope, $http, ngDialog, Mediafile, MediafileForm, User, Projector, ProjectionDefault) {
'osTableFilter',
'osTableSort',
'gettext',
function($scope, $http, ngDialog, Mediafile, MediafileForm, User, Projector, ProjectionDefault, osTableFilter, osTableSort, gettext) {
Mediafile.bindAll({}, $scope, 'mediafiles');
User.bindAll({}, $scope, 'users');
$scope.$watch(function() {
@ -95,44 +98,61 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
updatePresentedMediafiles();
// 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(" ");
// Filtering
$scope.filter = osTableFilter.createInstance();
$scope.filter.booleanFilters = {
isPrivate: {
value: undefined,
displayName: gettext('Private'),
choiceYes: gettext('Is private'),
choiceNo: gettext('Is not private'),
needExtraPermission: true,
},
isPdf: {
value: undefined,
displayName: gettext('Is PDF'),
choiceYes: gettext('Is PDF file'),
choiceNo: gettext('Is no PDF file'),
},
};
$scope.filter.propertyList = ['title_or_filename'];
$scope.filter.propertyFunctionList = [
function (mediafile) {return mediafile.uploader.get_short_name();},
function (mediafile) {return mediafile.mediafile.type;},
function (mediafile) {return mediafile.mediafile.name;},
];
// Sorting
$scope.sort = osTableSort.createInstance();
$scope.sort.column = 'title_or_filename';
$scope.sortOptions = [
{name: 'title_or_filename',
display_name: 'Title'},
{name: 'timestamp',
display_name: 'UploadTime'},
{name: 'uploader.get_short_name()',
display_name: 'Uploader'},
{name: 'mediafile.type',
display_name: 'Type'},
{name: 'filesize',
display_name: 'Filesize'},
];
// open new/edit dialog
$scope.openDialog = function (mediafile) {
ngDialog.open(MediafileForm.getDialog(mediafile));
};
// *** delete mode functions ***
$scope.isDeleteMode = false;
// *** select mode functions ***
$scope.isSelectMode = false;
// check all checkboxes
$scope.checkAll = function () {
angular.forEach($scope.mediafiles, function (mediafile) {
mediafile.selected = $scope.selectedAll;
});
};
// uncheck all checkboxes if isDeleteMode is closed
// uncheck all checkboxes if SelectMode is closed
$scope.uncheckAll = function () {
if (!$scope.isDeleteMode) {
if (!$scope.isSelectMode) {
$scope.selectedAll = false;
angular.forEach($scope.mediafiles, function (mediafile) {
mediafile.selected = false;
@ -145,7 +165,7 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
if (mediafile.selected)
Mediafile.destroy(mediafile.id);
});
$scope.isDeleteMode = false;
$scope.isSelectMode = false;
$scope.uncheckAll();
};
// delete single mediafile

View File

@ -115,44 +115,21 @@
<div class="row form-group">
<div class="col-sm-6">
<form class="form-inline">
<!-- delete mode -->
<!-- select mode -->
<button os-perms="mediafiles.can_manage" class="btn"
ng-class="$parent.isDeleteMode ? 'btn-primary' : 'btn-default'"
ng-click="$parent.isDeleteMode = !$parent.isDeleteMode; uncheckAll()">
ng-class="$parent.isSelectMode ? 'btn-primary' : 'btn-default'"
ng-click="$parent.isSelectMode = !$parent.isSelectMode; uncheckAll()">
<i class="fa fa-check-square-o"></i>
<translate>Select ...</translate>
</button>
</form>
</div>
<div class="col-sm-6">
<div class="form-inline text-right">
<div class="form-group">
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-search"></i></div>
<input type="text" ng-model="filter.search" class="form-control"
placeholder="{{ 'Search' | translate}}">
</div>
</div>
<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>
</div>
</div>
<div class="col-sm-6"></div>
</div>
<div uib-collapse="!isFilterOpen" class="row">
<div class="col-sm-6 text-right"></div>
<div class="col-sm-6 text-right">
<!-- pdf filter -->
<input type="checkbox" ng-model="filter.showPDFs" ng-true-value="'application/pdf'" ng-false-value=''>
<translate> Show PDF files only</translate>
</div>
</div>
<div uib-collapse="!isDeleteMode" class="row spacer">
<div uib-collapse="!isSelectMode" class="row spacer">
<div class="col-sm-12 text-left">
<!-- delete button -->
<a ng-show="isDeleteMode"
<a ng-show="isSelectMode"
os-perms="mediafiles.can_manage" ng-click="deleteMultiple()"
class="btn btn-primary">
<i class="fa fa-trash fa-lg"></i>
@ -166,87 +143,180 @@
{{ mediafiles.length }} {{ "files" | translate }}<span ng-if="(mediafiles|filter:{selected:true}).length > 0">,
{{(mediafiles|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
</div>
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<!-- projector column -->
<th ng-show="!isDeleteMode" os-perms="core.can_manage_projector" class="minimum">
<!-- delete selection column -->
<th ng-show="isDeleteMode" os-perms="mediafiles.can_manage" class="minimum deleteColumn">
<input type="checkbox" ng-model="$parent.selectedAll" ng-change="checkAll()">
<!-- title -->
<th ng-click="toggleSort('title_or_filename')" class="sortable">
<translate>Title</translate>
<i class="pull-right fa" ng-show="sortColumn === 'title_or_filename' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
<!-- upload time -->
<th ng-click="toggleSort('timestamp')" class="sortable optional">
<translate>Upload time</translate>
<i class="pull-right fa" ng-show="sortColumn === 'timestamp' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
<!-- uploaded by -->
<th ng-click="toggleSort('uploader')" class="sortable">
<translate>Uploaded by</translate>
<i class="pull-right fa" ng-show="sortColumn === 'uploader' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
</tr>
<tbody>
<tr ng-repeat="mediafile in mediafilesFiltered = (mediafiles | privateFilter | osFilter: filter.search : getFilterString |
filter: {filetype: filter.showPDFs} | orderBy: sortColumn:reverse )"
class="animate-item"
ng-class="{ 'activeline': mediafile.isProjected().length, 'selected': mediafile.selected }">
<!-- projector column -->
<td ng-show="!isDeleteMode"
os-perms="core.can_manage_projector">
<div class="btn-group" style="min-width:{{ projectors.length > 1 ? '54' : '34' }}px;" uib-dropdown
ng-if="mediafile.is_presentable"
uib-tooltip="{{ 'Projector' | translate }} {{ mediafile.isProjected()[0] || '' }}"
tooltip-enable="mediafile.isProjected().length">
<button type="button" class="btn btn-default btn-sm"
ng-click="showMediafile(defaultProjectorId, mediafile)"
ng-class="{ 'btn-primary': inArray(mediafile.isProjected(), defaultProjectorId) }">
<i class="fa fa-video-camera"></i>
</button>
<button type="button" class="btn btn-default btn-sm slimDropDown"
ng-class="{ 'btn-primary': (mediafile.isProjected().length && !inArray(mediafile.isProjected(), defaultProjectorId) ) }"
ng-if="projectors.length > 1"
uib-dropdown-toggle>
<div class="os-table container-fluid">
<div class="row header-row">
<div class="col-xs-1 centered" ng-show="isSelectMode" os-perms="mediafiles.can_manage">
<i class="fa text-danger pointer" ng-class="selectedAll ? 'fa-check-square-o' : 'fa-square-o'"
ng-click="checkAll()"></i>
</div>
<div class="col-xs-11 main-header">
<span class="form-inline text-right pull-right">
<!-- reset Filters -->
<span class="sort-spacer pointer" ng-click="filter.reset()"
ng-if="filter.areFiltersSet()" ng-disabled="isSelectMode"
ng-class="{'disabled': isSelectMode}">
<i class="fa fa-times-circle"></i>
<translate>Filter</translate>
</span>
<!-- boolean Filters -->
<span ng-repeat="(name, booleanFilter) in filter.booleanFilters"
ng-if="!booleanFilter.needExtraPermission || operator.hasPerms('mediafiles.can_see_private')" uib-dropdown>
<span class="pointer" id="dropdown{{ name }}" uib-dropdown-toggle
ng-class="{'bold': booleanFilter.value !== undefined, 'disabled': isSelectMode}"
ng-disabled="isSelectMode">
{{ booleanFilter.displayName }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" ng-if="projectors.length > 1">
<li role="menuitem" ng-repeat="projector in projectors">
<a href="" ng-click="showMediafile(projector.id, mediafile)"
ng-class="{ 'projected': inArray(mediafile.isProjected(), projector.id) }">
<i class="fa fa-video-camera" ng-show="inArray(mediafile.isProjected(), projector.id) "></i>
{{ projector.name }}
<span ng-if="defaultProjectorId == projector.id">(<translate>Default</translate>)</span>
</span>
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdown{{ name }}">
<li>
<a href ng-click="booleanFilter.value = (booleanFilter.value ? undefined : true)">
<i class="fa" ng-class="{'fa-check': booleanFilter.value === true}"></i>
{{ booleanFilter.choiceYes }}
</a>
</li>
<li>
<a href ng-click="booleanFilter.value = (booleanFilter.value === false) ? undefined : false">
<i class="fa" ng-class="{'fa-check': booleanFilter.value === false}"></i>
{{ booleanFilter.choiceNo }}
</a>
</li>
</ul>
</span>
<!-- dropdown sort -->
<span uib-dropdown>
<span class="pointer" id="dropdownSort" uib-dropdown-toggle
ng-class="{'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>Sort</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownSort">
<li ng-repeat="option in sortOptions">
<a href ng-click="sort.toggle(option.name)">
{{ option.display_name | translate }}
<span class="spacer-right pull-right"></span>
<i class="pull-right fa"
ng-style="{'visibility': sort.column === option.name ? 'visible' : 'hidden'}"
ng-class="sort.reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
</a>
</li>
</ul>
</span>
<!-- search field -->
<span class="form-group">
<span class="input-group">
<span class="input-group-addon"><i class="fa fa-search"></i></span>
<input type="text" ng-model="filter.filterString" class="form-control"
placeholder="{{ 'Search' | translate}}" ng-disabled="isSelectMode">
</span>
</span>
</span>
<!-- show all selected multiselectoptions -->
<span>
<!-- for all boolean Filters -->
<span ng-repeat="(name, booleanFilter) in filter.booleanFilters"
ng-hide="booleanFilter.value === undefined"
class="pointer spacer-left-lg"
ng-click="booleanFilter.value = undefined;"
ng-class="{'disabled': isSelectMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ booleanFilter.value ? booleanFilter.choiceYes : booleanFilter.choiceNo | translate }}
</span>
</span>
</span>
</div>
</div>
<!-- main table -->
<div class="row data-row" ng-mouseover="mediafile.hover=true"
ng-mouseleave="mediafile.hover=false"
ng-class="{'projected': mediafile.isProjected().length}"
ng-repeat="mediafile in mediafilesFiltered = (mediafiles
| privateFilter
| osFilter: filter.filterString : filter.getObjectQueryString
| filter: {filetype: (filter.booleanFilters.isPdf.value ? 'application/pdf' : (filter.booleanFilters.isPdf.value === false ? '!application/pdf' : ''))}
| filter: {private: filter.booleanFilters.isPrivate.value}
| orderBy: sort.column : sort.reverse )">
<!-- select column -->
<div ng-show="isSelectMode" os-perms="mediafiles.can_manage" class="col-xs-1 centered">
<i class="fa text-danger pointer" ng-click="mediafile.selected=!mediafile.selected"
ng-class="mediafile.selected ? 'fa-check-square-o' : 'fa-square-o'"></i>
</div>
<!-- projector column -->
<div class="col-xs-1 centered projector" os-perms="core.can_manage_projector">
<div class="btn-group" style="min-width:{{ projectors.length > 1 ? '54' : '34' }}px;" uib-dropdown
ng-if="mediafile.is_presentable"
uib-tooltip="{{ 'Projector' | translate }} {{ mediafile.isProjected()[0] || '' }}"
tooltip-enable="mediafile.isProjected().length">
<button type="button" class="btn btn-default btn-sm"
ng-click="showMediafile(defaultProjectorId, mediafile)"
ng-class="{ 'btn-primary': inArray(mediafile.isProjected(), defaultProjectorId) }">
<i class="fa fa-video-camera"></i>
</button>
<button type="button" class="btn btn-default btn-sm slimDropDown"
ng-class="{ 'btn-primary': (mediafile.isProjected().length && !inArray(mediafile.isProjected(), defaultProjectorId) ) }"
ng-if="projectors.length > 1"
uib-dropdown-toggle>
<span class="caret"></span>
</button>
<ul class="dropdown-menu" ng-if="projectors.length > 1">
<li role="menuitem" ng-repeat="projector in projectors">
<a href="" ng-click="showMediafile(projector.id, mediafile)"
ng-class="{ 'projected': inArray(mediafile.isProjected(), projector.id) }">
<i class="fa fa-video-camera" ng-show="inArray(mediafile.isProjected(), projector.id) "></i>
{{ projector.name }}
<span ng-if="defaultProjectorId == projector.id">(<translate>Default</translate>)</span>
</a>
</li>
</ul>
</div>
</div>
<!-- main content column -->
<div class="col-xs-6 content">
<div class="spacer-right"> <!-- horizontal block -->
<strong>
<i ng-style="{'visibility': mediafile.private ? 'visible' : 'hidden'}" class="fa fa-lock fa-lg"
title="{{ 'Is private' | translate }}"></i>
</strong>
</div>
<div>
<div> <!-- vertical block -->
<strong>
<a ng-href="{{ mediafile.mediafileUrl }}" target="_blank">{{ mediafile.title_or_filename }}</a>
</strong>
</div>
<!-- delete selection column -->
<td ng-show="isDeleteMode" os-perms="mediafiles.can_manage" class="deleteColumn">
<input type="checkbox" ng-model="mediafile.selected">
<!-- mediafile data colums -->
<td ng-mouseover="mediafile.hover=true" ng-mouseleave="mediafile.hover=false">
<strong><a ng-href="{{ mediafile.mediafileUrl }}" target="_blank">{{ mediafile.title_or_filename }}</a></strong>
<br><small><i class="fa fa-file"></i> {{ mediafile.filetype }}
<span ng-if="mediafile.mediafile.encrypted">(<translate>Encrypted</translate>)</span>
</small>
<br><small><i class="fa fa-database"></i> {{ mediafile.filesize }}</small>
<span ng-if="mediafile.private"><br><small><i class="fa fa-lock"></i> <translate>Private</translate></small></span>
<div os-perms="mediafiles.can_manage" class="hoverActions" ng-class="{'hiddenDiv': !mediafile.hover}">
<a href="" ng-click="openDialog(mediafile)" translate>Edit</a> |
<a href="" class="text-danger"
<div><small>{{ mediafile.uploader.get_full_name() }}</small></div>
<div os-perms="mediafiles.can_manage" ng-class="{'hiddenDiv': !mediafile.hover}">
<small>
<a href="" ng-click="openDialog(mediafile)" translate>Edit</a> &middot;
<a href="" class="text-danger"
ng-bootbox-confirm="{{ 'Are you sure you want to delete this file?' | translate }}<br>
<b>{{ mediafile.title }} [{{ mediafile.mediafile.name }}]</b>"
ng-bootbox-confirm-action="delete(mediafile)" translate>Delete</a>
</small>
</div>
<td class="optional">{{ mediafile.timestamp | date:'yyyy-MM-dd HH:mm:ss' }}
<td>{{ mediafile.uploader.get_full_name() }}
</tbody>
</table>
</div>
</div>
<!-- additional content column -->
<div class="col-xs-4 content" ng-style="{'width': isSelectMode ? 'calc(50% - 120px)' : 'calc(50% - 70px)'}">
<div style="width: 60%;" class="optional">
<small>
<div>
<i class="fa fa-file"></i> {{ mediafile.filetype }}
<span ng-if="mediafile.mediafile.encrypted">(<translate>Encrypted</translate>)</span>
</div>
<div><i class="fa fa-database"></i> {{ mediafile.filesize }}</div>
<div><i class="fa fa-upload"></i> {{ mediafile.timestamp | date:'yyyy-MM-dd HH:mm:ss' }}</div>
</small>
</div>
<div style="width: 40%;" class="pull-right optional"></div>
</div>
</div><!-- end data row -->
</div><!-- end os-table -->
</div>

View File

@ -751,10 +751,11 @@ angular.module('OpenSlidesApp.motions.site', [
'Projector',
'ProjectionDefault',
'MotionCsvExport',
'Multiselect',
'osTableFilter',
'osTableSort',
function($scope, $state, $http, ngDialog, MotionForm, Motion, Category, Tag, Workflow, User, Agenda, MotionBlock,
MotionDocxExport, MotionContentProvider, MotionCatalogContentProvider, PdfMakeConverter, PdfMakeDocumentProvider,
gettextCatalog, HTMLValidizer, Projector, ProjectionDefault, MotionCsvExport, Multiselect) {
gettextCatalog, HTMLValidizer, Projector, ProjectionDefault, MotionCsvExport, osTableFilter, osTableSort) {
Motion.bindAll({}, $scope, 'motions');
Category.bindAll({}, $scope, 'categories');
MotionBlock.bindAll({}, $scope, 'motionBlocks');
@ -772,22 +773,23 @@ angular.module('OpenSlidesApp.motions.site', [
});
$scope.alert = {};
$scope.multiselect = Multiselect.instance();
$scope.multiselect.filters = {
// Filtering
$scope.filter = osTableFilter.createInstance();
$scope.filter.multiselectFilters = {
state: [],
category: [],
motionBlock: [],
tag: []
};
$scope.multiselect.propertyList = ['identifier', 'origin'];
$scope.multiselect.propertyFunctionList = [
$scope.filter.propertyList = ['identifier', 'origin'];
$scope.filter.propertyFunctionList = [
function (motion) {return motion.getTitle();},
function (motion) {return motion.getText();},
function (motion) {return motion.getReason();},
function (motion) {return motion.category ? motion.category.name : '';},
function (motion) {return motion.motionBlock ? motion.motionBlock.name : '';},
];
$scope.multiselect.PropertyDict = {
$scope.filter.propertyDict = {
'submitters' : function (submitter) {
return submitter.get_short_name();
},
@ -804,7 +806,9 @@ angular.module('OpenSlidesApp.motions.site', [
motionBlock: function (motion) {return motion.motion_block_id;},
tag: function (motion) {return motion.tags_id;}
};
// setup table sorting
// Sorting
$scope.sort = osTableSort.createInstance();
$scope.sort.column = 'identifier';
$scope.sortOptions = [
{name: 'identifier',
display_name: 'Identifier'},
@ -823,34 +827,6 @@ angular.module('OpenSlidesApp.motions.site', [
{name: 'log_messages[0].time',
display_name: 'Last modified'},
];
$scope.sortColumn = 'identifier';
$scope.filterPresent = '';
$scope.reverse = false;
// function to operate the multiselectFilter
$scope.operateMultiselectFilter = function (filter, id) {
if (!$scope.isDeleteMode) {
$scope.multiselect.operate(filter, id);
}
};
// function to sort by clicked column
$scope.toggleSort = function (column) {
if ( $scope.sortColumn === column ) {
$scope.reverse = !$scope.reverse;
}
$scope.sortColumn = column;
};
// for reset-button
$scope.resetFilters = function () {
$scope.multiselect.resetFilters();
if ($scope.filter) {
$scope.filter.search = '';
}
};
$scope.areFiltersSet = function () {
return $scope.multiselect.areFiltersSet() ||
($scope.filter ? $scope.filter.search : false);
};
// collect all states of all workflows
// TODO: regard workflows only which are used by motions
@ -877,7 +853,7 @@ angular.module('OpenSlidesApp.motions.site', [
$http.put('/rest/motions/motion/' + motion.id + '/set_state/', {});
};
$scope.has_tag = function (motion, tag) {
$scope.hasTag = function (motion, tag) {
return _.indexOf(motion.tags_id, tag.id) > -1;
};
@ -889,8 +865,8 @@ angular.module('OpenSlidesApp.motions.site', [
motion.reason = motion.getReason(-1);
Motion.save(motion);
};
$scope.toggle_tag = function (motion, tag) {
if ($scope.has_tag(motion, tag)) {
$scope.toggleTag = function (motion, tag) {
if ($scope.hasTag(motion, tag)) {
// remove
motion.tags_id = _.filter(motion.tags_id, function (tag_id){
return tag_id != tag.id;
@ -900,7 +876,7 @@ angular.module('OpenSlidesApp.motions.site', [
}
save(motion);
};
$scope.toggle_category = function (motion, category) {
$scope.toggleCategory = function (motion, category) {
if (motion.category_id == category.id) {
motion.category_id = null;
} else {
@ -908,7 +884,7 @@ angular.module('OpenSlidesApp.motions.site', [
}
save(motion);
};
$scope.toggle_motionBlock = function (motion, block) {
$scope.toggleMotionBlock = function (motion, block) {
if (motion.motion_block_id == block.id) {
motion.motion_block_id = null;
} else {
@ -962,8 +938,8 @@ angular.module('OpenSlidesApp.motions.site', [
MotionDocxExport.export($scope.motionsFiltered, $scope.categories);
};
// *** delete mode functions ***
$scope.isDeleteMode = false;
// *** select mode functions ***
$scope.isSelectMode = false;
// check all checkboxes from filtered motions
$scope.checkAll = function () {
$scope.selectedAll = !$scope.selectedAll;
@ -971,9 +947,9 @@ angular.module('OpenSlidesApp.motions.site', [
motion.selected = $scope.selectedAll;
});
};
// uncheck all checkboxes if isDeleteMode is closed
// uncheck all checkboxes if isSelectMode is closed
$scope.uncheckAll = function () {
if (!$scope.isDeleteMode) {
if (!$scope.isSelectMode) {
$scope.selectedAll = false;
angular.forEach($scope.motions, function (motion) {
motion.selected = false;
@ -986,7 +962,7 @@ angular.module('OpenSlidesApp.motions.site', [
if (motion.selected)
Motion.destroy(motion.id);
});
$scope.isDeleteMode = false;
$scope.isSelectMode = false;
$scope.uncheckAll();
};
// delete single motion

View File

@ -29,16 +29,16 @@
<div class="details">
<div class="row">
<div class="col-sm-12">
<!-- delete mode -->
<!-- select mode -->
<button os-perms="motions.can_manage" class="btn"
ng-class="$parent.isDeleteMode ? 'btn-primary' : 'btn-default'"
ng-click="$parent.isDeleteMode = !$parent.isDeleteMode; uncheckAll()">
ng-class="$parent.isSelectMode ? 'btn-primary' : 'btn-default'"
ng-click="$parent.isSelectMode = !$parent.isSelectMode; uncheckAll()">
<i class="fa fa-check-square-o"></i>
<translate>Select ...</translate>
</button>
<!-- Export dropdown -->
<div class="dropdown pull-right" uib-dropdown>
<button type=button" class="btn btn-default" id="dropdownExport" uib-dropdown-toggle>
<button type="button" class="btn btn-default" id="dropdownExport" uib-dropdown-toggle>
<i class="fa fa-upload"></i>
<span ng-if="motionsFiltered.length == motions.length" translate>
Export all
@ -76,10 +76,10 @@
</div>
</div>
<div uib-collapse="!isDeleteMode" class="row spacer">
<div uib-collapse="!isSelectMode" class="row spacer">
<div class="col-sm-12 text-left">
<!-- delete button -->
<a ng-show="isDeleteMode" os-perms="motions.can_manage"
<a ng-show="isSelectMode" os-perms="motions.can_manage"
ng-bootbox-confirm="{{ 'Are you sure you want to delete all selected motions?' | translate }}"
ng-bootbox-confirm-action="deleteMultiple()"
class="btn btn-primary">
@ -95,125 +95,144 @@
{{(motions|filter:{selected:true}).length}} {{ "selected" | translate }}</span>
</div>
<div id="motion-os-table" class="os-table container-fluid">
<div class="os-table container-fluid">
<div class="row header-row">
<div class="col-xs-1 centered" ng-show="isDeleteMode">
<div class="col-xs-1 centered" ng-show="isSelectMode">
<i class="fa text-danger pointer" ng-class=" selectedAll ? 'fa-check-square-o' : 'fa-square-o'"
ng-click="checkAll()"></i>
</div>
<div class="col-xs-11 main-header">
<span class="form-inline text-right pull-right">
<span class="sort-spacer pointer" ng-click="resetFilters()"
ng-if="areFiltersSet()" ng-disabled="isDeleteMode"
ng-class="{'disabled': isDeleteMode}">
<!-- clear all filters -->
<span class="sort-spacer pointer" ng-click="filter.reset()"
ng-if="filter.areFiltersSet()" ng-disabled="isSelectMode"
ng-class="{'disabled': isSelectMode}">
<i class="fa fa-times-circle"></i>
<translate>Filter</translate>
</span>
<!-- State filter -->
<span class="dropdown" uib-dropdown>
<span uib-dropdown>
<span class="pointer" id="dropdownState" uib-dropdown-toggle
ng-class="{'bold': multiselect.filters.state.length > 0, 'disabled': isDeleteMode}"
ng-disabled="isDeleteMode">
ng-class="{'bold': filter.multiselectFilters.state.length > 0, 'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>State</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right dropdown-entries" aria-labelledby="dropdownState">
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownState">
<li ng-repeat="state in states" ng-class="state.workflowHeader ? 'dropdown-header' : ''">
<div ng-if="state.workflowHeader">
<a ng-if="state.workflowHeader">
{{ state.name | translate }}
</div>
<div ng-if="!state.workflowHeader"
ng-click="operateMultiselectFilter('state', state.id)">
<i class="fa fa-check" ng-if="multiselect.filters.state.indexOf(state.id) > -1"></i>
</a>
<a href ng-if="!state.workflowHeader"
ng-click="filter.operateMultiselectFilter('state', state.id, isSelectMode)">
<i class="fa fa-check" ng-if="filter.multiselectFilters.state.indexOf(state.id) > -1"></i>
{{ state.name | translate }}
</div>
</a>
</li>
</ul>
</span>
<!-- Category filter -->
<span class="dropdown" uib-dropdown ng-if="categories.length > 0">
<span uib-dropdown ng-if="categories.length > 0">
<span class="pointer" id="dropdownCategory" uib-dropdown-toggle
ng-class="{'bold': multiselect.filters.category.length > 0, 'disabled': isDeleteMode}"
ng-disabled="isDeleteMode">
ng-class="{'bold': filter.multiselectFilters.category.length > 0, 'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>Category</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right dropdown-entries"
aria-labelledby="dropdownCategory">
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownCategory">
<li ng-repeat="category in categories">
<div ng-click="operateMultiselectFilter('category', category.id)">
<i class="fa fa-check" ng-if="multiselect.filters.category.indexOf(category.id) > -1"></i>
<a href ng-click="filter.operateMultiselectFilter('category', category.id, isSelectMode)">
<i class="fa fa-check" ng-if="filter.multiselectFilters.category.indexOf(category.id) > -1"></i>
{{ category.name }}
</div>
</a>
</li>
<li class="divider"></li>
<li>
<a href ng-click="filter.operateMultiselectFilter('category', -1, isSelectMode)">
<i class="fa fa-check" ng-if="filter.multiselectFilters.category.indexOf(-1) > -1"></i>
<translate>No category set</translate>
</a>
</li>
</ul>
</span>
<!-- Motion block filter -->
<span class="dropdown" uib-dropdown ng-if="motionBlocks.length > 0">
<span uib-dropdown ng-if="motionBlocks.length > 0">
<span class="pointer" id="dropdownBlock" uib-dropdown-toggle
ng-class="{'bold': multiselect.filters.motionBlock.length > 0, 'disabled': isDeleteMode}"
ng-disabled="isDeleteMode">
ng-class="{'bold': filter.multiselectFilters.motionBlock.length > 0, 'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>Motion block</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right dropdown-entries"
aria-labelledby="dropdownBlock">
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownBlock">
<li ng-repeat="block in motionBlocks">
<div ng-click="operateMultiselectFilter('motionBlock', block.id)">
<i class="fa fa-check" ng-if="multiselect.filters.motionBlock.indexOf(block.id) > -1"></i>
<a href ng-click="filter.operateMultiselectFilter('motionBlock', block.id, isSelectMode)">
<i class="fa fa-check" ng-if="filter.multiselectFilters.motionBlock.indexOf(block.id) > -1"></i>
{{ block.title }}
</div>
</a>
</li>
<li class="divider"></li>
<li>
<a href ng-click="filter.operateMultiselectFilter('motionBlock', -1, isSelectMode)">
<i class="fa fa-check" ng-if="filter.multiselectFilters.motionBlock.indexOf(-1) > -1"></i>
<translate>No motion block set</translate>
</a>
</li>
</ul>
</span>
<!-- Tag filter -->
<span class="dropdown" uib-dropdown ng-if="tags.length > 0">
<span uib-dropdown ng-if="tags.length > 0">
<span class="pointer" id="dropdownTag" uib-dropdown-toggle
ng-class="{'bold': multiselect.filters.tag.length > 0, 'disabled': isDeleteMode}"
ng-disabled="isDeleteMode">
ng-class="{'bold': filter.multiselectFilters.tag.length > 0, 'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>Tag</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right dropdown-entries"
aria-labelledby="dropdownTag">
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownTag">
<li ng-repeat="tag in tags">
<div ng-click="operateMultiselectFilter('tag', tag.id)">
<i class="fa fa-check" ng-if="multiselect.filters.tag.indexOf(tag.id) > -1"></i>
<a href ng-click="filter.operateMultiselectFilter('tag', tag.id, isSelectMode)">
<i class="fa fa-check" ng-if="filter.multiselectFilters.tag.indexOf(tag.id) > -1"></i>
{{ tag.name }}
</div>
</a>
</li>
<li class="divider"></li>
<li>
<a href ng-click="filter.operateMultiselectFilter('tag', -1, isSelectMode)">
<i class="fa fa-check" ng-if="filter.multiselectFilters.tag.indexOf(-1) > -1"></i>
<translate>No tag set</translate>
</a>
</li>
</ul>
</span>
<!-- dropdown sort -->
<span class="dropdown" uib-dropdown>
<span uib-dropdown>
<span class="pointer" id="dropdownSort" uib-dropdown-toggle
ng-class="{'disabled': isDeleteMode}"
ng-disabled="isDeleteMode">
ng-class="{'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>Sort</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right dropdown-entries" aria-labelledby="dropdownSort">
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownSort">
<!-- item -->
<li>
<div ng-click="toggleSort('agenda_item.getItemNumberWithAncestors()')">
<a href ng-click="sort.toggle('agenda_item.getItemNumberWithAncestors()')">
<translate translate-comment="short form of agenda item">Item</translate>
<span class="spacer-right pull-right"></span>
<i class="pull-right fa"
ng-style="{'visibility': sortColumn === 'agenda_item.getItemNumberWithAncestors()' ? 'visible' : 'hidden'}"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
ng-style="{'visibility': sort.column === 'agenda_item.getItemNumberWithAncestors()' ? 'visible' : 'hidden'}"
ng-class="sort.reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
</div>
</a>
</li>
<!-- all other sortOptions -->
<li ng-repeat="option in sortOptions">
<div ng-click="toggleSort(option.name)">
<a href ng-click="sort.toggle(option.name)">
{{ option.display_name | translate }}
<span class="spacer-right pull-right"></span>
<i class="pull-right fa"
ng-style="{'visibility': sortColumn === option.name ? 'visible' : 'hidden'}"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
ng-style="{'visibility': sort.column === option.name ? 'visible' : 'hidden'}"
ng-class="sort.reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
</div>
</a>
</li>
</ul>
</span>
@ -221,49 +240,71 @@
<span class="form-group">
<span class="input-group">
<span class="input-group-addon"><i class="fa fa-search"></i></span>
<input type="text" ng-model="filter.search" class="form-control"
placeholder="{{ 'Search' | translate}}" ng-disabled="isDeleteMode">
<input type="text" ng-model="filter.filterString" class="form-control"
placeholder="{{ 'Search' | translate}}" ng-disabled="isSelectMode">
</span>
</span>
</span>
<!-- show all selected multiselectoptions -->
<span>
<!-- state -->
<span ng-repeat="state in states" class="pointer spacer-left-lg"
ng-if="!state.workflowHeader && multiselect.filters.state.indexOf(state.id) > -1"
ng-click="operateMultiselectFilter('state', state.id)"
ng-class="{'disabled': isDeleteMode}">
ng-if="!state.workflowHeader && filter.multiselectFilters.state.indexOf(state.id) > -1"
ng-click="filter.operateMultiselectFilter('state', state.id, isSelectMode)"
ng-class="{'disabled': isSelectMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ state.name | translate }}
</span>
</span>
<!-- category -->
<span ng-repeat="category in categories" class="pointer spacer-left-lg"
ng-if="multiselect.filters.category.indexOf(category.id) > -1"
ng-click="operateMultiselectFilter('category', category.id)"
ng-class="{'disabled': isDeleteMode}">
ng-if="filter.multiselectFilters.category.indexOf(category.id) > -1"
ng-click="filter.operateMultiselectFilter('category', category.id, isSelectMode)"
ng-class="{'disabled': isSelectMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ category.name }}
</span>
</span>
<span ng-if="filter.multiselectFilters.category.indexOf(-1) > -1" class="pointer spacer-left-lg"
ng-click="filter.operateMultiselectFilter('category', -1, isSelectMode)"
ng-class="{'disabled': isSelectMode}">
<i class="fa fa-times-circle"></i>
<translate>No category set</translate>
</span>
<!-- motion block -->
<span ng-repeat="motionBlock in motionBlocks" class="pointer spacer-left-lg"
ng-if="multiselect.filters.motionBlock.indexOf(motionBlock.id) > -1"
ng-click="operateMultiselectFilter('motionBlock', motionBlock.id)"
ng-class="{'disabled': isDeleteMode}">
ng-if="filter.multiselectFilters.motionBlock.indexOf(motionBlock.id) > -1"
ng-click="filter.operateMultiselectFilter('motionBlock', motionBlock.id, isSelectMode)"
ng-class="{'disabled': isSelectMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ motionBlock.title }}
</span>
</span>
<span ng-if="filter.multiselectFilters.motionBlock.indexOf(-1) > -1" class="pointer spacer-left-lg"
ng-click="filter.operateMultiselectFilter('motionBlock', -1, isSelectMode)"
ng-class="{'disabled': isSelectMode}">
<i class="fa fa-times-circle"></i>
<translate>No motion block set</translate>
</span>
<!-- tags -->
<span ng-repeat="tag in tags" class="pointer spacer-left-lg"
ng-if="multiselect.flters.tag.indexOf(tag.id) > -1"
ng-click="operateMultiselectFilter('tag', tag.id)"
ng-class="{'disabled': isDeleteMode}">
ng-if="filter.multiselectFilters.tag.indexOf(tag.id) > -1"
ng-click="filter.operateMultiselectFilter('tag', tag.id, isSelectMode)"
ng-class="{'disabled': isSelectMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ tag.name }}
</span>
</span>
<span ng-if="filter.multiselectFilters.tag.indexOf(-1) > -1" class="pointer spacer-left-lg"
ng-click="filter.operateMultiselectFilter('tag', -1, isSelectMode)"
ng-class="{'disabled': isSelectMode}">
<i class="fa fa-times-circle"></i>
<translate>No tag set</translate>
</span>
</span>
</div>
</div>
@ -273,16 +314,16 @@
ng-mouseleave="motion.hover=false"
ng-class="{'projected': motion.isProjected().length}"
ng-repeat="motion in motionsFiltered = (motions
| osFilter: filter.search : multiselect.getFilterString
| MultiselectFilter: multiselect.filters.state : getItemId.state
| MultiselectFilter: multiselect.filters.category : getItemId.category
| MultiselectFilter: multiselect.filters.motionBlock : getItemId.motionBlock
| MultiselectFilter: multiselect.filters.tag : getItemId.tag
| osFilter: filter.filterString : filter.getObjectQueryString
| MultiselectFilter: filter.multiselectFilters.state : getItemId.state
| MultiselectFilter: filter.multiselectFilters.category : getItemId.category
| MultiselectFilter: filter.multiselectFilters.motionBlock : getItemId.motionBlock
| MultiselectFilter: filter.multiselectFilters.tag : getItemId.tag
| toArray
| orderBy: sortColumn : reverse)">
| orderBy: sort.column : sort.reverse)">
<!-- select column -->
<div ng-show="isDeleteMode" os-perms="motions.can_manage" class="col-xs-1 centered">
<div ng-show="isSelectMode" os-perms="motions.can_manage" class="col-xs-1 centered">
<i class="fa text-danger pointer" ng-click="motion.selected=!motion.selected"
ng-class="motion.selected ? 'fa-check-square-o' : 'fa-square-o'"></i>
</div>
@ -293,12 +334,12 @@
</div>
<!-- main content column -->
<div class="col-xs-6 content">
<div class="identifier-col">
<div class="id-col">
<span ng-show="motion.identifier">
{{ motion.identifier }}
</span>
</div>
<div class="title-col">
<div>
<!-- ID and title -->
<div>
<strong>
@ -310,8 +351,8 @@
{{ motion.getStateName() }}
</span>
<span os-perms="motions.can_manage" ng-class="{'hiddenDiv': !motion.stateHover}" uib-dropdown>
<i class="fa fa-cog pointer" uib-dropdown-toggle id="state-dropdown{{ motion.id }}"></i>
<ul uib-dropdown-menu aria-labelledby="state-dropdown{{ motion.id }}">
<i class="fa fa-cog pointer" uib-dropdown-toggle id="stateDropdown{{ motion.id }}"></i>
<ul class="dropdown-menu" aria-labelledby="stateDropdown{{ motion.id }}">
<li ng-repeat="state in motion.state.getNextStates()">
<a href ng-click="updateState(motion, state.id)">{{ state.action_word | translate }}</a>
</li>
@ -353,12 +394,7 @@
</div>
</div>
<!-- additional content column -->
<style>
#motion-table .row .col-xs-4 {
width: calc(50% - {{ isDeleteMode ? '120' : '70' }}px);
}
</style>
<div class="col-xs-4 content">
<div class="col-xs-4 content" ng-style="{'width': isSelectMode ? 'calc(50% - 120px)' : 'calc(50% - 70px)'}">
<div style="width: 60%;" class="optional">
<small>
<!-- Category dropdown for manage user -->
@ -366,7 +402,7 @@
ng-mouseover="motion.categoryHover=true"
ng-mouseleave="motion.categoryHover=false">
<span uib-dropdown>
<span id="dropdown-category{{ motion.id }}" class="pointer"
<span id="dropdownCategory{{ motion.id }}" class="pointer"
uib-dropdown-toggle uib-tooltip="{{ 'Set a category' | translate }}"
tooltip-class="nobr">
<span ng-if="motion.category == null" ng-show="motion.hover">
@ -379,12 +415,12 @@
<i class="fa fa-cog fa-lg spacer-left" ng-show="motion.categoryHover"></i>
</span>
</span>
<ul class="dropdown-menu dropdown-entries" aria-labelledby="dropdown-category{{ motion.id }}">
<ul class="dropdown-menu" aria-labelledby="dropdownCategory{{ motion.id }}">
<li ng-repeat="category in categories">
<div ng-click="toggle_category(motion, category)">
<a href ng-click="toggleCategory(motion, category)">
<i class="fa fa-check" ng-if="category.id == motion.category.id"></i>
{{ category.name }}
</div>
</a>
</li>
</ul>
</span>
@ -400,7 +436,7 @@
ng-mouseover="motion.motionBlockHover=true"
ng-mouseleave="motion.motionBlockHover=false">
<span uib-dropdown>
<span id="dropdown-motionBlock{{ motion.id }}" class="pointer"
<span id="dropdownMotionBlock{{ motion.id }}" class="pointer"
uib-dropdown-toggle uib-tooltip="{{ 'Set a motion block' | translate }}"
tooltip-class="nobr">
<span ng-if="motion.motionBlock == null" ng-show="motion.hover">
@ -413,12 +449,12 @@
<i class="fa fa-cog fa-lg spacer-left" ng-show="motion.motionBlockHover"></i>
</span>
</span>
<ul class="dropdown-menu dropdown-entries" aria-labelledby="dropdown-motionBlock{{ motion.id }}">
<ul class="dropdown-menu" aria-labelledby="dropdownMotionBlock{{ motion.id }}">
<li ng-repeat="motionBlock in motionBlocks">
<div ng-click="toggle_motionBlock(motion, motionBlock)">
<a href ng-click="toggleMotionBlock(motion, motionBlock)">
<i class="fa fa-check" ng-if="motionBlock.id == motion.motionBlock.id"></i>
{{ motionBlock.title }}
</div>
</a>
</li>
</ul>
</span>
@ -434,7 +470,7 @@
ng-mouseover="motion.tagHover=true"
ng-mouseleave="motion.tagHover=false">
<span uib-dropdown>
<span id="dropdown-tags{{ motion.id }}" class="pointer"
<span id="dropdownTags{{ motion.id }}" class="pointer"
uib-dropdown-toggle uib-tooltip="{{ 'Add a tag' | translate }}"
tooltip-class="nobr">
<span ng-if="motion.tags.length == 0" ng-show="motion.hover">
@ -449,12 +485,12 @@
<i class="fa fa-cog fa-lg spacer-left" ng-show="motion.tagHover"></i>
</span>
</span>
<ul class="dropdown-menu dropdown-entries" aria-labelledby="dropdown-tags{{ motion.id }}">
<ul class="dropdown-menu" aria-labelledby="dropdownTags{{ motion.id }}">
<li ng-repeat="tag in tags">
<div ng-click="toggle_tag(motion, tag)">
<i class="fa fa-check" ng-if="has_tag(motion, tag)"></i>
<a href ng-click="toggleTag(motion, tag)">
<i class="fa fa-check" ng-if="hasTag(motion, tag)"></i>
{{ tag.name }}
</div>
</a>
</li>
</ul>
</span>
@ -475,7 +511,7 @@
</small>
</div>
<div style="width: 10%;" class="pull-right optional">
<div class="pull-right" ng-if="config('motions_min_supporters') != 0"
<div class="centered" ng-if="config('motions_min_supporters') != 0"
uib-tooltip="{{ motion.supporters.length }} {{ 'Supporters' | translate }}
{{ (config('motions_min_supporters') - motion.supporters.length) > 0 ? '(' + (config('motions_min_supporters') - motion.supporters.length) + ' ' + ('needed' | translate) + ')': '' }}"
tooltip-class="nobr">

View File

@ -462,10 +462,12 @@ angular.module('OpenSlidesApp.users.site', [
'PdfMakeDocumentProvider',
'gettextCatalog',
'UserCsvExport',
'Multiselect',
'osTableFilter',
'osTableSort',
'gettext',
function($scope, $state, $http, ngDialog, UserForm, User, Group, PasswordGenerator, Projector, ProjectionDefault,
UserListContentProvider, Config, UserAccessDataListContentProvider, PdfMakeDocumentProvider, gettextCatalog,
UserCsvExport, Multiselect) {
UserCsvExport, osTableFilter, osTableSort, gettext) {
User.bindAll({}, $scope, 'users');
Group.bindAll({where: {id: {'>': 1}}}, $scope, 'groups');
$scope.$watch(function () {
@ -478,20 +480,46 @@ angular.module('OpenSlidesApp.users.site', [
});
$scope.alert = {};
$scope.multiselect = Multiselect.instance();
$scope.multiselect.filters = {
// Filtering
$scope.filter = osTableFilter.createInstance();
$scope.filter.multiselectFilters = {
group: [],
};
$scope.multiselect.propertyList = ['first_name', 'last_name', 'title', 'number', 'comment', 'structure_level'];
$scope.multiselect.PropertyDict = {
$scope.filter.propertyList = ['first_name', 'last_name', 'title', 'number', 'comment', 'structure_level'];
$scope.filter.propertyDict = {
'groups_id' : function (group_id) {
return Group.get(group_id).name;
},
};
$scope.filter.booleanFilters = {
isPresent: {
value: undefined,
displayName: gettext('Present'),
choiceYes: gettext('Is present'),
choiceNo: gettext('Is not present'),
needExtraPermission: true,
},
isActive: {
value: undefined,
displayName: gettext('Active'),
choiceYes: gettext('Is active'),
choiceNo: gettext('Is not active'),
needExtraPermission: true,
},
isCommittee: {
value: undefined,
displayName: gettext('Committee'),
choiceYes: gettext('Is committee'),
choiceNo: gettext('Is not committee'),
},
};
$scope.getItemId = {
group: function (user) {return user.groups_id;},
};
// setup table sorting
// Sorting
$scope.sort = osTableSort.createInstance();
$scope.sort.column = $scope.config('users_sort_by');
$scope.sortOptions = [
{name: 'first_name',
display_name: 'First name'},
@ -510,39 +538,6 @@ angular.module('OpenSlidesApp.users.site', [
{name: 'comment',
display_name: 'Comment'},
];
$scope.sortColumn = $scope.config('users_sort_by');
$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;
};
// function to operate the multiselectFilter
$scope.operateMultiselectFilter = function (filter, id) {
if (!$scope.isSelectMode) {
$scope.multiselect.operate(filter, id);
}
};
// for reset-button
$scope.resetFilters = function () {
$scope.multiselect.resetFilters();
if ($scope.filter) {
$scope.filter.search = '';
}
$scope.isPresentFilter = undefined;
$scope.isActiveFilter = undefined;
$scope.isCommitteeFilter = undefined;
};
$scope.areFiltersSet = function () {
return $scope.multiselect.areFiltersSet() ||
$scope.isPresentFilter ||
$scope.isActiveFilter ||
$scope.isCommitteeFilter ||
($scope.filter ? $scope.filter.search : false);
};
// pagination
$scope.currentPage = 1;

View File

@ -116,20 +116,20 @@
<!-- filter users (for user with 'can_see_extra_data' permission) - consider present filter -->
<div os-perms="users.can_see_extra_data">
<span ng-repeat="user in $parent.usersFiltered = (users
| osFilter: filter.search : multiselect.getFilterString
| filter: {is_present: isPresentFilter}
| filter: {is_active: isActiveFilter}
| filter: {is_committee: isCommitteeFilter}
| MultiselectFilter: multiselect.filters.group : getItemId.group
| orderBy: sortColumn:reverse)"></span>
| osFilter: filter.filterString : filter.getObjectQueryString
| filter: {is_present: filter.booleanFilters.isPresent.value}
| filter: {is_active: filter.booleanFilters.isActive.value}
| filter: {is_committee: filter.booleanFilters.isCommittee.value}
| MultiselectFilter: filter.multiselectFilters.group : getItemId.group
| orderBy: sort.column: sort.reverse)"></span>
</div>
<!-- filter users (for user without 'can_see_extra_data' permission) -->
<div os-perms="!users.can_see_extra_data"
ng-repeat="user in $parent.usersFiltered = (users
| osFilter: filter.search : multiselect.getFilterString
| filter: {is_committee: isCommitteeFilter}
| MultiselectFilter: multiselect.filters.group : getItemId.group
| orderBy: sortColumn:reverse)"></div>
| osFilter: filter.filterString : filter.getObjectQueryString
| filter: {is_committee: filter.booleanFilters.isCommittee.value}
| MultiselectFilter: filter.multiselectFilters.group : getItemId.group
| orderBy: sort.column: sort.reverse)"></div>
<div class="os-table container-fluid">
<div class="row header-row">
@ -140,100 +140,62 @@
<div class="col-xs-11 main-header">
<span class="form-inline text-right pull-right">
<!-- reset Filters -->
<span class="sort-spacer pointer" ng-click="resetFilters()"
ng-if="areFiltersSet()" ng-disabled="isSelectMode"
<span class="sort-spacer pointer" ng-click="filter.reset()"
ng-if="filter.areFiltersSet()" ng-disabled="isSelectMode"
ng-class="{'disabled': isSelectMode}">
<i class="fa fa-times-circle"></i>
<translate>Filter</translate>
</span>
<!-- Group filter -->
<span class="dropdown" uib-dropdown>
<span uib-dropdown>
<span class="pointer" id="dropdownGroup" uib-dropdown-toggle
ng-class="{'bold': multiselect.filters.group.length > 0, 'disabled': isSelectMode}"
ng-class="{'bold': filter.multiselectFilters.group.length > 0, 'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>Groups</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right dropdown-entries" aria-labelledby="dropdownGroup">
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownGroup">
<li ng-repeat="group in groups">
<div ng-click="operateMultiselectFilter('group', group.id)">
<i class="fa fa-check" ng-if="multiselect.filters.group.indexOf(group.id) > -1"></i>
<a href ng-click="filter.operateMultiselectFilter('group', group.id, isSelectMode)">
<i class="fa fa-check" ng-if="filter.multiselectFilters.group.indexOf(group.id) > -1"></i>
{{ group.name | translate }}
</div>
</a>
</li>
<li class="divider"></li>
<li>
<a href ng-click="filter.operateMultiselectFilter('group', -1, isSelectMode)">
<i class="fa fa-check" ng-if="filter.multiselectFilters.group.indexOf(-1) > -1"></i>
<translate>No group set</translate>
</a>
</li>
</ul>
</span>
<!-- Present filter -->
<span class="dropdown" uib-dropdown>
<span class="pointer" id="dropdownPresent" uib-dropdown-toggle
ng-class="{'bold': isPresentFilter !== undefined, 'disabled': isSelectMode}"
<!-- boolean Filters -->
<span ng-repeat="(name, booleanFilter) in filter.booleanFilters"
ng-if="!booleanFilter.needExtraPermission || operator.hasPerms('users.can_see_extra_data')" uib-dropdown>
<span class="pointer" id="dropdown{{ name }}" uib-dropdown-toggle
ng-class="{'bold': booleanFilter.value !== undefined, 'disabled': isSelectMode}"
ng-disabled="isSelectMode">
<translate>Present</translate>
{{ booleanFilter.displayName }}
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right dropdown-entries" aria-labelledby="dropdownPresent">
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdown{{ name }}">
<li>
<div ng-click="isPresentFilter = (isPresentFilter ? undefined : true)">
<i class="fa" ng-class="{'fa-check': isPresentFilter === true}"></i>
<translate>Is present</translate>
</div>
<a href ng-click="booleanFilter.value = (booleanFilter.value ? undefined : true)">
<i class="fa" ng-class="{'fa-check': booleanFilter.value === true}"></i>
{{ booleanFilter.choiceYes }}
</a>
</li>
<li>
<div ng-click="isPresentFilter = (isPresentFilter === false) ? undefined : false">
<i class="fa" ng-class="{'fa-check': isPresentFilter === false}"></i>
<translate>Is not present</translate>
</div>
</li>
</ul>
</span>
<!-- Active filter -->
<span class="dropdown" uib-dropdown>
<span class="pointer" id="dropdownActive" uib-dropdown-toggle
ng-class="{'bold': isActiveFilter !== undefined, 'disabled': isSelectMode}"
ng-disabled="isSelecteMode">
<translate>Active</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right dropdown-entries" aria-labelledby="dropdownActive">
<li>
<div ng-click="isActiveFilter = (isActiveFilter ? undefined : true)">
<i class="fa" ng-class="{'fa-check': isActiveFilter === true}"></i>
<translate>Is active</translate>
</div>
</li>
<li>
<div ng-click="isActiveFilter = (isActiveFilter === false ? undefined : false)">
<i class="fa" ng-class="{'fa-check': isActiveFilter === false}"></i>
<translate>Is not active</translate>
</div>
</li>
</ul>
</span>
<!-- Committee filter -->
<span class="dropdown" uib-dropdown>
<span class="pointer" id="dropdownCommittee" uib-dropdown-toggle
ng-class="{'bold': isCommitteeFilter !== undefined, 'disabled': isSelectMode}"
ng-disabled="isSelecteMode">
<translate>Committee</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right dropdown-entries" aria-labelledby="dropdownCommittee">
<li>
<div ng-click="isCommitteeFilter = (isCommitteeFilter ? undefined : true)">
<i class="fa" ng-class="{'fa-check': isCommitteeFilter === true}"></i>
<translate>Yes</translate>
</div>
</li>
<li>
<div ng-click="isCommitteeFilter = (isCommitteeFilter === false ? undefined : false)">
<i class="fa" ng-class="{'fa-check': isCommitteeFilter === false}"></i>
<translate>No</translate>
</div>
<a href ng-click="booleanFilter.value = (booleanFilter.value === false) ? undefined : false">
<i class="fa" ng-class="{'fa-check': booleanFilter.value === false}"></i>
{{ booleanFilter.choiceNo }}
</a>
</li>
</ul>
</span>
<!-- dropdown sort -->
<span class="dropdown" uib-dropdown>
<span uib-dropdown>
<span class="pointer" id="dropdownSort" uib-dropdown-toggle
ng-class="{'disabled': isSelectMode}"
ng-disabled="isSelectMode">
@ -242,14 +204,14 @@
</span>
<ul class="dropdown-menu dropdown-menu-right dropdown-entries" aria-labelledby="dropdownSort">
<li ng-repeat="option in sortOptions">
<div ng-click="toggleSort(option.name)">
<a href ng-click="sort.toggle(option.name)">
{{ option.display_name | translate }}
<span class="spacer-right pull-right"></span>
<i class="pull-right fa"
ng-style="{'visibility': sortColumn === option.name ? 'visible' : 'hidden'}"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
ng-style="{'visibility': sort.column === option.name ? 'visible' : 'hidden'}"
ng-class="sort.reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
</div>
</a>
</li>
</ul>
</span>
@ -257,7 +219,7 @@
<span class="form-group">
<span class="input-group">
<span class="input-group-addon"><i class="fa fa-search"></i></span>
<input type="text" ng-model="filter.search" class="form-control" ng-model-options="{debounce: 500}"
<input type="text" ng-model="filter.filterString" class="form-control" ng-model-options="{debounce: 500}"
placeholder="{{ 'Search' | translate}}" ng-disabled="isSelectMode">
</span>
</span>
@ -265,39 +227,29 @@
<!-- show all selected multiselectoptions -->
<span>
<span ng-repeat="group in groups" class="pointer spacer-left-lg"
ng-if="multiselect.filters.group.indexOf(group.id) > -1"
ng-click="operateMultiselectFilter('group', group.id)"
ng-if="filter.multiselectFilters.group.indexOf(group.id) > -1"
ng-click="filter.operateMultiselectFilter('group', group.id, isSelectMode)"
ng-class="{'disabled': isSelectMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ group.name | translate }}
</span>
</span>
<span ng-hide="isPresentFilter === undefined"
class="pointer spacer-left-lg"
ng-click="isPresentFilter = undefined;"
<span ng-if="filter.multiselectFilters.group.indexOf(-1) > -1" class="pointer spacer-left-lg"
ng-click="filter.operateMultiselectFilter('group', -1, isSelectMode)"
ng-class="{'disabled': isSelectMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ isPresentFilter ? 'Is present' : 'Is not present' | translate }}
</span>
<i class="fa fa-times-circle"></i>
<translate>No group set</translate>
</span>
<span ng-hide="isActiveFilter === undefined"
<!-- for all boolean Filters -->
<span ng-repeat="(name, booleanFilter) in filter.booleanFilters"
ng-hide="booleanFilter.value === undefined"
class="pointer spacer-left-lg"
ng-click="isActiveFilter = undefined;"
ng-click="booleanFilter.value = undefined;"
ng-class="{'disabled': isSelectMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ isActiveFilter ? 'Is active' : 'Is not active' | translate }}
</span>
</span>
<span ng-hide="isCommitteeFilter === undefined"
class="pointer spacer-left-lg"
ng-click="isCommitteeFilter = undefined;"
ng-class="{'disabled': isSelectMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ isCommitteeFilter ? 'Is committee' : 'Is not committee' | translate }}
{{ booleanFilter.value ? booleanFilter.choiceYes : booleanFilter.choiceNo | translate }}
</span>
</span>
</span>
@ -350,13 +302,9 @@
</div>
</div>
</div>
<!-- additional content column -->
<style>
.os-table .row .col-xs-4 {
width: calc(50% - {{ isSelectMode ? '120' : '70' }}px);
}
</style>
<div class="col-xs-4 content">
<div class="col-xs-4 content" ng-style="{'width': isSelectMode ? 'calc(50% - 120px)' : 'calc(50% - 70px)'}">
<div style="width: 60%;" class="optional">
<small>
<!-- Group dropdown for manage user -->
@ -378,12 +326,12 @@
<i class="fa fa-cog fa-lg spcaer-left" ng-show="user.groupHover"></i>
</span>
</span>
<ul class="dropdown-menu dropdown-entries" aria-labelledby="dropdown-group{{ user.id }}">
<ul class="dropdown-menu" aria-labelledby="dropdown-group{{ user.id }}">
<li ng-repeat="group in groups">
<div ng-click="toggleGroup(user, group)">
<a href ng-click="toggleGroup(user, group)">
<i class="fa fa-check" ng-if="inArray(user.groups_id, group.id)"></i>
{{ group.name | translate }}
</div>
</a>
</li>
</ul>
</span>
@ -409,7 +357,7 @@
</div>
<div style="width: 40%;" class="pull-right" os-perms="users.can_see_extra_data">
<div os-perms="users.can_manage">
<span class="pointer" ng-click="user.is_present = !user.is_present; save(user);">
<span class="pointer nobr" ng-click="user.is_present = !user.is_present; save(user);">
<i class="fa" ng-class="user.is_present ? 'fa-check-square-o' : 'fa-square-o'"></i>
<span class="spacer-left" translate>Present</span>
</span>