From e0f78b16a0a18a0b4f73976c29a2fb970f44f49c Mon Sep 17 00:00:00 2001 From: FinnStutzenstein Date: Fri, 4 Nov 2016 11:57:23 +0100 Subject: [PATCH] New table design for users --- openslides/core/static/css/app.css | 94 ++-- openslides/core/static/js/core/base.js | 26 - openslides/core/static/js/core/site.js | 85 ++++ openslides/motions/static/js/motions/site.js | 172 ++++--- .../static/templates/motions/motion-list.html | 188 ++----- openslides/users/config_variables.py | 6 +- openslides/users/static/js/users/base.js | 2 +- openslides/users/static/js/users/site.js | 120 ++++- .../static/templates/users/user-list.html | 458 +++++++++++++----- 9 files changed, 711 insertions(+), 440 deletions(-) diff --git a/openslides/core/static/css/app.css b/openslides/core/static/css/app.css index 5a2cdaa5b..6ea0da188 100644 --- a/openslides/core/static/css/app.css +++ b/openslides/core/static/css/app.css @@ -866,23 +866,23 @@ img { } /** Motion **/ -#motion-table .row { - border: 1px solid #ddd; - border-top: 0px; +#motion-os-table .identifier-col { + width: 50px; + min-height: 1px; } -#motion-table .data-row:hover { - background-color: #f5f5f5; +#motion-os-table .identifier-col > div { + text-align: center; } -#motion-table .data-row > div { - padding: 10px 0px 10px 8px; +#motion-os-table .title-col { + width: calc(100% - 50px); } -#motion-table .header-row { - border-top: 1px solid #ddd; - background-color: #f5f5f5; +#motion-os-table .title-col, .os-table small { + color: #555; } + .motion-toolbar, .speakers-toolbar { background-color: #f5f5f5; border-bottom: 1px solid #ddd; @@ -890,6 +890,7 @@ img { height: 54px; margin: -20px -5px 50px -5px; } + .motion-toolbar:first-child { margin-bottom: 20px; } @@ -899,77 +900,78 @@ img { padding: 12px 15px 10px 15px; } -.header-row > div { + +/** OS-Table **/ +.os-table .row { + border: 1px solid #ddd; + border-top: 0px; +} + +.os-table .data-row:hover { + background-color: #f5f5f5; +} + +.os-table .data-row > div { + padding: 10px 0px 10px 0px; +} + +.os-table .header-row { + border-top: 1px solid #ddd; + background-color: #f5f5f5; +} + +.os-table .header-row > div { padding: 10px; } -#motion-table .main-header { +.os-table .main-header { width: calc(100% - 50px); float: right; } -#motion-table .main-header .form-inline { +.os-table .main-header .form-inline { margin-left: 15px; } -#motion-table .content > div { +.os-table .content > div { /* horizontal blocks */ display: inline-block; float: left; } -#motion-table .identifier-col { - width: 50px; - min-height: 1px; -} - -#motion-table .identifier-col > div { - text-align: center; -} - -#motion-table .title-col { - width: calc(100% - 50px); -} - -#motion-table .title-col, #motion-table small { - color: #555; -} - -#motion-table .content > div > div { +.os-table .content > div > div { /* vertival blocks */ margin-bottom: 3px; } -#motion-table .content > div > div:last-child { +.os-table .content > div > div:last-child { margin-bottom: 0px; } -#motion-table .row .centered { +.os-table .row .centered { text-align: center; } -#motion-table .row .col-xs-1 { +.os-table .row .col-xs-1 { width: 50px; } -#motion-table .row .col-xs-4 { +.os-table .projector { + width: 70px !important; +} + +.os-table .row .col-xs-4 { padding-right: 10px; } -#motion-table .dropdown { +.os-table .dropdown { display: inline-block; } -#motion-table .header-row .dropdown > span, #motion-table .sort-spacer { +.os-table .header-row .dropdown > span, .os-table .sort-spacer { padding: 5px 10px 5px 10px; } -#motion-table .dropdown-entry { - padding: 5px 10px 5px 10px; - display: inline-block; - width: 100%; -} - -#motion-table .title { - font-size: 110%; +.os-table .title { + font-size: 115%; margin-right: 10px; padding: 0; background-color: transparent; diff --git a/openslides/core/static/js/core/base.js b/openslides/core/static/js/core/base.js index ca0042c91..f0a48bbf6 100644 --- a/openslides/core/static/js/core/base.js +++ b/openslides/core/static/js/core/base.js @@ -699,32 +699,6 @@ angular.module('OpenSlidesApp.core', [ } ]) -/* - * This filter filters all items in an array. If the filterArray is empty, the - * array is passed. The filterArray contains numbers of the multiselect, e. g. [1, 3, 4]. - * 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 - */ -.filter('SelectMultipleFilter', [ - function () { - return function (array, filterArray, idFunction) { - if (filterArray.length === 0) { - return array; - } - return Array.prototype.filter.call(array, function (item) { - var id = idFunction(item); - if (!id) { - return false; - } else if (typeof id === 'number') { - id = [id]; - } - return _.intersection(id, filterArray).length > 0; - }); - }; - } -]) - // mark HTML as "trusted" .filter('trusted', [ '$sce', diff --git a/openslides/core/static/js/core/site.js b/openslides/core/static/js/core/site.js index 035a89939..c4ef29d96 100644 --- a/openslides/core/static/js/core/site.js +++ b/openslides/core/static/js/core/site.js @@ -342,6 +342,91 @@ angular.module('OpenSlidesApp.core.site', [ } ]) +.factory('Multiselect', [ + function () { + var instance = function () { + var areFiltersSet = function () { + var areFiltersSet = false; + _.forEach(self.filters, function (filterList, filter) { + if (filterList.length > 0) { + areFiltersSet = true; + } + }); + 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, + }; + resetFilters(); //Initiate filters + return self; + }; + + return { + instance: instance + }; + } +]) + +/* + * This filter filters all items in an array. If the filterArray is empty, the + * array is passed. The filterArray contains numbers of the multiselect, e. g. [1, 3, 4]. + * 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 + */ +.filter('MultiselectFilter', [ + function () { + return function (array, filterArray, idFunction) { + if (filterArray.length === 0) { + return array; + } + return Array.prototype.filter.call(array, function (item) { + var id = idFunction(item); + if (!id) { + return false; + } else if (typeof id === 'number') { + id = [id]; + } + return _.intersection(id, filterArray).length > 0; + }); + }; + } +]) + // Load the django url patterns .run([ 'runtimeStates', diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js index 7c2262d9d..bfe0acc47 100644 --- a/openslides/motions/static/js/motions/site.js +++ b/openslides/motions/static/js/motions/site.js @@ -653,6 +653,33 @@ angular.module('OpenSlidesApp.motions.site', [ } ]) +.factory('MotionCsvExport', [ + function () { + return function (element, motions) { + var csvRows = [ + ['identifier', 'title', 'text', 'reason', 'submitter', 'category', 'origin'], + ]; + _.forEach(motions, function (motion) { + var row = []; + row.push('"' + motion.identifier + '"'); + row.push('"' + motion.getTitle() + '"'); + row.push('"' + motion.getText() + '"'); + row.push('"' + motion.getReason() + '"'); + row.push('"' + motion.submitters[0].get_full_name() + '"'); + var category = motion.category ? motion.category.name : ''; + row.push('"' + category + '"'); + row.push('"' + motion.origin + '"'); + csvRows.push(row); + }); + + var csvString = csvRows.join("%0A"); + element.href = 'data:text/csv;charset=utf-8,' + csvString; + element.download = 'motions-export.csv'; + element.target = '_blank'; + }; + } +]) + // Cache for MotionPollDetailCtrl so that users choices are keeped during user actions (e. g. save poll form). .value('MotionPollDetailCtrlCache', {}) @@ -723,9 +750,11 @@ angular.module('OpenSlidesApp.motions.site', [ 'HTMLValidizer', 'Projector', 'ProjectionDefault', + 'MotionCsvExport', + 'Multiselect', function($scope, $state, $http, ngDialog, MotionForm, Motion, Category, Tag, Workflow, User, Agenda, MotionBlock, MotionDocxExport, MotionContentProvider, MotionCatalogContentProvider, PdfMakeConverter, PdfMakeDocumentProvider, - gettextCatalog, HTMLValidizer, Projector, ProjectionDefault) { + gettextCatalog, HTMLValidizer, Projector, ProjectionDefault, MotionCsvExport, Multiselect) { Motion.bindAll({}, $scope, 'motions'); Category.bindAll({}, $scope, 'categories'); MotionBlock.bindAll({}, $scope, 'motionBlocks'); @@ -743,35 +772,65 @@ angular.module('OpenSlidesApp.motions.site', [ }); $scope.alert = {}; - // setup table sorting - $scope.sortColumn = 'identifier'; - $scope.filterPresent = ''; - $scope.reverse = false; - - $scope.multiselectFilter = { + $scope.multiselect = Multiselect.instance(); + $scope.multiselect.filters = { state: [], category: [], motionBlock: [], tag: [] }; + $scope.multiselect.propertyList = ['identifier', 'origin']; + $scope.multiselect.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 = { + 'submitters' : function (submitter) { + return submitter.get_short_name(); + }, + 'supporters' : function (submitter) { + return supporter.get_short_name(); + }, + 'tags' : function (tag) { + return tag.name; + }, + }; $scope.getItemId = { state: function (motion) {return motion.state_id;}, category: function (motion) {return motion.category_id;}, motionBlock: function (motion) {return motion.motion_block_id;}, tag: function (motion) {return motion.tags_id;} }; + // setup table sorting + $scope.sortOptions = [ + {name: 'identifier', + display_name: 'Identifier'}, + {name: 'getTitle()', + display_name: 'Title'}, + {name: 'submitters', + display_name: 'Submitters'}, + {name: 'category.name', + display_name: 'Category'}, + {name: 'motionBlock.title', + display_name: 'Motion block'}, + {name: 'state.name', + display_name: 'State'}, + {name: 'log_messages[log_messages.length-1].time', + display_name: 'Creation date'}, + {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) { - if (_.indexOf($scope.multiselectFilter[filter], id) > -1) { - // remove id - $scope.multiselectFilter[filter] = _.filter($scope.multiselectFilter[filter], function (_id) { - return _id != id; - }); - } else { - // add id - $scope.multiselectFilter[filter].push(id); - } + $scope.multiselect.operate(filter, id); } }; // function to sort by clicked column @@ -781,61 +840,15 @@ angular.module('OpenSlidesApp.motions.site', [ } $scope.sortColumn = column; }; - // define custom search filter string - $scope.getFilterString = function (motion) { - var category = ''; - if (motion.category) { - category = motion.category.name; - } - var motionBlock = ''; - if (motion.motionBlock) { - motionBlock = motion.motionBlock.title; - } - return [ - motion.identifier, - motion.getTitle(), - motion.getText(), - motion.getReason(), - motion.origin, - _.map( - motion.submitters, - function (submitter) { - return submitter.get_short_name(); - } - ).join(" "), - _.map( - motion.supporters, - function (supporter) { - return supporter.get_short_name(); - } - ).join(" "), - _.map( - motion.tags, - function (tag) { - return tag.name; - } - ).join(" "), - category, - motionBlock - ].join(" "); - }; // for reset-button - $scope.reset_filters = function () { - $scope.multiselectFilter = { - state: [], - category: [], - motionBlock: [], - tag: [] - }; + $scope.resetFilters = function () { + $scope.multiselect.resetFilters(); if ($scope.filter) { $scope.filter.search = ''; } }; - $scope.are_filters_set = function () { - return $scope.multiselectFilter.state.length > 0 || - $scope.multiselectFilter.category.length > 0 || - $scope.multiselectFilter.motionBlock.length > 0 || - $scope.multiselectFilter.tag.length > 0 || + $scope.areFiltersSet = function () { + return $scope.multiselect.areFiltersSet() || ($scope.filter ? $scope.filter.search : false); }; @@ -910,7 +923,7 @@ angular.module('OpenSlidesApp.motions.site', [ }; // Export as a pdf file - $scope.pdf_export = function() { + $scope.pdfExport = function() { var filename = gettextCatalog.getString("Motions") + ".pdf"; var image_sources = []; @@ -940,31 +953,12 @@ angular.module('OpenSlidesApp.motions.site', [ }; // Export as a csv file - $scope.csv_export = function () { + $scope.csvExport = function () { var element = document.getElementById('downloadLinkCSV'); - var csvRows = [ - ['identifier', 'title', 'text', 'reason', 'submitter', 'category', 'origin'], - ]; - angular.forEach($scope.motionsFiltered, function (motion) { - var row = []; - row.push('"' + motion.identifier + '"'); - row.push('"' + motion.getTitle() + '"'); - row.push('"' + motion.getText() + '"'); - row.push('"' + motion.getReason() + '"'); - row.push('"' + motion.submitters[0].get_full_name() + '"'); - var category = motion.category ? motion.category.name : ''; - row.push('"' + category + '"'); - row.push('"' + motion.origin + '"'); - csvRows.push(row); - }); - - var csvString = csvRows.join("%0A"); - element.href = 'data:text/csv;charset=utf-8,' + csvString; - element.download = 'motions-export.csv'; - element.target = '_blank'; + MotionCsvExport(element, $scope.motionsFiltered); }; // Export as docx file - $scope.docx_export = function () { + $scope.docxExport = function () { MotionDocxExport.export($scope.motionsFiltered, $scope.categories); }; diff --git a/openslides/motions/static/templates/motions/motion-list.html b/openslides/motions/static/templates/motions/motion-list.html index fbcda27fa..dc94212b0 100644 --- a/openslides/motions/static/templates/motions/motion-list.html +++ b/openslides/motions/static/templates/motions/motion-list.html @@ -51,7 +51,7 @@