Skip to content

Commit

Permalink
(css,js) Improve progress feedback
Browse files Browse the repository at this point in the history
This ads a "ripple" effect that blocks the context when login in or
sending a message. Generic enough to be used elsewhere.

Fixes #3765
  • Loading branch information
cgx committed Jul 15, 2016
1 parent 0fe472b commit 6bbb56c
Show file tree
Hide file tree
Showing 13 changed files with 334 additions and 63 deletions.
6 changes: 6 additions & 0 deletions NEWS
@@ -1,3 +1,9 @@
3.1.5 (2016-MM-DD)
------------------

Enhancements
- [web] improve action progress when login in or sending a message (#3765, #3761)

3.1.4 (2016-07-12)
------------------

Expand Down
7 changes: 6 additions & 1 deletion UI/MailerUI/English.lproj/Localizable.strings
Expand Up @@ -305,7 +305,12 @@
"Error while uploading the file \"%{0}\":" = "Error while uploading the file \"%{0}\":";
"There is an active file upload. Closing the window will interrupt it." = "There is an active file upload. Closing the window will interrupt it.";

/* Message sending */
/* Appears while sending the message */
"Sending" = "Sending";

/* Appears when the message is successfuly sent */
"Sent" = "Sent";

"cannot send message: (smtp) all recipients discarded" = "Cannot send message: all recipients are invalid.";
"cannot send message (smtp) - recipients discarded" = "Cannot send message. The following addresses are invalid";
"cannot send message: (smtp) error when connecting" = "Cannot send message: error when connecting to the SMTP server.";
Expand Down
7 changes: 7 additions & 0 deletions UI/MainUI/English.lproj/Localizable.strings
Expand Up @@ -6,6 +6,13 @@
"Domain" = "Domain";
"Remember username" = "Remember username";
"Connect" = "Connect";

/* Appears while authentication is in progress */
"Authenticating" = "Authenticating";

/* Appears when authentication succeeds */
"Success" = "Success";

"Authentication Failed" = "Authentication Failed";
"Wrong username or password." = "Wrong username or password.";
"cookiesNotEnabled" = "You cannot login because your browser's cookies are disabled. Please enable cookies in your browser's settings and try again.";
Expand Down
43 changes: 41 additions & 2 deletions UI/Templates/MailerUI/UIxMailEditor.wox
Expand Up @@ -5,7 +5,7 @@
xmlns:const="http://www.skyrix.com/od/constant"
xmlns:label="OGo:label">

<md-dialog class="sg-mail-editor" flex="80" flex-sm="90" flex-xs="100"
<md-dialog id="mailEditor" class="sg-mail-editor" flex="80" flex-sm="90" flex-xs="100"
nv-file-drop="nv-file-drop"
nv-file-over="nv-file-over"
over-class="sg-over-dropzone"
Expand All @@ -26,7 +26,8 @@
</md-input-container>
<div flex="flex"><!-- spacer --></div>
<md-button class="sg-icon-button" ng-click="editor.send()"
ng-disabled="!(editor.message.editable.to.length > 0 || editor.message.editable.cc.length > 0 || editor.message.editable.bcc.length > 0) || messageForm.$invalid">
ng-disabled="!(editor.message.editable.to.length > 0 || editor.message.editable.cc.length > 0 || editor.message.editable.bcc.length > 0) || editor.uploader.isUploading || messageForm.$invalid"
sg-ripple-click="mailEditor">
<md-icon>send</md-icon>
</md-button>
<md-button class="sg-icon-button" ng-click="editor.save()">
Expand Down Expand Up @@ -254,5 +255,43 @@
</div>
</md-dialog-actions>
</form>
<sg-ripple class="md-default-theme md-accent md-bg"
ng-class="{ 'md-warn': editor.sendState == 'error' }"><!-- ripple background --></sg-ripple>
<sg-ripple-content class="md-default-theme md-accent md-hue-1 md-fg md-flex ng-hide"
layout="column" layout-align="center center" layout-fill="layout-fill"
ng-switch="editor.sendState">

<!-- Sending -->
<div layout="column" layout-align="center center"
ng-switch-when="sending">
<md-progress-circular class="md-hue-1"
md-mode="indeterminate"
md-diameter="48"><!-- mailbox loading progress --></md-progress-circular>
<div class="md-padding">
<var:string label:value="Sending"/>
</div>
</div>

<!-- Sent -->
<div layout="column" layout-align="center center"
ng-switch-when="sent">
<md-icon class="md-accent md-hue-1 sg-icon--large">done</md-icon>
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
<var:string label:value="Sent"/>
</div>
</div>

<!-- Error -->
<div layout="column" layout-align="center center"
ng-switch-when="error">
<md-icon class="md-accent md-hue-1 sg-icon--large">error</md-icon>
<div class="md-padding">
{{editor.errorMessage}}
</div>
<md-button sg-ripple-click="mailEditor"><var:string label:value="Close"/></md-button>
</div>

</sg-ripple-content>

</md-dialog>
</container>
68 changes: 53 additions & 15 deletions UI/Templates/MainUI/SOGoRootPage.wox
Expand Up @@ -9,30 +9,31 @@
xmlns:label="OGo:label"
const:jsFiles="Main.js, Common.js"
>
<script type="text/javascript">
var cookieUsername = '<var:string var:value="cookieUsername" const:escapeHTML="NO"/>';
</script>

<!--
MAIN CONTENT ROW
Content of the application view injected injected in the element bellow
MUST be the first html element after body
SHOULD be a main tag (with role="main")
-->
<main class="view layout-padding md-default-theme md-background md-hue-1 md-bg"
layout="row" layout-align="center start" layout-fill="layout-fill"
<main class="view md-default-theme md-background md-hue-1 md-bg"
layout-gt-md="row" layout-align-gt-md="center start" layout-fill="layout-fill"
ui-view="login"
ng-controller="LoginController as app">
<md-content class="ng-cloak md-whiteframe-z1"
layout-gt-md="row" layout-align-gt-md="start center"
layout="column" layout-align="space-between center"
md-scroll-y="true"
<md-content id="loginContent" class="ng-cloak md-whiteframe-3dp" flex="100"
layout="column" layout-gt-md="row" layout-align="start stretch"
ng-show="app.showLogin">
<div id="logo" class="md-padding">
<img const:alt="*" id="splash" rsrc:src="img/sogo-full.svg"/>
<div class="sg-logo" flex-gt-md="50">
<div layout="row" class="md-padding">
<div class="md-flex hide show-gt-md"><!-- push logo to the right on larger screens --></div>
<img const:alt="*" class="md-margin" rsrc:src="img/sogo-full.svg"/>
</div>
</div>
<div class="sg-login md-padding md-default-theme md-bg md-accent">
<script type="text/javascript">
var cookieUsername = '<var:string var:value="cookieUsername" const:escapeHTML="NO"/>';
</script>
<div id="login">

<div class="sg-login md-default-theme md-bg md-accent" flex-gt-md="50">
<div id="login" class="sg-login-content md-padding">
<form name="loginForm" layout="column"
ng-cloak="ng-cloak"
ng-submit="app.login()">
Expand All @@ -52,7 +53,7 @@

<!-- CONNECT BUTTON -->
<div layout="row" layout-align="end center">
<md-button class="md-raised md-accent md-hue-2" type="submit" ng-disabled='app.loginForm.$invalid'>
<md-button type="submit" ng-disabled='app.loginForm.$invalid' sg-ripple-click="loginContent">
<var:string label:value="Connect"/>
</md-button>
</div>
Expand Down Expand Up @@ -101,6 +102,43 @@
<md-icon class="md-fg">info</md-icon>
</md-button>
</div>
<sg-ripple class="md-default-theme md-accent md-bg"
ng-class="{ 'md-warn': app.loginState == 'error' }"><!-- ripple background --></sg-ripple>
<sg-ripple-content class="md-flex ng-hide"
layout="column" layout-align="center center" layout-fill="layout-fill"
ng-switch="app.loginState">

<!-- Authenticating -->
<div layout="column" layout-align="center center"
ng-switch-when="authenticating">
<md-progress-circular class="md-hue-1"
md-mode="indeterminate"
md-diameter="32"><!-- mailbox loading progress --></md-progress-circular>
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
<var:string label:value="Authenticating"/>
</div>
</div>

<!-- Logged in -->
<div layout="column" layout-align="center center"
ng-switch-when="logged">
<md-icon class="md-accent md-hue-1 sg-icon--large">done</md-icon>
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
<var:string label:value="Success"/>
</div>
</div>

<!-- Error -->
<div layout="column" layout-align="center center"
ng-switch-when="error">
<md-icon class="md-accent md-hue-1 sg-icon--large">error</md-icon>
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
{{app.errorMessage}}
</div>
<md-button sg-ripple-click="loginContent"><var:string label:value="Retry"/></md-button>
</div>

</sg-ripple-content>
</div>
</div>
</md-content>
Expand Down
108 changes: 108 additions & 0 deletions UI/WebServerResources/js/Common/sgRippleClick.directive.js
@@ -0,0 +1,108 @@
/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */

(function() {
'use strict';

angular
.module('SOGo.Common')
.directive('sgRippleClick', sgRippleClick);

/*
* sgRippleClick - A ripple effect to cover the parent element.
* @memberof SOGo.Common
* @restrict attribute
*
* @example:
<md-dialog id="mailEditor">
<md-button ng-click="editor.send()"
sg-ripple-click="mailEditor">Send</md-button>
</md-dialog>
*/
sgRippleClick.$inject = ['$log', '$timeout'];
function sgRippleClick($log, $timeout) {

return {
restrict: 'A',
compile: compile
};

function compile(tElement, tAttrs) {

return function postLink(scope, element, attr) {
var ripple, content, container, containerId;

// Lookup container element
containerId = element.attr('sg-ripple-click');
container = element[0].parentNode;
while (container && container.id != containerId) {
container = container.parentNode;
}
if (!container) {
$log.error('No parent element found with id ' + containerId);
return undefined;
}

// Lookup sg-ripple-content element
content = container.querySelector('sg-ripple-content');
if (!content) {
$log.error('sg-ripple-content not found inside #' + containerId);
return undefined;
}

// Lookup sg-ripple element
ripple = container.querySelector('sg-ripple');
if (ripple) {
ripple = angular.element(ripple);
}
else {
// If ripple layer doesn't exit, create it with the primary background color
ripple = angular.element('<sg-ripple class="md-default-theme md-bg"></sg-ripple>');
container.appendChild(ripple[0]);

// Hide ripple content on initialization
if (!content.classList.contains('ng-hide'))
content.classList.add('ng-hide');
}

// Register listener
element.on('click', listener);

function listener(event) {
if (element[0].hasAttribute('disabled')) {
return;
}

if (content.classList.contains('ng-hide')) {
// Show ripple
angular.element(container).css({ 'overflow': 'hidden' });
content.classList.remove('ng-hide');
angular.element(content).css({ top: container.scrollTop + 'px' });
ripple.css({
'top': (event.pageY - container.offsetTop + container.scrollTop) + 'px',
'left': (event.pageX - container.offsetLeft) + 'px',
'width': '400vmin',
'height': '400vmin'
});
}
else {
// Hide ripple layer
ripple.css({
'top': (event.pageY - container.offsetTop + container.scrollTop) + 'px',
'left': (event.pageX - container.offsetLeft) + 'px',
'height': '0px',
'width': '0px'
});
// Hide ripple content
content.classList.add('ng-hide');
// Restore overflow of container once the animation is completed
$timeout(function() {
angular.element(container).css({ 'overflow': '' });
}, 800);
}
}
};
}
}
})();
8 changes: 4 additions & 4 deletions UI/WebServerResources/js/Mailer/Message.service.js
Expand Up @@ -636,18 +636,18 @@

Message.$log.debug('send = ' + JSON.stringify(data, undefined, 2));

return Message.$$resource.post(this.$absolutePath({asDraft: true}), 'send', data).then(function(data) {
if (data.status == 'success') {
return Message.$$resource.post(this.$absolutePath({asDraft: true}), 'send', data).then(function(response) {
if (response.status == 'success') {
if (angular.isDefined(_this.origin)) {
if (_this.origin.action.startsWith('reply'))
_this.origin.message.isanswered = true;
else if (_this.origin.action == 'forward')
_this.origin.message.isforwarded = true;
}
return data;
return response;
}
else {
return Message.$q.reject(data);
return Message.$q.reject(response.data);
}
});
};
Expand Down

0 comments on commit 6bbb56c

Please sign in to comment.