From ee5adb8fd2667bd586d282567491d90d96935dbb Mon Sep 17 00:00:00 2001 From: FinnStutzenstein Date: Fri, 2 Dec 2016 09:38:02 +0100 Subject: [PATCH] New AgendaTable --- README.rst | 2 + bower.json | 2 + openslides/agenda/static/js/agenda/site.js | 212 ++++++--- .../static/templates/agenda/item-list.html | 427 ++++++++++-------- .../assignments/static/js/assignments/site.js | 16 +- .../assignments/assignment-list.html | 7 +- openslides/core/static/css/app.css | 17 +- openslides/core/static/js/core/site.js | 37 +- .../mediafiles/static/js/mediafiles/site.js | 35 +- .../templates/mediafiles/mediafile-list.html | 9 +- openslides/motions/static/js/motions/site.js | 25 +- .../static/templates/motions/motion-list.html | 9 +- openslides/users/static/js/users/site.js | 57 +-- .../static/templates/users/user-list.html | 54 ++- 14 files changed, 585 insertions(+), 324 deletions(-) diff --git a/README.rst b/README.rst index 5a100c867..0536ef1fc 100644 --- a/README.rst +++ b/README.rst @@ -178,6 +178,7 @@ OpenSlides uses the following projects or parts of them: * `angular-bootstrap `_, License: MIT * `angular-bootstrap-colorpicker `_, License: MIT * `angular-chosen-localytics `_, License: MIT + * `angular-cookies `_, License: MIT * `angular-csv-import `_, License: MIT * `angular-formly `_, License: MIT * `angular-formly-templates-bootstrap `_, License: MIT @@ -191,6 +192,7 @@ OpenSlides uses the following projects or parts of them: * `angular-ui-router `_, License: MIT * `angular-ui-tinymce `_, License: MIT * `angular-ui-tree `_, License: MIT + * `angular-xeditable `_, License: MIT * `api-check `_, License: MIT * `bootstrap `_, License: MIT * `bootstrap-ui-datetime-picker `_, License: MIT diff --git a/bower.json b/bower.json index 3e2168d31..aef1e10b8 100644 --- a/bower.json +++ b/bower.json @@ -9,6 +9,7 @@ "angular-bootstrap-colorpicker": "~3.0.25", "angular-chosen-localytics": "~1.5.0", "angular-csv-import": "0.0.36", + "angular-cookies": "~1.5.9", "angular-file-saver": "~1.1.2", "angular-formly": "~8.4.0", "angular-formly-templates-bootstrap": "~6.2.0", @@ -21,6 +22,7 @@ "angular-ui-router": "~0.3.1", "angular-ui-tinymce": "~0.0.17", "angular-ui-tree": "~2.22.0", + "angular-xeditable": "~0.5.0", "bootstrap-css-only": "~3.3.6", "bootstrap-ui-datetime-picker": "~2.4.0", "docxtemplater": "~2.1.5", diff --git a/openslides/agenda/static/js/agenda/site.js b/openslides/agenda/static/js/agenda/site.js index ee1df4c33..c4a16c275 100644 --- a/openslides/agenda/static/js/agenda/site.js +++ b/openslides/agenda/static/js/agenda/site.js @@ -107,8 +107,12 @@ angular.module('OpenSlidesApp.agenda.site', [ 'AgendaContentProvider', 'PdfMakeDocumentProvider', 'gettextCatalog', + 'gettext', + 'osTableFilter', + 'AgendaCsvExport', function($scope, $filter, $http, $state, DS, operator, ngDialog, Agenda, TopicForm, AgendaTree, Projector, - ProjectionDefault, AgendaContentProvider, PdfMakeDocumentProvider, gettextCatalog) { + ProjectionDefault, AgendaContentProvider, PdfMakeDocumentProvider, gettextCatalog, gettext, osTableFilter, + AgendaCsvExport) { // Bind agenda tree to the scope $scope.$watch(function () { return Agenda.lastModified(); @@ -132,6 +136,87 @@ angular.module('OpenSlidesApp.agenda.site', [ }); $scope.alert = {}; + + // Filtering + $scope.filter = osTableFilter.createInstance('AgendaTableFilter'); + + if (!$scope.filter.existsCookie()) { + $scope.filter.booleanFilters = { + closed: { + value: undefined, + displayName: gettext('Closed items'), + choiceYes: gettext('Closed items'), + choiceNo: gettext('Open items'), + }, + is_hidden: { + value: undefined, + displayName: gettext('Internal items'), + choiceYes: gettext('Internal items'), + choiceNo: gettext('No internal items'), + }, + }; + + $scope.filter.save(); + } + $scope.filter.propertyList = ['item_number', 'title', 'title_list_view', 'comment', 'duration']; + $scope.filter.propertyFunctionList = [ + function (item) {return item.getListViewTitle();}, + ]; + $scope.filter.propertyDict = { + 'speakers' : function (speaker) { + console.log(speaker); + return ''; + }, + }; + + // pagination + $scope.currentPage = 1; + $scope.itemsPerPage = 100; + $scope.limitBegin = 0; + $scope.pageChanged = function() { + $scope.limitBegin = ($scope.currentPage - 1) * $scope.itemsPerPage; + }; + + // parse duration for inline editing + $scope.generateDurationText = function (item) { + //convert data from model format (m) to view format (hh:mm) + if (item.duration) { + var time = "", + totalminutes = item.duration; + if (totalminutes < 0) { + time = "-"; + totalminutes = -totalminutes; + } + var hh = Math.floor(totalminutes / 60); + var mm = Math.floor(totalminutes % 60); + // Add leading "0" for double digit values + mm = ("0"+mm).slice(-2); + time += hh + ":" + mm; + item.durationText = time; + } else { + item.durationText = ""; + } + }; + $scope.setDurationText = function (item) { + //convert data from view format (hh:mm) to model format (m) + var time = item.durationText.replace('h', '').split(':'); + var data; + if (time.length > 1 && !isNaN(time[0]) && !isNaN(time[1])) { + data = (+time[0]) * 60 + (+time[1]); + if (data < 0) { + data = "-"+data; + } + item.duration = parseInt(data); + } else if (time.length == 1 && !isNaN(time[0])) { + data = (+time[0]); + item.duration = parseInt(data); + } else { + item.duration = 0; + } + $scope.save(item); + }; + + /** Duration calculations **/ $scope.sumDurations = function () { var totalDuration = 0; $scope.items.forEach(function (item) { @@ -141,7 +226,6 @@ angular.module('OpenSlidesApp.agenda.site', [ }); return totalDuration; }; - $scope.calculateEndTime = function () { var totalDuration = $scope.sumDurations(); var startTimestamp = $scope.config('agenda_start_event_date_time'); @@ -156,22 +240,33 @@ angular.module('OpenSlidesApp.agenda.site', [ } }; - $scope.getUpdateStatePrefix = function (item) { - var prefix = item.content_object.collection.replace('/','.'); - // Hotfix for Issue 2566. - // The changes could be reverted if Issue 2480 is closed. - prefix = prefix.replace('motion-block', 'motionBlock'); - return prefix; + /** Agenda item functions **/ + // open dialog for new topics // TODO Remove this. Don't forget import button in template. + $scope.newDialog = function () { + ngDialog.open(TopicForm.getDialog()); }; - - // pagination - $scope.currentPage = 1; - $scope.itemsPerPage = 100; - $scope.limitBegin = 0; - $scope.pageChanged = function() { - $scope.limitBegin = ($scope.currentPage - 1) * $scope.itemsPerPage; + // save changed item + $scope.save = function (item) { + Agenda.save(item).then( + function(success) { + $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 related item + $scope.deleteRelatedItem = function (item) { + DS.destroy(item.content_object.collection, item.content_object.id); + }; + // auto numbering of agenda items + $scope.autoNumbering = function() { + $http.post('/rest/agenda/item/numbering/', {}); }; - // check open permission // TODO: Use generic solution here. $scope.isAllowedToSeeOpenLink = function (item) { @@ -189,47 +284,37 @@ angular.module('OpenSlidesApp.agenda.site', [ return false; } }; - // open dialog for new topics // TODO Remove this. Don't forget import button in template. - $scope.newDialog = function () { - ngDialog.open(TopicForm.getDialog()); + $scope.getUpdateStatePrefix = function (item) { + var prefix = item.content_object.collection.replace('/','.'); + // Hotfix for Issue 2566. + // The changes could be reverted if Issue 2480 is closed. + prefix = prefix.replace('motion-block', 'motionBlock'); + return prefix; }; - // cancel QuickEdit mode - $scope.cancelQuickEdit = function (item) { - // revert all changes by restore (refresh) original item object from server - Agenda.refresh(item); - item.quickEdit = false; + // export + $scope.pdfExport = function () { + var filename = gettextCatalog.getString('Agenda') + '.pdf'; + var agendaContentProvider = AgendaContentProvider.createInstance($scope.itemsFiltered); + var documentProvider = PdfMakeDocumentProvider.createInstance(agendaContentProvider); + pdfMake.createPdf(documentProvider.getDocument()).download(filename); }; - // save changed item - $scope.save = function (item) { - Agenda.save(item).then( - function(success) { - item.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 related item - $scope.deleteRelatedItem = function (item) { - DS.destroy(item.content_object.collection, item.content_object.id); + $scope.csvExport = function () { + var element = document.getElementById('downloadLinkCSV'); + AgendaCsvExport(element, $scope.itemsFiltered); }; - // *** delete mode functions *** - $scope.isDeleteMode = false; + /** select mode functions **/ + $scope.isSelectMode = false; // check all checkboxes $scope.checkAll = function () { + $scope.selectedAll = !$scope.selectedAll; angular.forEach($scope.items, function (item) { item.selected = $scope.selectedAll; }); }; // uncheck all checkboxes if isDeleteMode is closed $scope.uncheckAll = function () { - if (!$scope.isDeleteMode) { + if (!$scope.isSelectMode) { $scope.selectedAll = false; angular.forEach($scope.items, function (item) { item.selected = false; @@ -243,7 +328,7 @@ angular.module('OpenSlidesApp.agenda.site', [ DS.destroy(item.content_object.collection, item.content_object.id); } }); - $scope.isDeleteMode = false; + $scope.isSelectMode = false; $scope.uncheckAll(); }; @@ -319,17 +404,6 @@ angular.module('OpenSlidesApp.agenda.site', [ }); return projectorIds; }; - // auto numbering of agenda items - $scope.autoNumbering = function() { - $http.post('/rest/agenda/item/numbering/', {}); - }; - - $scope.makePDF = function() { - var filename = gettextCatalog.getString('Agenda') + '.pdf'; - var agendaContentProvider = AgendaContentProvider.createInstance($scope.items); - var documentProvider = PdfMakeDocumentProvider.createInstance(agendaContentProvider); - pdfMake.createPdf(documentProvider.getDocument()).download(filename); - }; } ]) @@ -613,6 +687,30 @@ angular.module('OpenSlidesApp.agenda.site', [ } ]) +.factory('AgendaCsvExport', [ + function () { + return function (element, agenda) { + var csvRows = [ + ['title', 'text', 'duration', 'comment', 'is_hidden'], + ]; + _.forEach(agenda, function (item) { + var row = []; + row.push('"' + (item.title || '') + '"'); + row.push('"' + (item.text || '') + '"'); + row.push('"' + (item.duration || '') + '"'); + row.push('"' + (item.comment || '') + '"'); + row.push('"' + (item.is_hidden ? '1' : '') + '"'); + csvRows.push(row); + }); + + var csvString = csvRows.join("%0A"); + element.href = 'data:text/csv;charset=utf-8,' + csvString; + element.download = 'agenda-export.csv'; + element.target = '_blank'; + }; + } +]) + //mark all agenda config strings for translation with Javascript .config([ 'gettext', diff --git a/openslides/agenda/static/templates/agenda/item-list.html b/openslides/agenda/static/templates/agenda/item-list.html index 6861cfec8..fd6dec768 100644 --- a/openslides/agenda/static/templates/agenda/item-list.html +++ b/openslides/agenda/static/templates/agenda/item-list.html @@ -61,66 +61,61 @@
-
-
- - - - - - - Sort ... - - - - - Numbering - - - - - PDF - -
-
-
-
-
-
-
- -
-
- + + + + Sort ... + + + + +
+ +
-
-
- - - Show internal items - - - Show closed items -
-
-
+
- - - - - - - - - -
- - - - Agenda item - - Duration - - {{ sumDurations() | osMinutesToTime }}h - - (Estimated end: {{ calculateEndTime() }}) - + +
+
+
+ +
+
+ + + + + Filter -
- Done -
- - - - - - - - - {{ item.getListViewTitle() }} - - - {{ item.getListViewTitle() }} - - - -
- {{ item.comment }} -
-
- List of speakers - | - Edit | - QuickEdit | - Delete - -
-
- {{ item.duration | osMinutesToTime }} - h - - - - - - -

{{ item.getTitle() }} – QuickEdit

-
- {{ alert.msg }} + + + + + + + + + + + + + + + {{ booleanFilter.value ? booleanFilter.choiceYes : booleanFilter.choiceNo | translate }} + + + +
+ + + +
+ + +
+ +
+ + + + +
+
+ + + +
+
+ +
+ + + {{ item.getListViewTitle() }} + + + {{ item.getListViewTitle() }} + +
-
-
- - + +
+ + List of speakers + · + Edit · + Delete + + +
+ +
+
+ +
+
+ +
+
+ + + {{ (item.duration | osMinutesToTime) || ('Set duration...' | translate) }} + h + +
-
- - +
+
+ + {{ item.comment || ('Set comment...' | translate)}} +
+
+
+
+
+
+
+ + Internal item +
+
+ + Done
-
-
- - - Show as agenda item -
-
- - -
+
+ + Done
-
-   - - Edit ... -
-
+
+
+
+ +
+
- + diff --git a/openslides/assignments/static/js/assignments/site.js b/openslides/assignments/static/js/assignments/site.js index 6fa0367b9..8541bea19 100644 --- a/openslides/assignments/static/js/assignments/site.js +++ b/openslides/assignments/static/js/assignments/site.js @@ -303,9 +303,10 @@ angular.module('OpenSlidesApp.assignments.site', [ 'User', 'osTableFilter', 'osTableSort', + 'gettext', function($scope, ngDialog, AssignmentForm, Assignment, Tag, Agenda, phases, Projector, ProjectionDefault, gettextCatalog, AssignmentContentProvider, AssignmentCatalogContentProvider, PdfMakeDocumentProvider, - User, osTableFilter, osTableSort) { + User, osTableFilter, osTableSort, gettext) { Assignment.bindAll({}, $scope, 'assignments'); Tag.bindAll({}, $scope, 'tags'); $scope.$watch(function () { @@ -320,11 +321,14 @@ angular.module('OpenSlidesApp.assignments.site', [ $scope.alert = {}; // Filtering - $scope.filter = osTableFilter.createInstance(); - $scope.filter.multiselectFilters = { - tag: [], - phase: [], - }; + $scope.filter = osTableFilter.createInstance('AssignmentTableFilter'); + + if (!$scope.filter.existsCookie()) { + $scope.filter.multiselectFilters = { + tag: [], + phase: [], + }; + } $scope.filter.propertyList = ['title', 'description']; $scope.filter.propertyFunctionList = [ function (assignment) { diff --git a/openslides/assignments/static/templates/assignments/assignment-list.html b/openslides/assignments/static/templates/assignments/assignment-list.html index 60f40a6d6..8438a9221 100644 --- a/openslides/assignments/static/templates/assignments/assignment-list.html +++ b/openslides/assignments/static/templates/assignments/assignment-list.html @@ -162,7 +162,8 @@ + placeholder="{{ 'Search' | translate}}" ng-disable="isSelectMode" + ng-change="filter.save()"> @@ -224,7 +225,9 @@
- {{ assignment.title }} + + {{ assignment.title }} +
  • - + {{ booleanFilter.choiceYes }}
  • - + {{ booleanFilter.choiceNo }} @@ -209,7 +209,8 @@ + placeholder="{{ 'Search' | translate}}" ng-disabled="isSelectMode" + ng-change="filter.save()"> @@ -219,7 +220,7 @@ diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js index 2c2ceecff..aefbc49fa 100644 --- a/openslides/motions/static/js/motions/site.js +++ b/openslides/motions/static/js/motions/site.js @@ -822,13 +822,16 @@ angular.module('OpenSlidesApp.motions.site', [ $scope.alert = {}; // Filtering - $scope.filter = osTableFilter.createInstance(); - $scope.filter.multiselectFilters = { - state: [], - category: [], - motionBlock: [], - tag: [] - }; + $scope.filter = osTableFilter.createInstance('MotionTableFilter'); + + if (!$scope.filter.existsCookie()) { + $scope.filter.multiselectFilters = { + state: [], + category: [], + motionBlock: [], + tag: [] + }; + } $scope.filter.propertyList = ['identifier', 'origin']; $scope.filter.propertyFunctionList = [ function (motion) {return motion.getTitle();}, @@ -907,7 +910,7 @@ angular.module('OpenSlidesApp.motions.site', [ // Use this methon instead of Motion.save(), because otherwise // you have to provide always a title and a text - var save = function (motion) { + $scope.save = function (motion) { motion.title = motion.getTitle(-1); motion.text = motion.getText(-1); motion.reason = motion.getReason(-1); @@ -922,7 +925,7 @@ angular.module('OpenSlidesApp.motions.site', [ } else { motion.tags_id.push(tag.id); } - save(motion); + $scope.save(motion); }; $scope.toggleCategory = function (motion, category) { if (motion.category_id == category.id) { @@ -930,7 +933,7 @@ angular.module('OpenSlidesApp.motions.site', [ } else { motion.category_id = category.id; } - save(motion); + $scope.save(motion); }; $scope.toggleMotionBlock = function (motion, block) { if (motion.motion_block_id == block.id) { @@ -938,7 +941,7 @@ angular.module('OpenSlidesApp.motions.site', [ } else { motion.motion_block_id = block.id; } - save(motion); + $scope.save(motion); }; // open new/edit dialog diff --git a/openslides/motions/static/templates/motions/motion-list.html b/openslides/motions/static/templates/motions/motion-list.html index eeff103d8..c170f45b6 100644 --- a/openslides/motions/static/templates/motions/motion-list.html +++ b/openslides/motions/static/templates/motions/motion-list.html @@ -241,7 +241,8 @@ + placeholder="{{ 'Search' | translate}}" ng-disabled="isSelectMode" + ng-change="filter.save()"> @@ -506,7 +507,11 @@
    - {{ motion.origin | limitTo:25 }}{{ motion.origin.length > 25 ? '...' : '' }} +
    + + {{ motion.origin | limitTo:25 }}{{ motion.origin.length > 25 ? '...' : '' }} + +
  • diff --git a/openslides/users/static/js/users/site.js b/openslides/users/static/js/users/site.js index 3d833d97d..55ab08d4d 100644 --- a/openslides/users/static/js/users/site.js +++ b/openslides/users/static/js/users/site.js @@ -510,39 +510,42 @@ angular.module('OpenSlidesApp.users.site', [ $scope.alert = {}; // Filtering - $scope.filter = osTableFilter.createInstance(); - $scope.filter.multiselectFilters = { - group: [], - }; + $scope.filter = osTableFilter.createInstance('UserTableFilter'); + + if (!$scope.filter.existsCookie()) { + $scope.filter.multiselectFilters = { + group: [], + }; + $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.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;}, }; diff --git a/openslides/users/static/templates/users/user-list.html b/openslides/users/static/templates/users/user-list.html index 65097fdcb..a16fcf363 100644 --- a/openslides/users/static/templates/users/user-list.html +++ b/openslides/users/static/templates/users/user-list.html @@ -181,13 +181,13 @@