(html,js) Reorder and filter calendars list

pull/207/head
Francis Lachapelle 2016-04-27 15:48:31 -04:00
parent 6cb513f30b
commit 2963654800
7 changed files with 136 additions and 22 deletions

1
NEWS
View File

@ -6,6 +6,7 @@ New features
- [core] new user-based rate-limiting support for all SOGo requests (#3188) - [core] new user-based rate-limiting support for all SOGo requests (#3188)
- [web] toolbar of all-day events can be expanded to display all events - [web] toolbar of all-day events can be expanded to display all events
- [web] added AngularJS's XSRF support (#3246) - [web] added AngularJS's XSRF support (#3246)
- [web] calendars list can be reordered and filtered
Enhancements Enhancements
- [web] updated Angular Material to version 1.0.6 - [web] updated Angular Material to version 1.0.6

View File

@ -8,7 +8,7 @@
xmlns:label="OGo:label" xmlns:label="OGo:label"
className="UIxPageFrame" className="UIxPageFrame"
title="title" title="title"
const:jsFiles="Common.js, Preferences.services.js, Contacts.services.js, Mailer.services.js, vendor/angular-file-upload.min.js, Scheduler.js, Scheduler.services.js"> const:jsFiles="Common.js, vendor/ng-sortable.js, Preferences.services.js, Contacts.services.js, Mailer.services.js, vendor/angular-file-upload.min.js, Scheduler.js, Scheduler.services.js">
<script type="text/javascript"> <script type="text/javascript">
var firstDayOfWeek = <var:string value="firstDayOfWeek"/>; var firstDayOfWeek = <var:string value="firstDayOfWeek"/>;
var dayStartHour = <var:string value="dayStartHour"/>; var dayStartHour = <var:string value="dayStartHour"/>;
@ -37,9 +37,10 @@
ng-class="{ 'sg-close': leftIsClose }"> ng-class="{ 'sg-close': leftIsClose }">
<var:component className="UIxSidenavToolbarTemplate" /> <var:component className="UIxSidenavToolbarTemplate" />
<md-content class="md-flex" md-scroll-y="md-scroll-y" <md-content class="md-flex" md-scroll-y="md-scroll-y"
ng-class="{'sg-list-sortable': !app.sortableCalendars.disabled}"
md-colors="::{ backgroundColor: 'default-background-300' }"> md-colors="::{ backgroundColor: 'default-background-300' }">
<!-- User's calendars --> <!-- User's calendars -->
<section class="sg-section-list"> <section>
<md-subheader class="sg-md-subheader--icon-button" <md-subheader class="sg-md-subheader--icon-button"
md-colors="::{background: 'default-background-300'}"> md-colors="::{background: 'default-background-300'}">
<div layout="row" layout-align="space-between center"> <div layout="row" layout-align="space-between center">
@ -51,8 +52,8 @@
</md-button> </md-button>
</div> </div>
</md-subheader> </md-subheader>
<md-list> <md-list ng-sortable="app.sortableCalendars">
<md-list-item ng-repeat="calendar in app.service.$calendars" <md-list-item ng-repeat="calendar in app.service.$calendars | filter:app.filter"
ng-dblclick="app.editFolder(calendar)"> ng-dblclick="app.editFolder(calendar)">
<md-checkbox ng-model="calendar.active" <md-checkbox ng-model="calendar.active"
ng-class="calendar.getClassName('checkbox')" ng-class="calendar.getClassName('checkbox')"
@ -73,7 +74,8 @@
sg-enter="app.renameFolder(calendar)" sg-enter="app.renameFolder(calendar)"
sg-escape="app.revertEditing(calendar)"/> sg-escape="app.revertEditing(calendar)"/>
</md-input-container> </md-input-container>
<md-menu class="md-secondary" label:aria-label="Options"> <md-menu class="md-secondary" label:aria-label="Options"
md-colors="::{color: 'accent-400'}">
<md-button class="md-icon-button" label:aria-label="Options" <md-button class="md-icon-button" label:aria-label="Options"
ng-click="$mdOpenMenu()" ng-click="$mdOpenMenu()"
md-menu-origin="md-menu-origin"> md-menu-origin="md-menu-origin">
@ -135,7 +137,7 @@
</md-list> </md-list>
</section> </section>
<!-- Subscriptions --> <!-- Subscriptions -->
<section class="sg-section-list"> <section>
<md-subheader class="sg-md-subheader--icon-button" <md-subheader class="sg-md-subheader--icon-button"
md-colors="::{background: 'default-background-300'}"> md-colors="::{background: 'default-background-300'}">
<div layout="row" layout-align="space-between center"> <div layout="row" layout-align="space-between center">
@ -148,8 +150,8 @@
</md-button> </md-button>
</div> </div>
</md-subheader> </md-subheader>
<md-list> <md-list ng-sortable="app.sortableSubscriptions">
<md-list-item ng-repeat="calendar in app.service.$subscriptions" <md-list-item ng-repeat="calendar in app.service.$subscriptions | filter:app.filter"
ng-dblclick="app.editFolder(calendar)"> ng-dblclick="app.editFolder(calendar)">
<md-checkbox ng-model="calendar.active" <md-checkbox ng-model="calendar.active"
ng-class="calendar.getClassName('checkbox')" ng-class="calendar.getClassName('checkbox')"
@ -170,7 +172,8 @@
sg-enter="app.renameFolder(calendar)" sg-enter="app.renameFolder(calendar)"
sg-escape="app.revertEditing(calendar)"/> sg-escape="app.revertEditing(calendar)"/>
</md-input-container> </md-input-container>
<md-menu class="md-secondary" label:aria-label="Options"> <md-menu class="md-secondary" label:aria-label="Options"
md-colors="::{color: 'accent-400'}">
<md-button class="md-icon-button" label:aria-label="Options" <md-button class="md-icon-button" label:aria-label="Options"
ng-click="$mdOpenMenu()" ng-click="$mdOpenMenu()"
md-menu-origin="md-menu-origin"> md-menu-origin="md-menu-origin">
@ -204,7 +207,7 @@
</md-list> </md-list>
</section> </section>
<!-- Web Calendars --> <!-- Web Calendars -->
<section class="sg-section-list"> <section>
<md-subheader class="sg-md-subheader--icon-button" <md-subheader class="sg-md-subheader--icon-button"
md-colors="::{background: 'default-background-300'}"> md-colors="::{background: 'default-background-300'}">
<div layout="row" layout-align="space-between center"> <div layout="row" layout-align="space-between center">
@ -216,8 +219,8 @@
</md-button> </md-button>
</div> </div>
</md-subheader> </md-subheader>
<md-list> <md-list ng-sortable="app.sortableWebCalendars">
<md-list-item ng-repeat="calendar in app.service.$webcalendars" <md-list-item ng-repeat="calendar in app.service.$webcalendars | filter:app.filter"
ng-dblclick="app.editFolder(calendar)"> ng-dblclick="app.editFolder(calendar)">
<md-checkbox ng-model="calendar.active" <md-checkbox ng-model="calendar.active"
ng-class="calendar.getClassName('checkbox')" ng-class="calendar.getClassName('checkbox')"
@ -235,7 +238,8 @@
sg-enter="app.renameFolder(calendar)" sg-enter="app.renameFolder(calendar)"
sg-escape="app.revertEditing(calendar)"/> sg-escape="app.revertEditing(calendar)"/>
</md-input-container> </md-input-container>
<md-menu class="md-secondary" label:aria-label="Options"> <md-menu class="md-secondary" label:aria-label="Options"
md-colors="::{color: 'accent-400'}">
<md-button class="md-icon-button" label:aria-label="Options" <md-button class="md-icon-button" label:aria-label="Options"
ng-click="$mdOpenMenu()" ng-click="$mdOpenMenu()"
md-menu-origin="md-menu-origin"> md-menu-origin="md-menu-origin">
@ -269,6 +273,27 @@
</md-list> </md-list>
</section> </section>
</md-content> </md-content>
<md-toolbar class="sg-toolbar-multiple">
<div class="md-toolbar-tools sg-toolbar-tools--dense"
md-colors="::{backgroundColor: 'background-500'}"
ng-show="app.sortableCalendars.disabled">
<md-input-container class="md-flex" md-no-float="md-no-float">
<md-icon>search</md-icon>
<input ng-model="app.filter.name" type="search" label:placeholder="Filter"/>
</md-input-container>
<md-button class="md-icon-button"
ng-click="app.toggleSortableMode()">
<md-icon>reorder</md-icon>
</md-button>
</div>
<div class="md-toolbar-tools sg-toolbar-tools--dense ng-hide" layout-align="center center"
md-colors="::{backgroundColor: 'accent-600'}"
ng-hide="app.sortableCalendars.disabled">
<md-button ng-click="app.resetSort()"><var:string label:value="Reset"/></md-button>
<div class="md-flex"><!-- spacer --></div>
<md-button ng-click="app.toggleSortableMode()"><var:string label:value="Done"/></md-button>
</div>
</md-toolbar>
</md-sidenav> </md-sidenav>
<!-- Main section --> <!-- Main section -->

View File

@ -99,6 +99,12 @@
}); });
i = sibling ? _.indexOf(_.map(list, 'id'), sibling.id) : 1; i = sibling ? _.indexOf(_.map(list, 'id'), sibling.id) : 1;
list.splice(i, 0, calendar); list.splice(i, 0, calendar);
this.$Preferences.ready().then(function() {
if (Calendar.$Preferences.settings.Calendar.FoldersOrder)
// Save list order
Calendar.saveFoldersOrder(_.flatMap(Calendar.$findAll(), 'id'));
});
}; };
/** /**
@ -129,8 +135,8 @@
this.$calendars = []; this.$calendars = [];
this.$subscriptions = []; this.$subscriptions = [];
this.$webcalendars = []; this.$webcalendars = [];
Calendar.$$resource.fetch('calendarslist').then(function(data) { return Calendar.$$resource.fetch('calendarslist').then(function(data) {
Calendar.$findAll(data.calendars, writable); return Calendar.$findAll(data.calendars, writable);
}); });
} }
@ -261,6 +267,23 @@
return Calendar.$q.all(promises); return Calendar.$q.all(promises);
}; };
/**
* @function saveFoldersOrder
* @desc Save to the user's settings the current calendars order.
* @param {string[]} folders - the folders IDs
* @returns a promise of the HTTP operation
*/
Calendar.saveFoldersOrder = function(folders) {
return this.$$resource.post(null, 'saveFoldersOrder', { folders: folders }).then(function() {
Calendar.$Preferences.settings.Calendar.FoldersOrder = folders;
if (!folders)
// Calendars order was reset; reload list
return Calendar.$$resource.fetch('calendarslist').then(function(data) {
return Calendar.$findAll(data.calendars);
});
});
};
/** /**
* @function init * @function init
* @memberof Calendar.prototype * @memberof Calendar.prototype

View File

@ -28,6 +28,22 @@
vm.subscribeToFolder = subscribeToFolder; vm.subscribeToFolder = subscribeToFolder;
vm.today = today; vm.today = today;
vm.filter = { name: '' };
vm.toggleSortableMode = toggleSortableMode;
vm.resetSort = resetSort;
vm.sortableCalendars = {
disabled: true,
animation: 150,
draggable: 'md-list-item',
handle: '.md-menu',
ghostClass: 'sg-sortable-ghost',
chosenClass: 'sg-sortable-chosen',
setData: sortable_setData,
onEnd: sortable_onEnd
};
vm.sortableSubscriptions = angular.copy(vm.sortableCalendars);
vm.sortableWebCalendars = angular.copy(vm.sortableCalendars);
Preferences.ready().then(function() { Preferences.ready().then(function() {
vm.categories = _.map(Preferences.defaults.SOGoCalendarCategories, function(name) { vm.categories = _.map(Preferences.defaults.SOGoCalendarCategories, function(name) {
return { id: name.asCSSIdentifier(), return { id: name.asCSSIdentifier(),
@ -65,7 +81,7 @@
promises.push(calendar.$setActivation()); promises.push(calendar.$setActivation());
}); });
} }
if (commonList.length > 0) if (promises.length > 0 || commonList.length != newList.length || commonList.length != oldList.length)
Calendar.$q.all(promises).then(function() { Calendar.$q.all(promises).then(function() {
$rootScope.$emit('calendars:list'); $rootScope.$emit('calendars:list');
}); });
@ -73,6 +89,25 @@
true // compare for object equality true // compare for object equality
); );
function sortable_setData(dataTransfer, dragEl) {
dataTransfer.clearData();
}
function sortable_onEnd() {
Calendar.saveFoldersOrder(_.flatMap(Calendar.$findAll(), 'id'));
}
function toggleSortableMode() {
vm.sortableCalendars.disabled = !vm.sortableCalendars.disabled;
vm.sortableSubscriptions.disabled = !vm.sortableSubscriptions.disabled;
vm.sortableWebCalendars.disabled = !vm.sortableWebCalendars.disabled;
vm.filter.name = '';
}
function resetSort() {
Calendar.saveFoldersOrder();
}
function newCalendar(ev) { function newCalendar(ev) {
Dialog.prompt(l('New calendar'), l('Name of the Calendar')) Dialog.prompt(l('New calendar'), l('Name of the Calendar'))
.then(function(name) { .then(function(name) {

View File

@ -4,7 +4,7 @@
(function() { (function() {
'use strict'; 'use strict';
angular.module('SOGo.SchedulerUI', ['ui.router', 'angularFileUpload', 'SOGo.Common', 'SOGo.PreferencesUI', 'SOGo.ContactsUI', 'SOGo.MailerUI']) angular.module('SOGo.SchedulerUI', ['ui.router', 'angularFileUpload', 'SOGo.Common', 'SOGo.PreferencesUI', 'SOGo.ContactsUI', 'SOGo.MailerUI', 'ng-sortable'])
.config(configure) .config(configure)
.run(runBlock); .run(runBlock);

View File

@ -76,7 +76,7 @@ md-list-item {
} }
// Remove padding of input fields in the sidenav for better transitions between read and edit mode of a folder // Remove padding of input fields in the sidenav for better transitions between read and edit mode of a folder
md-input-container { md-input-container:not(.md-icon-left) {
margin: 0; margin: 0;
padding: 0; padding: 0;
.md-input { .md-input {
@ -231,12 +231,25 @@ div.md-tile-left {
} }
} }
&-sortable-chosen {
background-color: white;
}
&-sortable-ghost { &-sortable-ghost {
opacity: 0.5; opacity: 0.5;
} }
&-sortable-chosen { &-list-sortable {
background-color: white; ._md-secondary-container > .md-menu {
.md-button {
display: none;
}
&:before {
@extend .material-icons;
content: "\e8fe";
cursor: move;
}
}
} }
} }

View File

@ -112,6 +112,23 @@ hgroup {
flex: 1 1 auto; flex: 1 1 auto;
} }
.sg-toolbar-search { // Animate transitions from one toolbar to the other
padding: $toolbar-padding 0; .sg-toolbar-multiple {
overflow: hidden;
.md-toolbar-tools {
&.ng-hide {
transform: translateY(100%);
transition: transform 0ms;
}
transform: translateY(0%);
transition: transform 240ms;
}
} }
.sg-toolbar-tools--dense {
height: $bl * 6;
}
//.sg-toolbar-search {
// padding: $toolbar-padding 0;
//}