fix(GUI): backdrop click error on modals (#601)

When the user clicks on the backdrop of a modal, the modal promise gets
rejected, therefore triggering our application error handler.

UI Bootstrap provides a way to disable the backdrop completely, but
doesn't provide a way to allow a backdrop click to simply close the
modal rather than rejecting it, as if an issue happened.

To mitigate this issue, and still preserve the backdrop functionality,
we created `ModalService`, which abstracts the messy details of calling
`$uibModal`, and has custom logic to ignore "backdrop click" errors.

Change-Type: patch
Changelog-Entry: Fix "backdrop click" uncaught errors on modals.
Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
This commit is contained in:
Juan Cruz Viotti 2016-07-26 22:52:48 -04:00 committed by GitHub
parent 2d1b2ccf6e
commit d72364115d
10 changed files with 122 additions and 21 deletions

View File

@ -23,7 +23,7 @@
const angular = require('angular');
const MODULE_NAME = 'Etcher.Components.DriveSelector';
const DriveSelector = angular.module(MODULE_NAME, [
require('angular-ui-bootstrap'),
require('../modal/modal'),
require('../../models/drives'),
require('../../models/selection-state'),
require('../../utils/byte-size/byte-size')

View File

@ -16,7 +16,7 @@
'use strict';
module.exports = function($uibModal, $q) {
module.exports = function(ModalService, $q) {
let modal = null;
@ -34,11 +34,9 @@ module.exports = function($uibModal, $q) {
* });
*/
this.open = () => {
modal = $uibModal.open({
animation: true,
templateUrl: './components/drive-selector/templates/drive-selector-modal.tpl.html',
controller: 'DriveSelectorController as modal',
size: 'sm'
modal = ModalService.open({
template: './components/drive-selector/templates/drive-selector-modal.tpl.html',
controller: 'DriveSelectorController as modal'
});
return modal.result;

View File

@ -0,0 +1,31 @@
/*
* 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.Modal
*/
const angular = require('angular');
const MODULE_NAME = 'Etcher.Components.Modal';
const Modal = angular.module(MODULE_NAME, [
require('angular-ui-bootstrap')
]);
Modal.service('ModalService', require('./services/modal'));
module.exports = MODULE_NAME;

View File

@ -0,0 +1,74 @@
/*
* 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($uibModal, $q) {
/**
* @summary Open a modal
* @function
* @public
*
* @param {Object} options - options
* @param {String} options.template - template path
* @param {String} options.controller - controller
* @param {String} [options.size='sm'] - modal size
* @param {Object} options.resolve - modal resolves
* @returns {Object} modal
*
* @example
* ModalService.open({
* template: './path/to/modal.tpl.html',
* controller: 'DriveSelectorController as modal',
* });
*/
this.open = (options = {}) => {
_.defaults(options, {
size: 'sm'
});
const modal = $uibModal.open({
animation: true,
templateUrl: options.template,
controller: options.controller,
size: options.size,
resolve: options.resolve
});
return {
close: modal.close,
result: $q((resolve, reject) => {
modal.result
.then(resolve)
.catch((error) => {
// For some annoying reason, UI Bootstrap Modal rejects
// the result reason if the user clicks on the backdrop
// (e.g: the area surrounding the modal).
if (error !== 'backdrop click') {
return reject(error);
}
});
})
};
};
};

View File

@ -18,7 +18,7 @@
const _ = require('lodash');
module.exports = function($uibModal) {
module.exports = function(ModalService) {
/**
* @summary Open the tooltip modal
@ -37,9 +37,8 @@ module.exports = function($uibModal) {
* });
*/
this.show = (options) => {
return $uibModal.open({
animation: true,
templateUrl: './components/tooltip-modal/templates/tooltip-modal.tpl.html',
return ModalService.open({
template: './components/tooltip-modal/templates/tooltip-modal.tpl.html',
controller: 'TooltipModalController as modal',
size: 'tooltip-modal',
resolve: {

View File

@ -23,7 +23,7 @@
const angular = require('angular');
const MODULE_NAME = 'Etcher.Components.TooltipModal';
const TooltipModal = angular.module(MODULE_NAME, [
require('angular-ui-bootstrap')
require('../modal/modal')
]);
TooltipModal.controller('TooltipModalController', require('./controllers/tooltip-modal'));

View File

@ -19,7 +19,7 @@
const semver = require('semver');
const etcherLatestVersion = require('etcher-latest-version');
module.exports = function($uibModal, $http, $q, UPDATE_NOTIFIER_SLEEP_TIME, ManifestBindService, SettingsModel) {
module.exports = function($http, $q, ModalService, UPDATE_NOTIFIER_SLEEP_TIME, ManifestBindService, SettingsModel) {
/**
* @summary Get the latest available Etcher version
@ -111,9 +111,8 @@ module.exports = function($uibModal, $http, $q, UPDATE_NOTIFIER_SLEEP_TIME, Mani
* UpdateNotifierService.notify();
*/
this.notify = () => {
return $uibModal.open({
animation: true,
templateUrl: './components/update-notifier/templates/update-notifier-modal.tpl.html',
return ModalService.open({
template: './components/update-notifier/templates/update-notifier-modal.tpl.html',
controller: 'UpdateNotifierController as modal',
size: 'update-notifier'
}).result;

View File

@ -23,7 +23,7 @@
const angular = require('angular');
const MODULE_NAME = 'Etcher.Components.UpdateNotifier';
const UpdateNotifier = angular.module(MODULE_NAME, [
require('angular-ui-bootstrap'),
require('../modal/modal'),
require('../../models/settings'),
require('../../utils/manifest-bind/manifest-bind'),
require('../../os/open-external/open-external')

View File

@ -18,7 +18,7 @@
const _ = require('lodash');
module.exports = function($uibModal, SettingsModel) {
module.exports = function(ModalService, SettingsModel) {
/**
* @summary Refresh current settings
@ -62,9 +62,8 @@ module.exports = function($uibModal, SettingsModel) {
return this.refreshSettings();
}
$uibModal.open({
animation: true,
templateUrl: './pages/settings/templates/settings-dangerous-modal.tpl.html',
ModalService.open({
template: './pages/settings/templates/settings-dangerous-modal.tpl.html',
controller: 'SettingsDangerousModalController as modal',
size: 'settings-dangerous-modal',
resolve: {

View File

@ -24,6 +24,7 @@ const angular = require('angular');
const MODULE_NAME = 'Etcher.Pages.Settings';
const SettingsPage = angular.module(MODULE_NAME, [
require('angular-ui-router'),
require('../../components/modal/modal'),
require('../../models/settings')
]);