refactor(GUI): replace the orange alert ribbon with a modal (#689)

As a way to simplify the design and make more use of available
components rather than creating specialised ones, we replaced the
`.alert-ribbon` component, which consisted of an orange alert appearing
at the top of the screen for when a validation failed, the drive ran out
of space, or other issues, with a modal.

This change allowed us to remove the "warning"-related colors from the
theme palette.

Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
This commit is contained in:
Juan Cruz Viotti 2016-09-07 12:40:51 -07:00 committed by GitHub
parent ce1c1d724d
commit 951b8de9fc
14 changed files with 205 additions and 188 deletions

View File

@ -4460,25 +4460,25 @@ a.thumbnail:focus,
a.thumbnail.active { a.thumbnail.active {
border-color: #ddd; } border-color: #ddd; }
.alert, .alert-ribbon { .alert {
padding: 13px; padding: 15px;
margin-bottom: 18px; margin-bottom: 18px;
border: 1px solid transparent; border: 1px solid transparent;
border-radius: 4px; } border-radius: 4px; }
.alert h4, .alert-ribbon h4 { .alert h4 {
margin-top: 0; margin-top: 0;
color: inherit; } color: inherit; }
.alert .alert-link, .alert-ribbon .alert-link { .alert .alert-link {
font-weight: bold; } font-weight: bold; }
.alert > p, .alert-ribbon > p, .alert > p,
.alert > ul, .alert-ribbon > ul { .alert > ul {
margin-bottom: 0; } margin-bottom: 0; }
.alert > p + p, .alert-ribbon > p + p { .alert > p + p {
margin-top: 5px; } margin-top: 5px; }
.alert-dismissable, .alert-dismissable,
.alert-dismissible { .alert-dismissible {
padding-right: 33px; } padding-right: 35px; }
.alert-dismissable .close, .alert-dismissable .close,
.alert-dismissible .close { .alert-dismissible .close {
position: relative; position: relative;
@ -6153,16 +6153,6 @@ body {
background-color: #2d78d2; background-color: #2d78d2;
color: #fff; } color: #fff; }
.button-warning,
.button-warning:focus {
background-color: #e99852;
color: #fff;
outline: none; }
.button-warning:hover {
background-color: #e37d25;
color: #fff; }
.button-danger, .button-danger,
.button-danger:focus { .button-danger:focus {
background-color: #d9534f; background-color: #d9534f;
@ -6209,53 +6199,6 @@ body {
background-color: #d9534f; background-color: #d9534f;
border-color: #d9534f; } border-color: #d9534f; }
/*
* Copyright 2016 Resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.alert-ribbon {
background-color: #e99852;
color: #fff;
border-color: #f7dbc3;
width: 60%;
position: fixed;
text-align: center;
left: 0;
right: 0;
margin: 0 auto;
border: 0;
border-radius: 2px;
border-top-left-radius: 0;
border-top-right-radius: 0;
top: -100%;
transition: top .5s; }
.alert-ribbon > .glyphicon:first-child, .alert-ribbon > .tick:first-child {
margin-right: 5px; }
.alert-ribbon > .glyphicon:last-child, .alert-ribbon > .tick:last-child {
margin-left: 5px; }
.alert-ribbon .button-link {
padding: 0;
font-size: inherit;
vertical-align: baseline;
border-radius: 0;
border-bottom: 1px solid;
border-color: #fff;
color: #fff; }
.alert-ribbon--open {
top: 0; }
/* /*
* Copyright 2016 Resin.io * Copyright 2016 Resin.io
* *
@ -6489,6 +6432,24 @@ body {
background-color: #f2f2f2; background-color: #f2f2f2;
word-wrap: break-word; } word-wrap: break-word; }
/*
* Copyright 2016 Resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.modal-flash-error-modal .modal-title .glyphicon, .modal-flash-error-modal .modal-title .tick {
color: #d9534f; }
/* /*
* Copyright 2016 Resin.io * Copyright 2016 Resin.io
* *

View File

@ -0,0 +1,52 @@
/*
* Copyright 2016 Resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
module.exports = function(
$uibModalInstance,
SelectionStateModel,
FlashStateModel,
AnalyticsService,
flashErrorData
) {
/**
* @summary Flash error data
* @property
* @public
*/
this.data = flashErrorData;
/**
* @summary Retry flash process
* @function
* @public
*
* @example
* FlashErrorModalController.retry();
*/
this.retry = () => {
SelectionStateModel.clear({
preserveImage: true
});
FlashStateModel.resetState();
AnalyticsService.logEvent('Restart after failure');
$uibModalInstance.dismiss();
};
};

View File

@ -0,0 +1,35 @@
/*
* Copyright 2016 Resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
/**
* @module Etcher.Components.FlashErrorModal
*/
const angular = require('angular');
const MODULE_NAME = 'Etcher.Components.FlashErrorModal';
const FlashErrorModal = angular.module(MODULE_NAME, [
require('../modal/modal'),
require('../../models/selection-state'),
require('../../models/flash-state'),
require('../../modules/analytics')
]);
FlashErrorModal.controller('FlashErrorModalController', require('./controllers/flash-error-modal'));
FlashErrorModal.service('FlashErrorModalService', require('./services/flash-error-modal'));
module.exports = MODULE_NAME;

View File

@ -0,0 +1,47 @@
/*
* Copyright 2016 Resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
const _ = require('lodash');
module.exports = function(ModalService) {
/**
* @summary Open the flash error modal
* @function
* @public
*
* @param {String} message - flash error message
* @returns {Promise}
*
* @example
* FlashErrorModalService.show('The drive is not large enough!');
*/
this.show = (message) => {
return ModalService.open({
template: './components/flash-error-modal/templates/flash-error-modal.tpl.html',
controller: 'FlashErrorModalController as modal',
size: 'flash-error-modal',
resolve: {
flashErrorData: _.constant({
message: message
})
}
}).result;
};
};

View File

@ -0,0 +1,19 @@
/*
* Copyright 2016 Resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.modal-flash-error-modal .modal-title .glyphicon {
color: $palette-theme-danger-background;
}

View File

@ -0,0 +1,17 @@
<div class="modal-header">
<h4 class="modal-title">
<span class="glyphicon glyphicon-exclamation-sign"></span>
<span>An error ocurred!</span>
</h4>
<button class="close" ng-click="modal.retry()">&times;</button>
</div>
<div class="modal-body">
<div class="modal-text">{{ ::modal.data.message }}</div>
</div>
<div class="modal-footer">
<button class="button button-danger button-block"
ng-click="modal.retry()">Retry</button>
</div>

View File

@ -23,6 +23,7 @@ module.exports = function(
DriveScannerService, DriveScannerService,
ImageWriterService, ImageWriterService,
AnalyticsService, AnalyticsService,
FlashErrorModalService,
ErrorService, ErrorService,
OSNotificationService, OSNotificationService,
OSWindowProgressService OSWindowProgressService
@ -72,10 +73,14 @@ module.exports = function(
OSNotificationService.send('Oops!', 'Looks like your flash has failed'); OSNotificationService.send('Oops!', 'Looks like your flash has failed');
if (error.code === 'EVALIDATION') { if (error.code === 'EVALIDATION') {
FlashErrorModalService.show('Your removable drive may be corrupted. Try inserting a different one and try again.');
AnalyticsService.logEvent('Validation error'); AnalyticsService.logEvent('Validation error');
} else if (error.code === 'ENOSPC') {
FlashErrorModalService.show('Not enough space on the drive. Please insert larger one and try again.');
} else { } else {
AnalyticsService.logEvent('Flash error'); FlashErrorModalService.show('Oops, seems something went wrong.');
ErrorService.reportException(error); ErrorService.reportException(error);
AnalyticsService.logEvent('Flash error');
} }
}) })

View File

@ -21,7 +21,6 @@ module.exports = function(
DrivesModel, DrivesModel,
FlashStateModel, FlashStateModel,
SettingsModel, SettingsModel,
AnalyticsService,
TooltipModalService, TooltipModalService,
OSOpenExternalService OSOpenExternalService
) { ) {
@ -34,23 +33,6 @@ module.exports = function(
this.external = OSOpenExternalService; this.external = OSOpenExternalService;
this.tooltipModal = TooltipModalService; this.tooltipModal = TooltipModalService;
/**
* @summary Restart after failure
* @function
* @public
*
* @example
* MainController.restartAfterFailure();
*/
this.restartAfterFailure = () => {
SelectionStateModel.clear({
preserveImage: true
});
FlashStateModel.resetState();
AnalyticsService.logEvent('Restart after failure');
};
/** /**
* @summary Determine if the drive step should be disabled * @summary Determine if the drive step should be disabled
* @function * @function

View File

@ -33,6 +33,7 @@ const MainPage = angular.module(MODULE_NAME, [
require('../../components/drive-selector/drive-selector'), require('../../components/drive-selector/drive-selector'),
require('../../components/tooltip-modal/tooltip-modal'), require('../../components/tooltip-modal/tooltip-modal'),
require('../../components/flash-error-modal/flash-error-modal'),
require('../../components/progress-button/progress-button'), require('../../components/progress-button/progress-button'),
require('../../os/window-progress/window-progress'), require('../../os/window-progress/window-progress'),

View File

@ -1,28 +1,4 @@
<div class="page-main row around-xs"> <div class="page-main row around-xs">
<div class="alert-ribbon" ng-class="{
'alert-ribbon--open': main.state.getLastFlashErrorCode()
}" ng-switch="main.state.getLastFlashErrorCode()">
<span class="glyphicon glyphicon-warning-sign"></span>
<span ng-switch-when="ENOSPC">
Not enough space on the drive.<br>
Please insert larger one and
<button class="button button-link" ng-click="main.restartAfterFailure()">try again</button>
</span>
<span ng-switch-when="EVALIDATION">
Your removable drive may be corrupted.
<br>Try inserting a different one and
<button class="button button-link" ng-click="main.restartAfterFailure()">press "retry"</button>
</span>
<span ng-switch-default>
Oops, seems something went wrong.
Click <button class="button button-link" ng-click="main.restartAfterFailure()">here</button> to retry
</span>
</div>
<div class="col-xs" ng-controller="ImageSelectionController as image"> <div class="col-xs" ng-controller="ImageSelectionController as image">
<div class="box text-center" os-dropzone="image.selectImage($file)"> <div class="box text-center" os-dropzone="image.selectImage($file)">
<svg-icon class="center-block" path="{{ main.selection.getImageLogo() || '../../../assets/image.svg' }}"></svg-icon> <svg-icon class="center-block" path="{{ main.selection.getImageLogo() || '../../../assets/image.svg' }}"></svg-icon>
@ -114,16 +90,11 @@
percentage="main.state.getFlashState().percentage" percentage="main.state.getFlashState().percentage"
striped="{{ main.state.getFlashState().type == 'check' }}" striped="{{ main.state.getFlashState().type == 'check' }}"
ng-attr-active="{{ main.state.isFlashing() }}" ng-attr-active="{{ main.state.isFlashing() }}"
ng-show="!main.state.getLastFlashErrorCode()"
ng-click="flash.flashImageToDrive(main.selection.getImagePath(), main.selection.getDrive())" ng-click="flash.flashImageToDrive(main.selection.getImagePath(), main.selection.getDrive())"
ng-disabled="main.shouldFlashStepBeDisabled()"> ng-disabled="main.shouldFlashStepBeDisabled() || main.state.getLastFlashErrorCode()">
<span ng-bind="flash.getProgressButtonLabel()"></span> <span ng-bind="flash.getProgressButtonLabel()"></span>
</progress-button> </progress-button>
<button class="button button-warning button-brick" ng-show="main.state.getLastFlashErrorCode()" ng-click="main.restartAfterFailure()">
<span class="glyphicon glyphicon-repeat"></span> Retry
</button>
<p class="step-footer step-footer-split" ng-show="main.state.getFlashState().speed && main.state.getFlashState().percentage != 100"> <p class="step-footer step-footer-split" ng-show="main.state.getFlashState().speed && main.state.getFlashState().percentage != 100">
<span>ETA: {{ main.state.getFlashState().eta | secondsToDate | amDateFormat:'m[m]ss[s]' }}</span> <span>ETA: {{ main.state.getFlashState().eta | secondsToDate | amDateFormat:'m[m]ss[s]' }}</span>
<span ng-bind="main.state.getFlashState().speed.toFixed(2) + ' MB/s'"></span> <span ng-bind="main.state.getFlashState().speed.toFixed(2) + ' MB/s'"></span>

View File

@ -1,66 +0,0 @@
/*
* Copyright 2016 Resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.alert-ribbon {
@extend .alert;
background-color: $palette-theme-warning-background;
color: $palette-theme-warning-foreground;
border-color: lighten($palette-theme-warning-background, 25%);
width: 60%;
position: fixed;
text-align: center;
left: 0;
right: 0;
margin: 0 auto;
border: 0;
border-radius: 2px;
border-top-left-radius: 0;
border-top-right-radius: 0;
// Animate appearance
top: -100%;
transition: top .5s;
// Align alert icons a bit better
> .glyphicon {
&:first-child {
margin-right: 5px;
}
&:last-child {
margin-left: 5px;
}
}
.button-link {
padding: 0;
font-size: inherit;
vertical-align: baseline;
border-radius: 0;
border-bottom: 1px solid;
border-color: $palette-theme-warning-foreground;
color: $palette-theme-warning-foreground;
}
}
.alert-ribbon--open {
top: 0;
}

View File

@ -66,10 +66,6 @@ $button-types-styles: (
bg: $palette-theme-primary-background, bg: $palette-theme-primary-background,
color: $palette-theme-primary-foreground color: $palette-theme-primary-foreground
), ),
warning: (
bg: $palette-theme-warning-background,
color: $palette-theme-warning-foreground
),
danger: ( danger: (
bg: $palette-theme-danger-background, bg: $palette-theme-danger-background,
color: $palette-theme-danger-foreground color: $palette-theme-danger-foreground

View File

@ -18,7 +18,6 @@ $icon-font-path: "../../node_modules/bootstrap-sass/assets/fonts/bootstrap/";
$font-size-base: 13px; $font-size-base: 13px;
$cursor-disabled: initial; $cursor-disabled: initial;
$link-hover-decoration: none; $link-hover-decoration: none;
$alert-padding: 13px;
$btn-min-width: 170px; $btn-min-width: 170px;
$link-color: #ddd; $link-color: #ddd;
@ -31,12 +30,12 @@ $link-color: #ddd;
@import "./components/caption"; @import "./components/caption";
@import "./components/button"; @import "./components/button";
@import "./components/tick"; @import "./components/tick";
@import "./components/alert-ribbon";
@import "../components/modal/styles/modal"; @import "../components/modal/styles/modal";
@import "../components/update-notifier/styles/update-notifier"; @import "../components/update-notifier/styles/update-notifier";
@import "../components/progress-button/styles/progress-button"; @import "../components/progress-button/styles/progress-button";
@import "../components/drive-selector/styles/drive-selector"; @import "../components/drive-selector/styles/drive-selector";
@import "../components/tooltip-modal/styles/tooltip-modal"; @import "../components/tooltip-modal/styles/tooltip-modal";
@import "../components/flash-error-modal/styles/flash-error-modal";
@import "../pages/main/styles/main"; @import "../pages/main/styles/main";
@import "../pages/settings/styles/settings"; @import "../pages/settings/styles/settings";
@import "../pages/finish/styles/finish"; @import "../pages/finish/styles/finish";

View File

@ -29,8 +29,6 @@ $palette-theme-default-background: #ececec;
$palette-theme-default-foreground: #b3b3b3; $palette-theme-default-foreground: #b3b3b3;
$palette-theme-primary-background: #5793db; $palette-theme-primary-background: #5793db;
$palette-theme-primary-foreground: #fff; $palette-theme-primary-foreground: #fff;
$palette-theme-warning-background: #e99852;
$palette-theme-warning-foreground: #fff;
$palette-theme-danger-background: #d9534f; $palette-theme-danger-background: #d9534f;
$palette-theme-danger-foreground: #fff; $palette-theme-danger-foreground: #fff;
$palette-theme-success-background: #5fb835; $palette-theme-success-background: #5fb835;